合并分支 '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 | -[![Build Status](https://travis-ci.org/go-pg/pg.svg?branch=master)](https://travis-ci.org/go-pg/pg) | ||
4 | -[![GoDoc](https://godoc.org/github.com/go-pg/pg?status.svg)](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc) | 3 | +[![Build Status](https://travis-ci.org/go-pg/pg.svg?branch=v10)](https://travis-ci.org/go-pg/pg) |
4 | +[![PkgGoDev](https://pkg.go.dev/badge/github.com/go-pg/pg/v10)](https://pkg.go.dev/github.com/go-pg/pg/v10) | ||
5 | +[![Documentation](https://img.shields.io/badge/pg-documentation-informational)](https://pg.uptrace.dev/) | ||
6 | +[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj) | ||
5 | 7 | ||
6 | -- [Docs](https://pg.uptrace.dev) | 8 | +> :heart: [**Uptrace.dev** - distributed traces, logs, and errors in one place](https://uptrace.dev) |
9 | + | ||
10 | +- Join [Discord](https://discord.gg/rWtp5Aj) to ask questions. | ||
11 | +- [Documentation](https://pg.uptrace.dev) | ||
7 | - [Reference](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc) | 12 | - [Reference](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc) |
8 | - [Examples](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#pkg-examples) | 13 | - [Examples](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#pkg-examples) |
14 | +- Example projects: | ||
15 | + - [treemux](https://github.com/uptrace/go-treemux-realworld-example-app) | ||
16 | + - [gin](https://github.com/gogjango/gjango) | ||
17 | + - [go-kit](https://github.com/Tsovak/rest-api-demo) | ||
18 | + - [aah framework](https://github.com/kieusonlam/golamapi) | ||
19 | +- [GraphQL Tutorial on YouTube](https://www.youtube.com/playlist?list=PLzQWIQOqeUSNwXcneWYJHUREAIucJ5UZn). | ||
9 | 20 | ||
10 | ## Ecosystem | 21 | ## Ecosystem |
11 | 22 | ||
12 | - Migrations by [vmihailenco](https://github.com/go-pg/migrations) and | 23 | - Migrations by [vmihailenco](https://github.com/go-pg/migrations) and |
13 | [robinjoseph08](https://github.com/robinjoseph08/go-pg-migrations). | 24 | [robinjoseph08](https://github.com/robinjoseph08/go-pg-migrations). |
25 | +- [Genna - cli tool for generating go-pg models](https://github.com/dizzyfool/genna). | ||
26 | +- [urlstruct](https://github.com/go-pg/urlstruct) to decode `url.Values` into structs. | ||
14 | - [Sharding](https://github.com/go-pg/sharding). | 27 | - [Sharding](https://github.com/go-pg/sharding). |
15 | -- [Model generator from SQL tables](https://github.com/dizzyfool/genna). | ||
16 | -- [urlstruct](https://github.com/go-pg/urlstruct) to decode `url.Values` into | ||
17 | - structs. | ||
18 | - | ||
19 | -## Sponsors | ||
20 | - | ||
21 | -- [**Uptrace.dev** - distributed traces and metrics](https://uptrace.dev) | ||
22 | 28 | ||
23 | ## Features | 29 | ## Features |
24 | 30 | ||
@@ -26,71 +32,200 @@ | @@ -26,71 +32,200 @@ | ||
26 | - sql.NullBool, sql.NullString, sql.NullInt64, sql.NullFloat64 and | 32 | - sql.NullBool, sql.NullString, sql.NullInt64, sql.NullFloat64 and |
27 | [pg.NullTime](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#NullTime). | 33 | [pg.NullTime](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#NullTime). |
28 | - [sql.Scanner](http://golang.org/pkg/database/sql/#Scanner) and | 34 | - [sql.Scanner](http://golang.org/pkg/database/sql/#Scanner) and |
29 | - [sql/driver.Valuer](http://golang.org/pkg/database/sql/driver/#Valuer) | ||
30 | - interfaces. | 35 | + [sql/driver.Valuer](http://golang.org/pkg/database/sql/driver/#Valuer) interfaces. |
31 | - Structs, maps and arrays are marshalled as JSON by default. | 36 | - Structs, maps and arrays are marshalled as JSON by default. |
32 | - PostgreSQL multidimensional Arrays using | 37 | - PostgreSQL multidimensional Arrays using |
33 | [array tag](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-PostgresArrayStructTag) | 38 | [array tag](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-PostgresArrayStructTag) |
34 | - and | ||
35 | - [Array wrapper](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Array). | 39 | + and [Array wrapper](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Array). |
36 | - Hstore using | 40 | - Hstore using |
37 | [hstore tag](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-HstoreStructTag) | 41 | [hstore tag](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-HstoreStructTag) |
38 | - and | ||
39 | - [Hstore wrapper](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Hstore). | 42 | + and [Hstore wrapper](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Hstore). |
40 | - [Composite types](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-CompositeType). | 43 | - [Composite types](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-CompositeType). |
41 | -- All struct fields are nullable by default and zero values (empty string, 0, | ||
42 | - zero time, empty map or slice, nil ptr) are marshalled as SQL `NULL`. | ||
43 | - `pg:",notnull"` is used to add SQL `NOT NULL` constraint and `pg:",use_zero"` | ||
44 | - to allow Go zero values. | 44 | +- All struct fields are nullable by default and zero values (empty string, 0, zero time, empty map |
45 | + or slice, nil ptr) are marshalled as SQL `NULL`. `pg:",notnull"` is used to add SQL `NOT NULL` | ||
46 | + constraint and `pg:",use_zero"` to allow Go zero values. | ||
45 | - [Transactions](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Begin). | 47 | - [Transactions](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Begin). |
46 | - [Prepared statements](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Prepare). | 48 | - [Prepared statements](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Prepare). |
47 | -- [Notifications](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Listener) | ||
48 | - using `LISTEN` and `NOTIFY`. | ||
49 | -- [Copying data](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-CopyFrom) | ||
50 | - using `COPY FROM` and `COPY TO`. | ||
51 | -- [Timeouts](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#Options) and | ||
52 | - canceling queries using context.Context. | 49 | +- [Notifications](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Listener) using |
50 | + `LISTEN` and `NOTIFY`. | ||
51 | +- [Copying data](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-CopyFrom) using | ||
52 | + `COPY FROM` and `COPY TO`. | ||
53 | +- [Timeouts](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#Options) and canceling queries using | ||
54 | + context.Context. | ||
53 | - Automatic connection pooling with | 55 | - Automatic connection pooling with |
54 | - [circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) | ||
55 | - support. | 56 | + [circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) support. |
56 | - Queries retry on network errors. | 57 | - Queries retry on network errors. |
57 | - Working with models using | 58 | - Working with models using |
58 | - [ORM](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model) and | ||
59 | - [SQL](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Query). | 59 | + [ORM](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model) and |
60 | + [SQL](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Query). | ||
60 | - Scanning variables using | 61 | - Scanning variables using |
61 | - [ORM](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Select-SomeColumnsIntoVars) | 62 | + [ORM](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SelectSomeColumnsIntoVars) |
62 | and [SQL](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Scan). | 63 | and [SQL](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Scan). |
63 | -- [SelectOrInsert](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Insert-SelectOrInsert) | 64 | +- [SelectOrInsert](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-InsertSelectOrInsert) |
64 | using on-conflict. | 65 | using on-conflict. |
65 | -- [INSERT ... ON CONFLICT DO UPDATE](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Insert-OnConflictDoUpdate) | 66 | +- [INSERT ... ON CONFLICT DO UPDATE](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-InsertOnConflictDoUpdate) |
66 | using ORM. | 67 | using ORM. |
67 | - Bulk/batch | 68 | - Bulk/batch |
68 | - [inserts](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Insert-BulkInsert), | ||
69 | - [updates](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Update-BulkUpdate), | ||
70 | - and | ||
71 | - [deletes](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Delete-BulkDelete). | 69 | + [inserts](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BulkInsert), |
70 | + [updates](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BulkUpdate), and | ||
71 | + [deletes](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BulkDelete). | ||
72 | - Common table expressions using | 72 | - Common table expressions using |
73 | - [WITH](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Select-With) | ||
74 | - and | ||
75 | - [WrapWith](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Select-WrapWith). | ||
76 | -- [CountEstimate](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-CountEstimate) | 73 | + [WITH](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SelectWith) and |
74 | + [WrapWith](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SelectWrapWith). | ||
75 | +- [CountEstimate](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-CountEstimate) | ||
77 | using `EXPLAIN` to get | 76 | using `EXPLAIN` to get |
78 | [estimated number of matching rows](https://wiki.postgresql.org/wiki/Count_estimate). | 77 | [estimated number of matching rows](https://wiki.postgresql.org/wiki/Count_estimate). |
79 | - ORM supports | 78 | - ORM supports |
80 | - [has one](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-HasOne), | ||
81 | - [belongs to](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-BelongsTo), | ||
82 | - [has many](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-HasMany), | ||
83 | - and | ||
84 | - [many to many](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-ManyToMany) | 79 | + [has one](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-HasOne), |
80 | + [belongs to](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BelongsTo), | ||
81 | + [has many](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-HasMany), and | ||
82 | + [many to many](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-ManyToMany) | ||
85 | with composite/multi-column primary keys. | 83 | with composite/multi-column primary keys. |
86 | -- [Soft deletes](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-SoftDelete). | ||
87 | -- [Creating tables from structs](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-CreateTable). | ||
88 | -- [ForEach](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-ForEach) | ||
89 | - that calls a function for each row returned by the query without loading all | ||
90 | - rows into the memory. | 84 | +- [Soft deletes](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SoftDelete). |
85 | +- [Creating tables from structs](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-CreateTable). | ||
86 | +- [ForEach](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-ForEach) that calls | ||
87 | + a function for each row returned by the query without loading all rows into the memory. | ||
91 | - Works with PgBouncer in transaction pooling mode. | 88 | - Works with PgBouncer in transaction pooling mode. |
92 | 89 | ||
90 | +## Installation | ||
91 | + | ||
92 | +go-pg supports 2 last Go versions and requires a Go version with | ||
93 | +[modules](https://github.com/golang/go/wiki/Modules) support. So make sure to initialize a Go | ||
94 | +module: | ||
95 | + | ||
96 | +```shell | ||
97 | +go mod init github.com/my/repo | ||
98 | +``` | ||
99 | + | ||
100 | +And then install go-pg (note _v10_ in the import; omitting it is a popular mistake): | ||
101 | + | ||
102 | +```shell | ||
103 | +go get github.com/go-pg/pg/v10 | ||
104 | +``` | ||
105 | + | ||
106 | +## Quickstart | ||
107 | + | ||
108 | +```go | ||
109 | +package pg_test | ||
110 | + | ||
111 | +import ( | ||
112 | + "fmt" | ||
113 | + | ||
114 | + "github.com/go-pg/pg/v10" | ||
115 | + "github.com/go-pg/pg/v10/orm" | ||
116 | +) | ||
117 | + | ||
118 | +type User struct { | ||
119 | + Id int64 | ||
120 | + Name string | ||
121 | + Emails []string | ||
122 | +} | ||
123 | + | ||
124 | +func (u User) String() string { | ||
125 | + return fmt.Sprintf("User<%d %s %v>", u.Id, u.Name, u.Emails) | ||
126 | +} | ||
127 | + | ||
128 | +type Story struct { | ||
129 | + Id int64 | ||
130 | + Title string | ||
131 | + AuthorId int64 | ||
132 | + Author *User `pg:"rel:has-one"` | ||
133 | +} | ||
134 | + | ||
135 | +func (s Story) String() string { | ||
136 | + return fmt.Sprintf("Story<%d %s %s>", s.Id, s.Title, s.Author) | ||
137 | +} | ||
138 | + | ||
139 | +func ExampleDB_Model() { | ||
140 | + db := pg.Connect(&pg.Options{ | ||
141 | + User: "postgres", | ||
142 | + }) | ||
143 | + defer db.Close() | ||
144 | + | ||
145 | + err := createSchema(db) | ||
146 | + if err != nil { | ||
147 | + panic(err) | ||
148 | + } | ||
149 | + | ||
150 | + user1 := &User{ | ||
151 | + Name: "admin", | ||
152 | + Emails: []string{"admin1@admin", "admin2@admin"}, | ||
153 | + } | ||
154 | + _, err = db.Model(user1).Insert() | ||
155 | + if err != nil { | ||
156 | + panic(err) | ||
157 | + } | ||
158 | + | ||
159 | + _, err = db.Model(&User{ | ||
160 | + Name: "root", | ||
161 | + Emails: []string{"root1@root", "root2@root"}, | ||
162 | + }).Insert() | ||
163 | + if err != nil { | ||
164 | + panic(err) | ||
165 | + } | ||
166 | + | ||
167 | + story1 := &Story{ | ||
168 | + Title: "Cool story", | ||
169 | + AuthorId: user1.Id, | ||
170 | + } | ||
171 | + _, err = db.Model(story1).Insert() | ||
172 | + if err != nil { | ||
173 | + panic(err) | ||
174 | + } | ||
175 | + | ||
176 | + // Select user by primary key. | ||
177 | + user := &User{Id: user1.Id} | ||
178 | + err = db.Model(user).WherePK().Select() | ||
179 | + if err != nil { | ||
180 | + panic(err) | ||
181 | + } | ||
182 | + | ||
183 | + // Select all users. | ||
184 | + var users []User | ||
185 | + err = db.Model(&users).Select() | ||
186 | + if err != nil { | ||
187 | + panic(err) | ||
188 | + } | ||
189 | + | ||
190 | + // Select story and associated author in one query. | ||
191 | + story := new(Story) | ||
192 | + err = db.Model(story). | ||
193 | + Relation("Author"). | ||
194 | + Where("story.id = ?", story1.Id). | ||
195 | + Select() | ||
196 | + if err != nil { | ||
197 | + panic(err) | ||
198 | + } | ||
199 | + | ||
200 | + fmt.Println(user) | ||
201 | + fmt.Println(users) | ||
202 | + fmt.Println(story) | ||
203 | + // Output: User<1 admin [admin1@admin admin2@admin]> | ||
204 | + // [User<1 admin [admin1@admin admin2@admin]> User<2 root [root1@root root2@root]>] | ||
205 | + // Story<1 Cool story User<1 admin [admin1@admin admin2@admin]>> | ||
206 | +} | ||
207 | + | ||
208 | +// createSchema creates database schema for User and Story models. | ||
209 | +func createSchema(db *pg.DB) error { | ||
210 | + models := []interface{}{ | ||
211 | + (*User)(nil), | ||
212 | + (*Story)(nil), | ||
213 | + } | ||
214 | + | ||
215 | + for _, model := range models { | ||
216 | + err := db.Model(model).CreateTable(&orm.CreateTableOptions{ | ||
217 | + Temp: true, | ||
218 | + }) | ||
219 | + if err != nil { | ||
220 | + return err | ||
221 | + } | ||
222 | + } | ||
223 | + return nil | ||
224 | +} | ||
225 | +``` | ||
226 | + | ||
93 | ## See also | 227 | ## See also |
94 | 228 | ||
229 | +- [Fast and flexible HTTP router](https://github.com/vmihailenco/treemux) | ||
95 | - [Golang msgpack](https://github.com/vmihailenco/msgpack) | 230 | - [Golang msgpack](https://github.com/vmihailenco/msgpack) |
96 | - [Golang message task queue](https://github.com/vmihailenco/taskq) | 231 | - [Golang message task queue](https://github.com/vmihailenco/taskq) |
@@ -5,12 +5,13 @@ import ( | @@ -5,12 +5,13 @@ import ( | ||
5 | "io" | 5 | "io" |
6 | "time" | 6 | "time" |
7 | 7 | ||
8 | - "go.opentelemetry.io/otel/api/kv" | ||
9 | - "go.opentelemetry.io/otel/api/trace" | 8 | + "go.opentelemetry.io/otel/label" |
9 | + "go.opentelemetry.io/otel/trace" | ||
10 | 10 | ||
11 | "github.com/go-pg/pg/v10/internal" | 11 | "github.com/go-pg/pg/v10/internal" |
12 | "github.com/go-pg/pg/v10/internal/pool" | 12 | "github.com/go-pg/pg/v10/internal/pool" |
13 | "github.com/go-pg/pg/v10/orm" | 13 | "github.com/go-pg/pg/v10/orm" |
14 | + "github.com/go-pg/pg/v10/types" | ||
14 | ) | 15 | ) |
15 | 16 | ||
16 | type baseDB struct { | 17 | type baseDB struct { |
@@ -83,14 +84,14 @@ func (db *baseDB) getConn(ctx context.Context) (*pool.Conn, error) { | @@ -83,14 +84,14 @@ func (db *baseDB) getConn(ctx context.Context) (*pool.Conn, error) { | ||
83 | return cn, nil | 84 | return cn, nil |
84 | } | 85 | } |
85 | 86 | ||
86 | - err = internal.WithSpan(ctx, "init_conn", func(ctx context.Context, span trace.Span) error { | 87 | + err = internal.WithSpan(ctx, "pg.init_conn", func(ctx context.Context, span trace.Span) error { |
87 | return db.initConn(ctx, cn) | 88 | return db.initConn(ctx, cn) |
88 | }) | 89 | }) |
89 | if err != nil { | 90 | if err != nil { |
90 | - db.pool.Remove(cn, err) | ||
91 | - // It is safe to reset SingleConnPool if conn can't be initialized. | ||
92 | - if p, ok := db.pool.(*pool.SingleConnPool); ok { | ||
93 | - _ = p.Reset() | 91 | + db.pool.Remove(ctx, cn, err) |
92 | + // It is safe to reset StickyConnPool if conn can't be initialized. | ||
93 | + if p, ok := db.pool.(*pool.StickyConnPool); ok { | ||
94 | + _ = p.Reset(ctx) | ||
94 | } | 95 | } |
95 | if err := internal.Unwrap(err); err != nil { | 96 | if err := internal.Unwrap(err); err != nil { |
96 | return nil, err | 97 | return nil, err |
@@ -101,45 +102,44 @@ func (db *baseDB) getConn(ctx context.Context) (*pool.Conn, error) { | @@ -101,45 +102,44 @@ func (db *baseDB) getConn(ctx context.Context) (*pool.Conn, error) { | ||
101 | return cn, nil | 102 | return cn, nil |
102 | } | 103 | } |
103 | 104 | ||
104 | -func (db *baseDB) initConn(c context.Context, cn *pool.Conn) error { | 105 | +func (db *baseDB) initConn(ctx context.Context, cn *pool.Conn) error { |
105 | if cn.Inited { | 106 | if cn.Inited { |
106 | return nil | 107 | return nil |
107 | } | 108 | } |
108 | cn.Inited = true | 109 | cn.Inited = true |
109 | 110 | ||
110 | if db.opt.TLSConfig != nil { | 111 | if db.opt.TLSConfig != nil { |
111 | - err := db.enableSSL(c, cn, db.opt.TLSConfig) | 112 | + err := db.enableSSL(ctx, cn, db.opt.TLSConfig) |
112 | if err != nil { | 113 | if err != nil { |
113 | return err | 114 | return err |
114 | } | 115 | } |
115 | } | 116 | } |
116 | 117 | ||
117 | - err := db.startup(c, cn, db.opt.User, db.opt.Password, db.opt.Database, db.opt.ApplicationName) | 118 | + err := db.startup(ctx, cn, db.opt.User, db.opt.Password, db.opt.Database, db.opt.ApplicationName) |
118 | if err != nil { | 119 | if err != nil { |
119 | return err | 120 | return err |
120 | } | 121 | } |
121 | 122 | ||
122 | if db.opt.OnConnect != nil { | 123 | if db.opt.OnConnect != nil { |
123 | - p := pool.NewSingleConnPool(nil) | ||
124 | - p.SetConn(cn) | ||
125 | - return db.opt.OnConnect(newConn(c, db.withPool(p))) | 124 | + p := pool.NewSingleConnPool(db.pool, cn) |
125 | + return db.opt.OnConnect(ctx, newConn(ctx, db.withPool(p))) | ||
126 | } | 126 | } |
127 | 127 | ||
128 | return nil | 128 | return nil |
129 | } | 129 | } |
130 | 130 | ||
131 | -func (db *baseDB) releaseConn(cn *pool.Conn, err error) { | 131 | +func (db *baseDB) releaseConn(ctx context.Context, cn *pool.Conn, err error) { |
132 | if isBadConn(err, false) { | 132 | if isBadConn(err, false) { |
133 | - db.pool.Remove(cn, err) | 133 | + db.pool.Remove(ctx, cn, err) |
134 | } else { | 134 | } else { |
135 | - db.pool.Put(cn) | 135 | + db.pool.Put(ctx, cn) |
136 | } | 136 | } |
137 | } | 137 | } |
138 | 138 | ||
139 | func (db *baseDB) withConn( | 139 | func (db *baseDB) withConn( |
140 | ctx context.Context, fn func(context.Context, *pool.Conn) error, | 140 | ctx context.Context, fn func(context.Context, *pool.Conn) error, |
141 | ) error { | 141 | ) error { |
142 | - return internal.WithSpan(ctx, "with_conn", func(ctx context.Context, span trace.Span) error { | 142 | + return internal.WithSpan(ctx, "pg.with_conn", func(ctx context.Context, span trace.Span) error { |
143 | cn, err := db.getConn(ctx) | 143 | cn, err := db.getConn(ctx) |
144 | if err != nil { | 144 | if err != nil { |
145 | return err | 145 | return err |
@@ -154,7 +154,7 @@ func (db *baseDB) withConn( | @@ -154,7 +154,7 @@ func (db *baseDB) withConn( | ||
154 | case <-ctx.Done(): | 154 | case <-ctx.Done(): |
155 | err := db.cancelRequest(cn.ProcessID, cn.SecretKey) | 155 | err := db.cancelRequest(cn.ProcessID, cn.SecretKey) |
156 | if err != nil { | 156 | if err != nil { |
157 | - internal.Logger.Printf("cancelRequest failed: %s", err) | 157 | + internal.Logger.Printf(ctx, "cancelRequest failed: %s", err) |
158 | } | 158 | } |
159 | // Signal end of conn use. | 159 | // Signal end of conn use. |
160 | fnDone <- struct{}{} | 160 | fnDone <- struct{}{} |
@@ -169,7 +169,7 @@ func (db *baseDB) withConn( | @@ -169,7 +169,7 @@ func (db *baseDB) withConn( | ||
169 | case fnDone <- struct{}{}: // signal fn finish, skip cancel goroutine | 169 | case fnDone <- struct{}{}: // signal fn finish, skip cancel goroutine |
170 | } | 170 | } |
171 | } | 171 | } |
172 | - db.releaseConn(cn, err) | 172 | + db.releaseConn(ctx, cn, err) |
173 | }() | 173 | }() |
174 | 174 | ||
175 | err = fn(ctx, cn) | 175 | err = fn(ctx, cn) |
@@ -179,9 +179,12 @@ func (db *baseDB) withConn( | @@ -179,9 +179,12 @@ func (db *baseDB) withConn( | ||
179 | 179 | ||
180 | func (db *baseDB) shouldRetry(err error) bool { | 180 | func (db *baseDB) shouldRetry(err error) bool { |
181 | switch err { | 181 | switch err { |
182 | + case io.EOF, io.ErrUnexpectedEOF: | ||
183 | + return true | ||
182 | case nil, context.Canceled, context.DeadlineExceeded: | 184 | case nil, context.Canceled, context.DeadlineExceeded: |
183 | return false | 185 | return false |
184 | } | 186 | } |
187 | + | ||
185 | if pgerr, ok := err.(Error); ok { | 188 | if pgerr, ok := err.(Error); ok { |
186 | switch pgerr.Field('C') { | 189 | switch pgerr.Field('C') { |
187 | case "40001", // serialization_failure | 190 | case "40001", // serialization_failure |
@@ -194,7 +197,12 @@ func (db *baseDB) shouldRetry(err error) bool { | @@ -194,7 +197,12 @@ func (db *baseDB) shouldRetry(err error) bool { | ||
194 | return false | 197 | return false |
195 | } | 198 | } |
196 | } | 199 | } |
197 | - return isNetworkError(err) | 200 | + |
201 | + if _, ok := err.(timeoutError); ok { | ||
202 | + return true | ||
203 | + } | ||
204 | + | ||
205 | + return false | ||
198 | } | 206 | } |
199 | 207 | ||
200 | // Close closes the database client, releasing any open resources. | 208 | // Close closes the database client, releasing any open resources. |
@@ -233,9 +241,9 @@ func (db *baseDB) exec(ctx context.Context, query interface{}, params ...interfa | @@ -233,9 +241,9 @@ func (db *baseDB) exec(ctx context.Context, query interface{}, params ...interfa | ||
233 | for attempt := 0; attempt <= db.opt.MaxRetries; attempt++ { | 241 | for attempt := 0; attempt <= db.opt.MaxRetries; attempt++ { |
234 | attempt := attempt | 242 | attempt := attempt |
235 | 243 | ||
236 | - lastErr = internal.WithSpan(ctx, "exec", func(ctx context.Context, span trace.Span) error { | 244 | + lastErr = internal.WithSpan(ctx, "pg.exec", func(ctx context.Context, span trace.Span) error { |
237 | if attempt > 0 { | 245 | if attempt > 0 { |
238 | - span.SetAttributes(kv.Int("retry", attempt)) | 246 | + span.SetAttributes(label.Int("retry", attempt)) |
239 | 247 | ||
240 | if err := internal.Sleep(ctx, db.retryBackoff(attempt-1)); err != nil { | 248 | if err := internal.Sleep(ctx, db.retryBackoff(attempt-1)); err != nil { |
241 | return err | 249 | return err |
@@ -311,9 +319,9 @@ func (db *baseDB) query(ctx context.Context, model, query interface{}, params .. | @@ -311,9 +319,9 @@ func (db *baseDB) query(ctx context.Context, model, query interface{}, params .. | ||
311 | for attempt := 0; attempt <= db.opt.MaxRetries; attempt++ { | 319 | for attempt := 0; attempt <= db.opt.MaxRetries; attempt++ { |
312 | attempt := attempt | 320 | attempt := attempt |
313 | 321 | ||
314 | - lastErr = internal.WithSpan(ctx, "query", func(ctx context.Context, span trace.Span) error { | 322 | + lastErr = internal.WithSpan(ctx, "pg.query", func(ctx context.Context, span trace.Span) error { |
315 | if attempt > 0 { | 323 | if attempt > 0 { |
316 | - span.SetAttributes(kv.Int("retry", attempt)) | 324 | + span.SetAttributes(label.Int("retry", attempt)) |
317 | 325 | ||
318 | if err := internal.Sleep(ctx, db.retryBackoff(attempt-1)); err != nil { | 326 | if err := internal.Sleep(ctx, db.retryBackoff(attempt-1)); err != nil { |
319 | return err | 327 | return err |
@@ -373,7 +381,7 @@ func (db *baseDB) CopyFrom(r io.Reader, query interface{}, params ...interface{} | @@ -373,7 +381,7 @@ func (db *baseDB) CopyFrom(r io.Reader, query interface{}, params ...interface{} | ||
373 | return res, err | 381 | return res, err |
374 | } | 382 | } |
375 | 383 | ||
376 | -// TODO: don't get/put conn in the pool | 384 | +// TODO: don't get/put conn in the pool. |
377 | func (db *baseDB) copyFrom( | 385 | func (db *baseDB) copyFrom( |
378 | ctx context.Context, cn *pool.Conn, r io.Reader, query interface{}, params ...interface{}, | 386 | ctx context.Context, cn *pool.Conn, r io.Reader, query interface{}, params ...interface{}, |
379 | ) (res Result, err error) { | 387 | ) (res Result, err error) { |
@@ -396,6 +404,7 @@ func (db *baseDB) copyFrom( | @@ -396,6 +404,7 @@ func (db *baseDB) copyFrom( | ||
396 | return nil, err | 404 | return nil, err |
397 | } | 405 | } |
398 | 406 | ||
407 | + // Note that afterQuery uses the err. | ||
399 | defer func() { | 408 | defer func() { |
400 | if afterQueryErr := db.afterQuery(ctx, evt, res, err); afterQueryErr != nil { | 409 | if afterQueryErr := db.afterQuery(ctx, evt, res, err); afterQueryErr != nil { |
401 | err = afterQueryErr | 410 | err = afterQueryErr |
@@ -434,7 +443,7 @@ func (db *baseDB) copyFrom( | @@ -434,7 +443,7 @@ func (db *baseDB) copyFrom( | ||
434 | return nil, err | 443 | return nil, err |
435 | } | 444 | } |
436 | 445 | ||
437 | - err = cn.WithReader(ctx, db.opt.ReadTimeout, func(rd *pool.BufReader) error { | 446 | + err = cn.WithReader(ctx, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error { |
438 | res, err = readReadyForQuery(rd) | 447 | res, err = readReadyForQuery(rd) |
439 | return err | 448 | return err |
440 | }) | 449 | }) |
@@ -456,7 +465,7 @@ func (db *baseDB) CopyTo(w io.Writer, query interface{}, params ...interface{}) | @@ -456,7 +465,7 @@ func (db *baseDB) CopyTo(w io.Writer, query interface{}, params ...interface{}) | ||
456 | } | 465 | } |
457 | 466 | ||
458 | func (db *baseDB) copyTo( | 467 | func (db *baseDB) copyTo( |
459 | - c context.Context, cn *pool.Conn, w io.Writer, query interface{}, params ...interface{}, | 468 | + ctx context.Context, cn *pool.Conn, w io.Writer, query interface{}, params ...interface{}, |
460 | ) (res Result, err error) { | 469 | ) (res Result, err error) { |
461 | var evt *QueryEvent | 470 | var evt *QueryEvent |
462 | 471 | ||
@@ -472,25 +481,26 @@ func (db *baseDB) copyTo( | @@ -472,25 +481,26 @@ func (db *baseDB) copyTo( | ||
472 | model, _ = params[len(params)-1].(orm.TableModel) | 481 | model, _ = params[len(params)-1].(orm.TableModel) |
473 | } | 482 | } |
474 | 483 | ||
475 | - c, evt, err = db.beforeQuery(c, db.db, model, query, params, wb.Query()) | 484 | + ctx, evt, err = db.beforeQuery(ctx, db.db, model, query, params, wb.Query()) |
476 | if err != nil { | 485 | if err != nil { |
477 | return nil, err | 486 | return nil, err |
478 | } | 487 | } |
479 | 488 | ||
489 | + // Note that afterQuery uses the err. | ||
480 | defer func() { | 490 | defer func() { |
481 | - if afterQueryErr := db.afterQuery(c, evt, res, err); afterQueryErr != nil { | 491 | + if afterQueryErr := db.afterQuery(ctx, evt, res, err); afterQueryErr != nil { |
482 | err = afterQueryErr | 492 | err = afterQueryErr |
483 | } | 493 | } |
484 | }() | 494 | }() |
485 | 495 | ||
486 | - err = cn.WithWriter(c, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error { | 496 | + err = cn.WithWriter(ctx, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error { |
487 | return writeQueryMsg(wb, db.fmter, query, params...) | 497 | return writeQueryMsg(wb, db.fmter, query, params...) |
488 | }) | 498 | }) |
489 | if err != nil { | 499 | if err != nil { |
490 | return nil, err | 500 | return nil, err |
491 | } | 501 | } |
492 | 502 | ||
493 | - err = cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.BufReader) error { | 503 | + err = cn.WithReader(ctx, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error { |
494 | err := readCopyOutResponse(rd) | 504 | err := readCopyOutResponse(rd) |
495 | if err != nil { | 505 | if err != nil { |
496 | return err | 506 | return err |
@@ -522,52 +532,6 @@ func (db *baseDB) ModelContext(c context.Context, model ...interface{}) *orm.Que | @@ -522,52 +532,6 @@ func (db *baseDB) ModelContext(c context.Context, model ...interface{}) *orm.Que | ||
522 | return orm.NewQueryContext(c, db.db, model...) | 532 | return orm.NewQueryContext(c, db.db, model...) |
523 | } | 533 | } |
524 | 534 | ||
525 | -// Select selects the model by primary key. | ||
526 | -func (db *baseDB) Select(model interface{}) error { | ||
527 | - return orm.Select(db.db, model) | ||
528 | -} | ||
529 | - | ||
530 | -// Insert inserts the model updating primary keys if they are empty. | ||
531 | -func (db *baseDB) Insert(model ...interface{}) error { | ||
532 | - return orm.Insert(db.db, model...) | ||
533 | -} | ||
534 | - | ||
535 | -// Update updates the model by primary key. | ||
536 | -func (db *baseDB) Update(model interface{}) error { | ||
537 | - return orm.Update(db.db, model) | ||
538 | -} | ||
539 | - | ||
540 | -// Delete deletes the model by primary key. | ||
541 | -func (db *baseDB) Delete(model interface{}) error { | ||
542 | - return orm.Delete(db.db, model) | ||
543 | -} | ||
544 | - | ||
545 | -// Delete forces delete of the model with deleted_at column. | ||
546 | -func (db *baseDB) ForceDelete(model interface{}) error { | ||
547 | - return orm.ForceDelete(db.db, model) | ||
548 | -} | ||
549 | - | ||
550 | -// CreateTable creates table for the model. It recognizes following field tags: | ||
551 | -// - notnull - sets NOT NULL constraint. | ||
552 | -// - unique - sets UNIQUE constraint. | ||
553 | -// - default:value - sets default value. | ||
554 | -func (db *baseDB) CreateTable(model interface{}, opt *orm.CreateTableOptions) error { | ||
555 | - return orm.CreateTable(db.db, model, opt) | ||
556 | -} | ||
557 | - | ||
558 | -// DropTable drops table for the model. | ||
559 | -func (db *baseDB) DropTable(model interface{}, opt *orm.DropTableOptions) error { | ||
560 | - return orm.DropTable(db.db, model, opt) | ||
561 | -} | ||
562 | - | ||
563 | -func (db *baseDB) CreateComposite(model interface{}, opt *orm.CreateCompositeOptions) error { | ||
564 | - return orm.CreateComposite(db.db, model, opt) | ||
565 | -} | ||
566 | - | ||
567 | -func (db *baseDB) DropComposite(model interface{}, opt *orm.DropCompositeOptions) error { | ||
568 | - return orm.DropComposite(db.db, model, opt) | ||
569 | -} | ||
570 | - | ||
571 | func (db *baseDB) Formatter() orm.QueryFormatter { | 535 | func (db *baseDB) Formatter() orm.QueryFormatter { |
572 | return db.fmter | 536 | return db.fmter |
573 | } | 537 | } |
@@ -597,7 +561,7 @@ func (db *baseDB) simpleQuery( | @@ -597,7 +561,7 @@ func (db *baseDB) simpleQuery( | ||
597 | } | 561 | } |
598 | 562 | ||
599 | var res *result | 563 | var res *result |
600 | - if err := cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.BufReader) error { | 564 | + if err := cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error { |
601 | var err error | 565 | var err error |
602 | res, err = readSimpleQuery(rd) | 566 | res, err = readSimpleQuery(rd) |
603 | return err | 567 | return err |
@@ -616,7 +580,7 @@ func (db *baseDB) simpleQueryData( | @@ -616,7 +580,7 @@ func (db *baseDB) simpleQueryData( | ||
616 | } | 580 | } |
617 | 581 | ||
618 | var res *result | 582 | var res *result |
619 | - if err := cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.BufReader) error { | 583 | + if err := cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error { |
620 | var err error | 584 | var err error |
621 | res, err = readSimpleQueryData(c, rd, model) | 585 | res, err = readSimpleQueryData(c, rd, model) |
622 | return err | 586 | return err |
@@ -631,12 +595,12 @@ func (db *baseDB) simpleQueryData( | @@ -631,12 +595,12 @@ func (db *baseDB) simpleQueryData( | ||
631 | // executions. Multiple queries or executions may be run concurrently | 595 | // executions. Multiple queries or executions may be run concurrently |
632 | // from the returned statement. | 596 | // from the returned statement. |
633 | func (db *baseDB) Prepare(q string) (*Stmt, error) { | 597 | func (db *baseDB) Prepare(q string) (*Stmt, error) { |
634 | - return prepareStmt(db.withPool(pool.NewSingleConnPool(db.pool)), q) | 598 | + return prepareStmt(db.withPool(pool.NewStickyConnPool(db.pool)), q) |
635 | } | 599 | } |
636 | 600 | ||
637 | func (db *baseDB) prepare( | 601 | func (db *baseDB) prepare( |
638 | c context.Context, cn *pool.Conn, q string, | 602 | c context.Context, cn *pool.Conn, q string, |
639 | -) (string, [][]byte, error) { | 603 | +) (string, []types.ColumnInfo, error) { |
640 | name := cn.NextID() | 604 | name := cn.NextID() |
641 | err := cn.WithWriter(c, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error { | 605 | err := cn.WithWriter(c, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error { |
642 | writeParseDescribeSyncMsg(wb, name, q) | 606 | writeParseDescribeSyncMsg(wb, name, q) |
@@ -646,8 +610,8 @@ func (db *baseDB) prepare( | @@ -646,8 +610,8 @@ func (db *baseDB) prepare( | ||
646 | return "", nil, err | 610 | return "", nil, err |
647 | } | 611 | } |
648 | 612 | ||
649 | - var columns [][]byte | ||
650 | - err = cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.BufReader) error { | 613 | + var columns []types.ColumnInfo |
614 | + err = cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error { | ||
651 | columns, err = readParseDescribeSync(rd) | 615 | columns, err = readParseDescribeSync(rd) |
652 | return err | 616 | return err |
653 | }) | 617 | }) |
@@ -75,12 +75,12 @@ func (db *DB) WithParam(param string, value interface{}) *DB { | @@ -75,12 +75,12 @@ func (db *DB) WithParam(param string, value interface{}) *DB { | ||
75 | } | 75 | } |
76 | 76 | ||
77 | // Listen listens for notifications sent with NOTIFY command. | 77 | // Listen listens for notifications sent with NOTIFY command. |
78 | -func (db *DB) Listen(channels ...string) *Listener { | 78 | +func (db *DB) Listen(ctx context.Context, channels ...string) *Listener { |
79 | ln := &Listener{ | 79 | ln := &Listener{ |
80 | db: db, | 80 | db: db, |
81 | } | 81 | } |
82 | ln.init() | 82 | ln.init() |
83 | - _ = ln.Listen(channels...) | 83 | + _ = ln.Listen(ctx, channels...) |
84 | return ln | 84 | return ln |
85 | } | 85 | } |
86 | 86 | ||
@@ -105,7 +105,7 @@ var _ orm.DB = (*Conn)(nil) | @@ -105,7 +105,7 @@ var _ orm.DB = (*Conn)(nil) | ||
105 | // Every Conn must be returned to the database pool after use by | 105 | // Every Conn must be returned to the database pool after use by |
106 | // calling Conn.Close. | 106 | // calling Conn.Close. |
107 | func (db *DB) Conn() *Conn { | 107 | func (db *DB) Conn() *Conn { |
108 | - return newConn(db.ctx, db.baseDB.withPool(pool.NewSingleConnPool(db.pool))) | 108 | + return newConn(db.ctx, db.baseDB.withPool(pool.NewStickyConnPool(db.pool))) |
109 | } | 109 | } |
110 | 110 | ||
111 | func newConn(ctx context.Context, baseDB *baseDB) *Conn { | 111 | func newConn(ctx context.Context, baseDB *baseDB) *Conn { |
1 | package pg | 1 | package pg |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | - "io" | ||
5 | "net" | 4 | "net" |
6 | 5 | ||
7 | "github.com/go-pg/pg/v10/internal" | 6 | "github.com/go-pg/pg/v10/internal" |
@@ -22,10 +21,10 @@ var ErrMultiRows = internal.ErrMultiRows | @@ -22,10 +21,10 @@ var ErrMultiRows = internal.ErrMultiRows | ||
22 | type Error interface { | 21 | type Error interface { |
23 | error | 22 | error |
24 | 23 | ||
25 | - // Field returns a string value associated with an error code. | 24 | + // Field returns a string value associated with an error field. |
26 | // | 25 | // |
27 | // https://www.postgresql.org/docs/10/static/protocol-error-fields.html | 26 | // https://www.postgresql.org/docs/10/static/protocol-error-fields.html |
28 | - Field(byte) string | 27 | + Field(field byte) string |
29 | 28 | ||
30 | // IntegrityViolation reports whether an error is a part of | 29 | // IntegrityViolation reports whether an error is a part of |
31 | // Integrity Constraint Violation class of errors. | 30 | // Integrity Constraint Violation class of errors. |
@@ -43,21 +42,19 @@ func isBadConn(err error, allowTimeout bool) bool { | @@ -43,21 +42,19 @@ func isBadConn(err error, allowTimeout bool) bool { | ||
43 | if _, ok := err.(internal.Error); ok { | 42 | if _, ok := err.(internal.Error); ok { |
44 | return false | 43 | return false |
45 | } | 44 | } |
46 | - if pgErr, ok := err.(Error); ok && pgErr.Field('S') != "FATAL" { | ||
47 | - return false | 45 | + if pgErr, ok := err.(Error); ok { |
46 | + return pgErr.Field('S') == "FATAL" | ||
48 | } | 47 | } |
49 | if allowTimeout { | 48 | if allowTimeout { |
50 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() { | 49 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() { |
51 | - return false | 50 | + return !netErr.Temporary() |
52 | } | 51 | } |
53 | } | 52 | } |
54 | return true | 53 | return true |
55 | } | 54 | } |
56 | 55 | ||
57 | -func isNetworkError(err error) bool { | ||
58 | - if err == io.EOF { | ||
59 | - return true | ||
60 | - } | ||
61 | - _, ok := err.(net.Error) | ||
62 | - return ok | 56 | +//------------------------------------------------------------------------------ |
57 | + | ||
58 | +type timeoutError interface { | ||
59 | + Timeout() bool | ||
63 | } | 60 | } |
@@ -3,25 +3,24 @@ module github.com/go-pg/pg/v10 | @@ -3,25 +3,24 @@ module github.com/go-pg/pg/v10 | ||
3 | go 1.11 | 3 | go 1.11 |
4 | 4 | ||
5 | require ( | 5 | require ( |
6 | - github.com/go-pg/pg/v9 v9.1.6 // indirect | ||
7 | - github.com/go-pg/urlstruct v0.4.0 | ||
8 | - github.com/go-pg/zerochecker v0.1.1 | ||
9 | - github.com/golang/protobuf v1.4.2 // indirect | 6 | + github.com/go-pg/zerochecker v0.2.0 |
7 | + github.com/golang/protobuf v1.4.3 // indirect | ||
10 | github.com/jinzhu/inflection v1.0.0 | 8 | github.com/jinzhu/inflection v1.0.0 |
11 | - github.com/onsi/ginkgo v1.10.1 | ||
12 | - github.com/onsi/gomega v1.7.0 | ||
13 | - github.com/segmentio/encoding v0.1.13 | 9 | + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect |
10 | + github.com/onsi/ginkgo v1.14.2 | ||
11 | + github.com/onsi/gomega v1.10.3 | ||
12 | + github.com/stretchr/testify v1.6.1 | ||
14 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc | 13 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc |
15 | github.com/vmihailenco/bufpool v0.1.11 | 14 | github.com/vmihailenco/bufpool v0.1.11 |
16 | - github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1 | ||
17 | - github.com/vmihailenco/tagparser v0.1.1 | ||
18 | - go.opentelemetry.io/otel v0.6.0 | ||
19 | - golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect | ||
20 | - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect | ||
21 | - golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect | ||
22 | - google.golang.org/appengine v1.6.6 // indirect | ||
23 | - google.golang.org/grpc v1.29.1 | ||
24 | - google.golang.org/protobuf v1.24.0 // indirect | ||
25 | - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 | 15 | + github.com/vmihailenco/msgpack/v4 v4.3.11 // indirect |
16 | + github.com/vmihailenco/msgpack/v5 v5.0.0 | ||
17 | + github.com/vmihailenco/tagparser v0.1.2 | ||
18 | + go.opentelemetry.io/otel v0.14.0 | ||
19 | + golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 // indirect | ||
20 | + golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect | ||
21 | + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect | ||
22 | + google.golang.org/appengine v1.6.7 // indirect | ||
23 | + google.golang.org/protobuf v1.25.0 // indirect | ||
24 | + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f | ||
26 | mellium.im/sasl v0.2.1 | 25 | mellium.im/sasl v0.2.1 |
27 | ) | 26 | ) |
@@ -8,15 +8,17 @@ import ( | @@ -8,15 +8,17 @@ import ( | ||
8 | "github.com/go-pg/pg/v10/orm" | 8 | "github.com/go-pg/pg/v10/orm" |
9 | ) | 9 | ) |
10 | 10 | ||
11 | -type BeforeScanHook = orm.BeforeScanHook | ||
12 | -type AfterScanHook = orm.AfterScanHook | ||
13 | -type AfterSelectHook = orm.AfterSelectHook | ||
14 | -type BeforeInsertHook = orm.BeforeInsertHook | ||
15 | -type AfterInsertHook = orm.AfterInsertHook | ||
16 | -type BeforeUpdateHook = orm.BeforeUpdateHook | ||
17 | -type AfterUpdateHook = orm.AfterUpdateHook | ||
18 | -type BeforeDeleteHook = orm.BeforeDeleteHook | ||
19 | -type AfterDeleteHook = orm.AfterDeleteHook | 11 | +type ( |
12 | + BeforeScanHook = orm.BeforeScanHook | ||
13 | + AfterScanHook = orm.AfterScanHook | ||
14 | + AfterSelectHook = orm.AfterSelectHook | ||
15 | + BeforeInsertHook = orm.BeforeInsertHook | ||
16 | + AfterInsertHook = orm.AfterInsertHook | ||
17 | + BeforeUpdateHook = orm.BeforeUpdateHook | ||
18 | + AfterUpdateHook = orm.AfterUpdateHook | ||
19 | + BeforeDeleteHook = orm.BeforeDeleteHook | ||
20 | + AfterDeleteHook = orm.AfterDeleteHook | ||
21 | +) | ||
20 | 22 | ||
21 | //------------------------------------------------------------------------------ | 23 | //------------------------------------------------------------------------------ |
22 | 24 | ||
@@ -94,11 +96,14 @@ func (db *baseDB) beforeQuery( | @@ -94,11 +96,14 @@ func (db *baseDB) beforeQuery( | ||
94 | fmtedQuery: fmtedQuery, | 96 | fmtedQuery: fmtedQuery, |
95 | } | 97 | } |
96 | 98 | ||
97 | - for _, hook := range db.queryHooks { | 99 | + for i, hook := range db.queryHooks { |
98 | var err error | 100 | var err error |
99 | ctx, err = hook.BeforeQuery(ctx, event) | 101 | ctx, err = hook.BeforeQuery(ctx, event) |
100 | if err != nil { | 102 | if err != nil { |
101 | - return nil, nil, err | 103 | + if err := db.afterQueryFromIndex(ctx, event, i); err != nil { |
104 | + return ctx, nil, err | ||
105 | + } | ||
106 | + return ctx, nil, err | ||
102 | } | 107 | } |
103 | } | 108 | } |
104 | 109 | ||
@@ -117,14 +122,15 @@ func (db *baseDB) afterQuery( | @@ -117,14 +122,15 @@ func (db *baseDB) afterQuery( | ||
117 | 122 | ||
118 | event.Err = err | 123 | event.Err = err |
119 | event.Result = res | 124 | event.Result = res |
125 | + return db.afterQueryFromIndex(ctx, event, len(db.queryHooks)-1) | ||
126 | +} | ||
120 | 127 | ||
121 | - for _, hook := range db.queryHooks { | ||
122 | - err := hook.AfterQuery(ctx, event) | ||
123 | - if err != nil { | 128 | +func (db *baseDB) afterQueryFromIndex(ctx context.Context, event *QueryEvent, hookIndex int) error { |
129 | + for ; hookIndex >= 0; hookIndex-- { | ||
130 | + if err := db.queryHooks[hookIndex].AfterQuery(ctx, event); err != nil { | ||
124 | return err | 131 | return err |
125 | } | 132 | } |
126 | } | 133 | } |
127 | - | ||
128 | return nil | 134 | return nil |
129 | } | 135 | } |
130 | 136 |
@@ -4,8 +4,10 @@ import ( | @@ -4,8 +4,10 @@ import ( | ||
4 | "fmt" | 4 | "fmt" |
5 | ) | 5 | ) |
6 | 6 | ||
7 | -var ErrNoRows = Errorf("pg: no rows in result set") | ||
8 | -var ErrMultiRows = Errorf("pg: multiple rows in result set") | 7 | +var ( |
8 | + ErrNoRows = Errorf("pg: no rows in result set") | ||
9 | + ErrMultiRows = Errorf("pg: multiple rows in result set") | ||
10 | +) | ||
9 | 11 | ||
10 | type Error struct { | 12 | type Error struct { |
11 | s string | 13 | s string |
@@ -8,20 +8,20 @@ import ( | @@ -8,20 +8,20 @@ import ( | ||
8 | "time" | 8 | "time" |
9 | ) | 9 | ) |
10 | 10 | ||
11 | -// Retry backoff with jitter sleep to prevent overloaded conditions during intervals | ||
12 | -// https://www.awsarchitectureblog.com/2015/03/backoff.html | ||
13 | func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration { | 11 | func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration { |
14 | if retry < 0 { | 12 | if retry < 0 { |
15 | - retry = 0 | 13 | + panic("not reached") |
16 | } | 14 | } |
17 | - | ||
18 | - backoff := minBackoff << uint(retry) | ||
19 | - if backoff > maxBackoff || backoff < minBackoff { | ||
20 | - backoff = maxBackoff | 15 | + if minBackoff == 0 { |
16 | + return 0 | ||
21 | } | 17 | } |
22 | 18 | ||
23 | - if backoff == 0 { | ||
24 | - return 0 | 19 | + d := minBackoff << uint(retry) |
20 | + d = minBackoff + time.Duration(rand.Int63n(int64(d))) | ||
21 | + | ||
22 | + if d > maxBackoff || d < minBackoff { | ||
23 | + d = maxBackoff | ||
25 | } | 24 | } |
26 | - return time.Duration(rand.Int63n(int64(backoff))) | 25 | + |
26 | + return d | ||
27 | } | 27 | } |
1 | package internal | 1 | package internal |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | + "context" | ||
5 | + "fmt" | ||
4 | "log" | 6 | "log" |
5 | "os" | 7 | "os" |
6 | ) | 8 | ) |
7 | 9 | ||
8 | -var Logger = log.New(os.Stderr, "pg: ", log.LstdFlags|log.Lshortfile) | 10 | +var Warn = log.New(os.Stderr, "WARN: pg: ", log.LstdFlags) |
11 | + | ||
12 | +var Deprecated = log.New(os.Stderr, "DEPRECATED: pg: ", log.LstdFlags) | ||
13 | + | ||
14 | +type Logging interface { | ||
15 | + Printf(ctx context.Context, format string, v ...interface{}) | ||
16 | +} | ||
17 | + | ||
18 | +type logger struct { | ||
19 | + log *log.Logger | ||
20 | +} | ||
21 | + | ||
22 | +func (l *logger) Printf(ctx context.Context, format string, v ...interface{}) { | ||
23 | + _ = l.log.Output(2, fmt.Sprintf(format, v...)) | ||
24 | +} | ||
25 | + | ||
26 | +var Logger Logging = &logger{ | ||
27 | + log: log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile), | ||
28 | +} |
@@ -8,16 +8,15 @@ import ( | @@ -8,16 +8,15 @@ import ( | ||
8 | "time" | 8 | "time" |
9 | 9 | ||
10 | "github.com/go-pg/pg/v10/internal" | 10 | "github.com/go-pg/pg/v10/internal" |
11 | - "go.opentelemetry.io/otel/api/kv" | ||
12 | - "go.opentelemetry.io/otel/api/trace" | 11 | + "go.opentelemetry.io/otel/label" |
12 | + "go.opentelemetry.io/otel/trace" | ||
13 | ) | 13 | ) |
14 | 14 | ||
15 | var noDeadline = time.Time{} | 15 | var noDeadline = time.Time{} |
16 | 16 | ||
17 | type Conn struct { | 17 | type Conn struct { |
18 | netConn net.Conn | 18 | netConn net.Conn |
19 | - | ||
20 | - rd *BufReader | 19 | + rd *ReaderContext |
21 | 20 | ||
22 | ProcessID int32 | 21 | ProcessID int32 |
23 | SecretKey int32 | 22 | SecretKey int32 |
@@ -31,8 +30,6 @@ type Conn struct { | @@ -31,8 +30,6 @@ type Conn struct { | ||
31 | 30 | ||
32 | func NewConn(netConn net.Conn) *Conn { | 31 | func NewConn(netConn net.Conn) *Conn { |
33 | cn := &Conn{ | 32 | cn := &Conn{ |
34 | - rd: NewBufReader(netConn), | ||
35 | - | ||
36 | createdAt: time.Now(), | 33 | createdAt: time.Now(), |
37 | } | 34 | } |
38 | cn.SetNetConn(netConn) | 35 | cn.SetNetConn(netConn) |
@@ -55,7 +52,17 @@ func (cn *Conn) RemoteAddr() net.Addr { | @@ -55,7 +52,17 @@ func (cn *Conn) RemoteAddr() net.Addr { | ||
55 | 52 | ||
56 | func (cn *Conn) SetNetConn(netConn net.Conn) { | 53 | func (cn *Conn) SetNetConn(netConn net.Conn) { |
57 | cn.netConn = netConn | 54 | cn.netConn = netConn |
55 | + if cn.rd != nil { | ||
58 | cn.rd.Reset(netConn) | 56 | cn.rd.Reset(netConn) |
57 | + } | ||
58 | +} | ||
59 | + | ||
60 | +func (cn *Conn) LockReader() { | ||
61 | + if cn.rd != nil { | ||
62 | + panic("not reached") | ||
63 | + } | ||
64 | + cn.rd = NewReaderContext() | ||
65 | + cn.rd.Reset(cn.netConn) | ||
59 | } | 66 | } |
60 | 67 | ||
61 | func (cn *Conn) NetConn() net.Conn { | 68 | func (cn *Conn) NetConn() net.Conn { |
@@ -68,30 +75,44 @@ func (cn *Conn) NextID() string { | @@ -68,30 +75,44 @@ func (cn *Conn) NextID() string { | ||
68 | } | 75 | } |
69 | 76 | ||
70 | func (cn *Conn) WithReader( | 77 | func (cn *Conn) WithReader( |
71 | - ctx context.Context, timeout time.Duration, fn func(rd *BufReader) error, | 78 | + ctx context.Context, timeout time.Duration, fn func(rd *ReaderContext) error, |
72 | ) error { | 79 | ) error { |
73 | - return internal.WithSpan(ctx, "with_reader", func(ctx context.Context, span trace.Span) error { | ||
74 | - err := cn.netConn.SetReadDeadline(cn.deadline(ctx, timeout)) | ||
75 | - if err != nil { | 80 | + return internal.WithSpan(ctx, "pg.with_reader", func(ctx context.Context, span trace.Span) error { |
81 | + if err := cn.netConn.SetReadDeadline(cn.deadline(ctx, timeout)); err != nil { | ||
82 | + span.RecordError(err) | ||
76 | return err | 83 | return err |
77 | } | 84 | } |
78 | 85 | ||
79 | - cn.rd.bytesRead = 0 | ||
80 | - err = fn(cn.rd) | ||
81 | - span.SetAttributes(kv.Int64("net.read_bytes", cn.rd.bytesRead)) | 86 | + rd := cn.rd |
87 | + if rd == nil { | ||
88 | + rd = GetReaderContext() | ||
89 | + defer PutReaderContext(rd) | ||
90 | + | ||
91 | + rd.Reset(cn.netConn) | ||
92 | + } | ||
82 | 93 | ||
94 | + rd.bytesRead = 0 | ||
95 | + | ||
96 | + if err := fn(rd); err != nil { | ||
97 | + span.RecordError(err) | ||
83 | return err | 98 | return err |
99 | + } | ||
100 | + | ||
101 | + span.SetAttributes(label.Int64("net.read_bytes", rd.bytesRead)) | ||
102 | + | ||
103 | + return nil | ||
84 | }) | 104 | }) |
85 | } | 105 | } |
86 | 106 | ||
87 | func (cn *Conn) WithWriter( | 107 | func (cn *Conn) WithWriter( |
88 | ctx context.Context, timeout time.Duration, fn func(wb *WriteBuffer) error, | 108 | ctx context.Context, timeout time.Duration, fn func(wb *WriteBuffer) error, |
89 | ) error { | 109 | ) error { |
90 | - return internal.WithSpan(ctx, "with_writer", func(ctx context.Context, span trace.Span) error { | 110 | + return internal.WithSpan(ctx, "pg.with_writer", func(ctx context.Context, span trace.Span) error { |
91 | wb := GetWriteBuffer() | 111 | wb := GetWriteBuffer() |
92 | defer PutWriteBuffer(wb) | 112 | defer PutWriteBuffer(wb) |
93 | 113 | ||
94 | if err := fn(wb); err != nil { | 114 | if err := fn(wb); err != nil { |
115 | + span.RecordError(err) | ||
95 | return err | 116 | return err |
96 | } | 117 | } |
97 | 118 | ||
@@ -100,7 +121,7 @@ func (cn *Conn) WithWriter( | @@ -100,7 +121,7 @@ func (cn *Conn) WithWriter( | ||
100 | } | 121 | } |
101 | 122 | ||
102 | func (cn *Conn) WriteBuffer(ctx context.Context, timeout time.Duration, wb *WriteBuffer) error { | 123 | func (cn *Conn) WriteBuffer(ctx context.Context, timeout time.Duration, wb *WriteBuffer) error { |
103 | - return internal.WithSpan(ctx, "with_writer", func(ctx context.Context, span trace.Span) error { | 124 | + return internal.WithSpan(ctx, "pg.with_writer", func(ctx context.Context, span trace.Span) error { |
104 | return cn.writeBuffer(ctx, span, timeout, wb) | 125 | return cn.writeBuffer(ctx, span, timeout, wb) |
105 | }) | 126 | }) |
106 | } | 127 | } |
@@ -111,14 +132,19 @@ func (cn *Conn) writeBuffer( | @@ -111,14 +132,19 @@ func (cn *Conn) writeBuffer( | ||
111 | timeout time.Duration, | 132 | timeout time.Duration, |
112 | wb *WriteBuffer, | 133 | wb *WriteBuffer, |
113 | ) error { | 134 | ) error { |
114 | - err := cn.netConn.SetWriteDeadline(cn.deadline(ctx, timeout)) | ||
115 | - if err != nil { | 135 | + if err := cn.netConn.SetWriteDeadline(cn.deadline(ctx, timeout)); err != nil { |
136 | + span.RecordError(err) | ||
116 | return err | 137 | return err |
117 | } | 138 | } |
118 | 139 | ||
119 | - span.SetAttributes(kv.Int("net.wrote_bytes", len(wb.Bytes))) | ||
120 | - _, err = cn.netConn.Write(wb.Bytes) | 140 | + span.SetAttributes(label.Int("net.wrote_bytes", len(wb.Bytes))) |
141 | + | ||
142 | + if _, err := cn.netConn.Write(wb.Bytes); err != nil { | ||
143 | + span.RecordError(err) | ||
121 | return err | 144 | return err |
145 | + } | ||
146 | + | ||
147 | + return nil | ||
122 | } | 148 | } |
123 | 149 | ||
124 | func (cn *Conn) Close() error { | 150 | func (cn *Conn) Close() error { |
@@ -11,8 +11,10 @@ import ( | @@ -11,8 +11,10 @@ import ( | ||
11 | "github.com/go-pg/pg/v10/internal" | 11 | "github.com/go-pg/pg/v10/internal" |
12 | ) | 12 | ) |
13 | 13 | ||
14 | -var ErrClosed = errors.New("pg: database is closed") | ||
15 | -var ErrPoolTimeout = errors.New("pg: connection pool timeout") | 14 | +var ( |
15 | + ErrClosed = errors.New("pg: database is closed") | ||
16 | + ErrPoolTimeout = errors.New("pg: connection pool timeout") | ||
17 | +) | ||
16 | 18 | ||
17 | var timers = sync.Pool{ | 19 | var timers = sync.Pool{ |
18 | New: func() interface{} { | 20 | New: func() interface{} { |
@@ -38,8 +40,8 @@ type Pooler interface { | @@ -38,8 +40,8 @@ type Pooler interface { | ||
38 | CloseConn(*Conn) error | 40 | CloseConn(*Conn) error |
39 | 41 | ||
40 | Get(context.Context) (*Conn, error) | 42 | Get(context.Context) (*Conn, error) |
41 | - Put(*Conn) | ||
42 | - Remove(*Conn, error) | 43 | + Put(context.Context, *Conn) |
44 | + Remove(context.Context, *Conn, error) | ||
43 | 45 | ||
44 | Len() int | 46 | Len() int |
45 | IdleLen() int | 47 | IdleLen() int |
@@ -216,12 +218,12 @@ func (p *ConnPool) getLastDialError() error { | @@ -216,12 +218,12 @@ func (p *ConnPool) getLastDialError() error { | ||
216 | } | 218 | } |
217 | 219 | ||
218 | // Get returns existed connection from the pool or creates a new one. | 220 | // Get returns existed connection from the pool or creates a new one. |
219 | -func (p *ConnPool) Get(c context.Context) (*Conn, error) { | 221 | +func (p *ConnPool) Get(ctx context.Context) (*Conn, error) { |
220 | if p.closed() { | 222 | if p.closed() { |
221 | return nil, ErrClosed | 223 | return nil, ErrClosed |
222 | } | 224 | } |
223 | 225 | ||
224 | - err := p.waitTurn(c) | 226 | + err := p.waitTurn(ctx) |
225 | if err != nil { | 227 | if err != nil { |
226 | return nil, err | 228 | return nil, err |
227 | } | 229 | } |
@@ -246,7 +248,7 @@ func (p *ConnPool) Get(c context.Context) (*Conn, error) { | @@ -246,7 +248,7 @@ func (p *ConnPool) Get(c context.Context) (*Conn, error) { | ||
246 | 248 | ||
247 | atomic.AddUint32(&p.stats.Misses, 1) | 249 | atomic.AddUint32(&p.stats.Misses, 1) |
248 | 250 | ||
249 | - newcn, err := p.newConn(c, true) | 251 | + newcn, err := p.newConn(ctx, true) |
250 | if err != nil { | 252 | if err != nil { |
251 | p.freeTurn() | 253 | p.freeTurn() |
252 | return nil, err | 254 | return nil, err |
@@ -312,15 +314,9 @@ func (p *ConnPool) popIdle() *Conn { | @@ -312,15 +314,9 @@ func (p *ConnPool) popIdle() *Conn { | ||
312 | return cn | 314 | return cn |
313 | } | 315 | } |
314 | 316 | ||
315 | -func (p *ConnPool) Put(cn *Conn) { | ||
316 | - if cn.rd.Buffered() > 0 { | ||
317 | - internal.Logger.Printf("Conn has unread data") | ||
318 | - p.Remove(cn, BadConnError{}) | ||
319 | - return | ||
320 | - } | ||
321 | - | 317 | +func (p *ConnPool) Put(ctx context.Context, cn *Conn) { |
322 | if !cn.pooled { | 318 | if !cn.pooled { |
323 | - p.Remove(cn, nil) | 319 | + p.Remove(ctx, cn, nil) |
324 | return | 320 | return |
325 | } | 321 | } |
326 | 322 | ||
@@ -331,7 +327,7 @@ func (p *ConnPool) Put(cn *Conn) { | @@ -331,7 +327,7 @@ func (p *ConnPool) Put(cn *Conn) { | ||
331 | p.freeTurn() | 327 | p.freeTurn() |
332 | } | 328 | } |
333 | 329 | ||
334 | -func (p *ConnPool) Remove(cn *Conn, reason error) { | 330 | +func (p *ConnPool) Remove(ctx context.Context, cn *Conn, reason error) { |
335 | p.removeConnWithLock(cn) | 331 | p.removeConnWithLock(cn) |
336 | p.freeTurn() | 332 | p.freeTurn() |
337 | _ = p.closeConn(cn) | 333 | _ = p.closeConn(cn) |
@@ -446,7 +442,7 @@ func (p *ConnPool) reaper(frequency time.Duration) { | @@ -446,7 +442,7 @@ func (p *ConnPool) reaper(frequency time.Duration) { | ||
446 | } | 442 | } |
447 | n, err := p.ReapStaleConns() | 443 | n, err := p.ReapStaleConns() |
448 | if err != nil { | 444 | if err != nil { |
449 | - internal.Logger.Printf("ReapStaleConns failed: %s", err) | 445 | + internal.Logger.Printf(context.TODO(), "ReapStaleConns failed: %s", err) |
450 | continue | 446 | continue |
451 | } | 447 | } |
452 | atomic.AddUint32(&p.stats.StaleConns, uint32(n)) | 448 | atomic.AddUint32(&p.stats.StaleConns, uint32(n)) |
-
请 注册 或 登录 后发表评论