怎样使用 Go 写 “递归 + 协程” 代码?

时间:2022-10-08 08:56:28


func (rcvr *ProductOverviewDownloadCateService) executeDownload(ctx context.Context,
req *common.GeneralizedCallForRpcReq,
cateIndexDataRequest product_overview.ProductOverviewCateIndexDataByParentCateIdRequest,
err error,
h *ProductOverviewDownloadCateService,
resp *common.GeneralizedCallForRpcResp,
excelTableData [][]string) (*product_overview.ProductOverviewCateIndexDataResponse, *common.GeneralizedCallForRpcResp, error, bool, [][]string) {

logu.CtxInfo(ctx, "ProductOverviewDownloadCate", "ProductOverviewGetCategoryIndexDataListByParentCateIdService.ProductOverviewGetCategoryIndexDataListByParentCateId,cateIndexDataRequest=%v", convert.ToJSONString(cateIndexDataRequest))
fisrtLevelCateDataIndexResp, err := h.ProductOverviewGetCategoryIndexDataListByParentCateIdService.ProductOverviewGetCategoryIndexDataListByParentCateId(ctx, &cateIndexDataRequest, false)

if err != nil {
logu.CtxError(ctx, error_code.ParamError, "ProductOverviewDownloadCate", "ProductOverviewGetCategoryIndexDataListByParentCateId,cateIndexDataRequest=%v,err=%v", convert.ToJSONString(cateIndexDataRequest), err)
return nil, resp, err, true, excelTableData
}

logu.CtxInfo(ctx, "ProductOverviewDownloadCate,", "ProductOverviewGetCategoryIndexDataListByParentCateIdService.ProductOverviewGetCategoryIndexDataListByParentCateId, fisrtLevelCateDataIndexResp=%v", convert.ToJSONString(fisrtLevelCateDataIndexResp))

if fisrtLevelCateDataIndexResp.Data == nil || len(fisrtLevelCateDataIndexResp.Data.DataResult_) == 0 {
return nil, resp, err, true, excelTableData
}

// 声明一个带“读写锁 + map ” 的组合结构体
cateDataIndexMapSafe := struct {
sync.RWMutex
cateDataIndexMap map[int32]*product_overview.ProductOverviewCateIndexDataResponse
}{cateDataIndexMap: make(map[int32]*product_overview.ProductOverviewCateIndexDataResponse, 0)}

// 一级类目指标数据
cateDataIndexMapSafe.cateDataIndexMap[0] = fisrtLevelCateDataIndexResp // 一级类目指标 parentId=0
// 递归获取当前页所有子类目指标数据
h.parallelGetCateDataIndex(ctx, &cateDataIndexMapSafe, cateIndexDataRequest)

logu.CtxInfo(ctx, "parallelGetCateDataIndex", "递归获取当前页所有子类目指标数据结果 cateDataIndexMap=%v", convert.ToJSONString(cateDataIndexMapSafe))

// 递归组装返回结果
excelTableData = h.buildExcelTableData(ctx, excelTableData, cateDataIndexMapSafe.cateDataIndexMap, config.DefaultParentIdInt)

logu.CtxInfo(ctx, "downloadCateStatsData", "cateIndexDataRequest=%v,excelTableData=%v", convert.ToJSONString(cateIndexDataRequest), convert.ToJSONString(excelTableData))

return fisrtLevelCateDataIndexResp, nil, nil, false, excelTableData
}

