合并分支 'test' 到 'master'
Test 查看合并请求 !19
正在显示
62 个修改的文件
包含
2627 行增加
和
1079 行删除
| @@ -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 | +} |
| @@ -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 | +} |
pkg/lib/exceltool/reader.go
0 → 100644
| 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(¶m); 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 | } |
tags
已删除
100644 → 0
此 diff 太大无法显示。
vendor/github.com/beego/beego/v2/LICENSE
0 → 100644
| 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 |
vendor/github.com/codemodus/kace/LICENSE
已删除
100644 → 0
| 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 |
vendor/github.com/codemodus/kace/go.mod
已删除
100644 → 0
| 1 | -module github.com/codemodus/kace |
vendor/github.com/codemodus/kace/kace.go
已删除
100644 → 0
| 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 | -} |
| 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 | -[](https://travis-ci.org/go-pg/pg) | ||
| 4 | -[](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc) | 3 | +[](https://travis-ci.org/go-pg/pg) |
| 4 | +[](https://pkg.go.dev/github.com/go-pg/pg/v10) | ||
| 5 | +[](https://pg.uptrace.dev/) | ||
| 6 | +[](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)) |
-
请 注册 或 登录 后发表评论