// 注意:cateIndexDataRequest 的传值方式,协程递归循环的时候,小心使用指针,入参推荐使用值传递.
func (h *ProductOverviewDownloadCateService) parallelGetCateDataIndex(
ctx context.Context,
cateDataIndexMapSafe *struct {
sync.RWMutex
cateDataIndexMap map[int32]*product_overview.ProductOverviewCateIndexDataResponse
},
cateIndexDataRequest product_overview.ProductOverviewCateIndexDataByParentCateIdRequest) {

// 父节点ID
parentCateId := *cateIndexDataRequest.ParentCateId
var cateDataIndexResp *product_overview.ProductOverviewCateIndexDataResponse
// 在协程操作map的过程中,进行map的读操作,也需要加个锁:
cateDataIndexMapSafe.RLock()
cateDataIndexResp = (cateDataIndexMapSafe.cateDataIndexMap)[parentCateId]
cateDataIndexMapSafe.RUnlock()

logu.CtxInfo(ctx, "ProductOverviewDownloadCate,", "parallelGetCateDataIndex, parentCateId=%v, cateIndexDataRequest=%v,cateDataIndexResp=%v", parentCateId, convert.ToJSONString(cateIndexDataRequest), convert.ToJSONString(cateDataIndexResp))

// 递归结束的判断
if cateDataIndexResp == nil || cateDataIndexResp.Data == nil || len(cateDataIndexResp.Data.DataResult_) == 0 {
return
}

// 开启协程获取
var fns []func()

for _, parentCateDataIndex := range cateDataIndexResp.Data.DataResult_ {
// 临时变量 cateDataStats
cateDataStats := parentCateDataIndex

fns = append(fns, func() {
// 作为下一次递归的父节点ID
currentParentCateId := cateDataStats.CateId
// 这里,要使用值传递
cateIndexDataRequestSub := product_overview.ProductOverviewCateIndexDataByParentCateIdRequest{
DateType: cateIndexDataRequest.DateType,
BeginDate: cateIndexDataRequest.BeginDate,
EndDate: cateIndexDataRequest.EndDate,
// 店铺ID
ShopId: cateIndexDataRequest.ShopId,
// 设置子类目列表的父节点ID
ParentCateId: convert.ToInt32Ptr(currentParentCateId),
SortField: cateIndexDataRequest.SortField,
IsAsc: cateIndexDataRequest.IsAsc,
PageNo: cateIndexDataRequest.PageNo,
PageSize: cateIndexDataRequest.PageSize,
}

logu.CtxInfo(ctx, "ProductOverviewDownloadCate", "获取子级类目指标请求 ProductOverviewGetCategoryIndexDataListByParentCateId,cateIndexDataRequestSub=%v", convert.ToJSONString(cateIndexDataRequestSub))
// 获取子级类目指标
subProductListResp, err := h.ProductOverviewGetCategoryIndexDataListByParentCateIdService.ProductOverviewGetCategoryIndexDataListByParentCateId(ctx, &cateIndexDataRequestSub, false)

if err != nil {
logu.CtxError(ctx, error_code.ProcessError, "ProductOverviewDownloadCate", "开启协程获取 ProductOverviewGetCategoryIndexDataListByParentCateId cateIndexDataRequestSub==%v,err=%v", convert.ToJSONString(cateIndexDataRequestSub), err)
return
}

logu.CtxInfo(ctx, "ProductOverviewDownloadCate", "获取子级类目指标返回 ProductOverviewGetCategoryIndexDataListByParentCateId,subProductListResp=%v", convert.ToJSONString(subProductListResp))

// currentParentCateId 指标值存储到 cateDataIndexMap
cateDataIndexMapSafe.Lock()
(cateDataIndexMapSafe.cateDataIndexMap)[currentParentCateId] = subProductListResp
cateDataIndexMapSafe.Unlock()

// 递归获取 subProductListResp
h.parallelGetCateDataIndex(ctx, cateDataIndexMapSafe, cateIndexDataRequestSub)
})
}

// go 协程并行执行
stream.Parallel(fns...)

}

/*
"一级品类",
"二级品类",
"三级品类",
"四级品类",
"品类名称",
"曝光次数",
"点击次数",
"成交订单数",
"成交金额",
"成交件数",
"件单价",
"直播间成交金额",
"短视频成交金额",
"退款金额",
"曝光点击率",
"点击成交转化率",
"千次曝光成交金额",
"成交人数",
"成交新客数",
*/
func (h *ProductOverviewDownloadCateService) buildExcelTableData(ctx context.Context, excelTableData [][]string, cateDataIndexMap map[int32]*product_overview.ProductOverviewCateIndexDataResponse, parentId int32) [][]string {
logu.CtxInfo(ctx, "ProductOverviewDownloadCate", "buildExcelTableData parentId=%v,excelTableData=%v", parentId, convert.ToJSONString(excelTableData))
// 如果 cateDataIndexMap 中不存在 parentId 值,直接返回
if _, exist := cateDataIndexMap[parentId]; !exist {
return excelTableData
}

// 取出 parentId 对应的列表值
cateDataIndexResp := cateDataIndexMap[parentId]
// 递归结束的判断
if cateDataIndexResp.Data == nil || len(cateDataIndexResp.Data.DataResult_) == 0 {
return excelTableData
}

for _, cateData := range cateDataIndexResp.Data.DataResult_ {
// 组装 Excel 一行数据
excelTableData = append(excelTableData, []string{
cateData.CateNameLevel1, //一级品类
cateData.CateNameLevel2, //二级品类
cateData.CateNameLevel3, //三级品类
cateData.CateNameLevel4, //四级品类
cateData.CateName, // 品类名称
download_util.GetElementStrByIndexInfo(cateData.ProductShowCnt), //"曝光次数",
download_util.GetElementStrByIndexInfo(cateData.ProductClickCnt), //"点击次数",
download_util.GetElementStrByIndexInfo(cateData.PayCnt), //"成交订单数",
download_util.GetElementStrByIndexInfo(cateData.PayAmt), //"成交金额",
download_util.GetElementStrByIndexInfo(cateData.PayComboCnt), //"成交件数",
download_util.GetElementStrByIndexInfo(cateData.AvgPrice), //"件单价",
download_util.GetElementStrByIndexInfo(cateData.LivePayAmt), //"直播间成交金额",
download_util.GetElementStrByIndexInfo(cateData.VideoPayAmt), //"短视频成交金额",
download_util.GetElementStrByIndexInfo(cateData.RefundAmt), //"退款金额",
download_util.GetElementStrByIndexInfo(cateData.ClickShowRatio), //"曝光点击率",
download_util.GetElementStrByIndexInfo(cateData.PayClickRatio), //"点击成交转化率",
download_util.GetElementStrByIndexInfo(cateData.Gpm), //"千次曝光成交金额",
download_util.GetElementStrByIndexInfo(cateData.PayUcnt), //"成交人数",
//download_util.GetElementStrByIndexInfo(cateData.PayUcntNew), //"成交新客数",
})

// 递归子类目ID指标
excelTableData = h.buildExcelTableData(ctx, excelTableData, cateDataIndexMap, cateData.CateId)
}

return excelTableData
}