Commit ed5f3a6bc2031a5eedf6be32e0a3fe0f3f156813

Authored by YunaiV
1 parent 0eff2ae6

crm:code review 销售漏斗

Showing 13 changed files with 38 additions and 82 deletions
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
... ... @@ -177,7 +177,7 @@ public class CrmBusinessController {
177 177 buildBusinessDetailList(list));
178 178 }
179 179  
180   - private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
  180 + public List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
181 181 if (CollUtil.isEmpty(list)) {
182 182 return Collections.emptyList();
183 183 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsFunnelController.java
1 1 package cn.iocoder.yudao.module.crm.controller.admin.statistics;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
  3 +import cn.hutool.extra.spring.SpringUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7   -import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.business.CrmBusinessController;
9 7 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
10 8 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticBusinessEndStatusRespVO;
11 9 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticFunnelRespVO;
12 10 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
13 11 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
14 12 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
15   -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
16   -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
17   -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
18   -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
19   -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
20   -import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
21 13 import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsFunnelService;
22   -import cn.iocoder.yudao.module.system.api.dept.DeptApi;
23   -import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
24   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
25   -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
26 14 import io.swagger.v3.oas.annotations.Operation;
27 15 import io.swagger.v3.oas.annotations.tags.Tag;
28 16 import jakarta.annotation.Resource;
... ... @@ -33,14 +21,9 @@ import org.springframework.web.bind.annotation.GetMapping;
33 21 import org.springframework.web.bind.annotation.RequestMapping;
34 22 import org.springframework.web.bind.annotation.RestController;
35 23  
36   -import java.util.Collections;
37 24 import java.util.List;
38   -import java.util.Map;
39   -import java.util.stream.Stream;
40 25  
41 26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
42   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
43   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
44 27  
45 28 @Tag(name = "管理后台 - CRM 销售漏斗")
46 29 @RestController
... ... @@ -48,31 +31,20 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
48 31 @Validated
49 32 public class CrmStatisticsFunnelController {
50 33  
  34 + // TODO @puhui999:crmStatisticsFunnelService 改成 funnelService 更好点哈
51 35 @Resource
52 36 private CrmStatisticsFunnelService crmStatisticsFunnelService;
53   - @Resource
54   - private CrmBusinessService businessService;
55   - @Resource
56   - private CrmCustomerService customerService;
57   - @Resource
58   - private CrmBusinessStatusService businessStatusTypeService;
59   - @Resource
60   - private CrmBusinessStatusService businessStatusService;
61   -
62   - @Resource
63   - private AdminUserApi adminUserApi;
64   - @Resource
65   - private DeptApi deptApi;
66 37  
67 38 @GetMapping("/get-funnel-summary")
68   - @Operation(summary = "获取销售漏斗统计数据", description = "用于【销售漏斗】页面")
  39 + @Operation(summary = "获取销售漏斗统计数据", description = "用于【销售漏斗】页面的【销售漏斗分析】")
69 40 @PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
70 41 public CommonResult<CrmStatisticFunnelRespVO> getFunnelSummary(@Valid CrmStatisticsFunnelReqVO reqVO) {
71 42 return success(crmStatisticsFunnelService.getFunnelSummary(reqVO));
72 43 }
73 44  
  45 + // TODO @puhui:这个接口,应该是 getBusinessSummaryByEndStatus?这样更统一哈;
74 46 @GetMapping("/get-business-end-status-summary")
75   - @Operation(summary = "获取商机结束状态统计", description = "用于【销售漏斗】页面")
  47 + @Operation(summary = "获取商机结束状态统计", description = "用于【销售漏斗】页面的【销售漏斗分析】")
76 48 @PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
77 49 public CommonResult<List<CrmStatisticBusinessEndStatusRespVO>> getBusinessEndStatusSummary(@Valid CrmStatisticsFunnelReqVO reqVO) {
78 50 return success(crmStatisticsFunnelService.getBusinessEndStatusSummary(reqVO));
... ... @@ -86,7 +58,7 @@ public class CrmStatisticsFunnelController {
86 58 }
87 59  
88 60 @GetMapping("/get-business-page-by-date")
89   - @Operation(summary = "获得商机分页(按日期)", description = "用于【销售漏斗】页面")
  61 + @Operation(summary = "获得商机分页(按日期)", description = "用于【销售漏斗】页面的【新增商机分析】")
90 62 @PreAuthorize("@ss.hasPermission('crm:business:query')")
91 63 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByDate(@Valid CrmStatisticsFunnelReqVO pageVO) {
92 64 PageResult<CrmBusinessDO> pageResult = crmStatisticsFunnelService.getBusinessPageByDate(pageVO);
... ... @@ -94,37 +66,7 @@ public class CrmStatisticsFunnelController {
94 66 }
95 67  
96 68 private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
97   - if (CollUtil.isEmpty(list)) {
98   - return Collections.emptyList();
99   - }
100   - // 1.1 获取客户列表
101   - Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
102   - convertSet(list, CrmBusinessDO::getCustomerId));
103   - // 1.2 获取创建人、负责人列表
104   - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
105   - contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
106   - Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
107   - // 1.3 获得商机状态组
108   - Map<Long, CrmBusinessStatusTypeDO> statusTypeMap = businessStatusTypeService.getBusinessStatusTypeMap(
109   - convertSet(list, CrmBusinessDO::getStatusTypeId));
110   - Map<Long, CrmBusinessStatusDO> statusMap = businessStatusService.getBusinessStatusMap(
111   - convertSet(list, CrmBusinessDO::getStatusId));
112   - // 2. 拼接数据
113   - return BeanUtils.toBean(list, CrmBusinessRespVO.class, businessVO -> {
114   - // 2.1 设置客户名称
115   - MapUtils.findAndThen(customerMap, businessVO.getCustomerId(), customer -> businessVO.setCustomerName(customer.getName()));
116   - // 2.2 设置创建人、负责人名称
117   - MapUtils.findAndThen(userMap, NumberUtils.parseLong(businessVO.getCreator()),
118   - user -> businessVO.setCreatorName(user.getNickname()));
119   - MapUtils.findAndThen(userMap, businessVO.getOwnerUserId(), user -> {
120   - businessVO.setOwnerUserName(user.getNickname());
121   - MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> businessVO.setOwnerUserDeptName(dept.getName()));
122   - });
123   - // 2.3 设置商机状态
124   - MapUtils.findAndThen(statusTypeMap, businessVO.getStatusTypeId(), statusType -> businessVO.setStatusTypeName(statusType.getName()));
125   - MapUtils.findAndThen(statusMap, businessVO.getStatusId(), status -> businessVO.setStatusName(
126   - businessService.getBusinessStatusName(businessVO.getEndStatus(), status)));
127   - });
  69 + return SpringUtil.getBean(CrmBusinessController.class).buildBusinessDetailList(list);
128 70 }
129 71  
130 72 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticBusinessEndStatusRespVO.java
... ... @@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
7 7  
8 8 import java.math.BigDecimal;
9 9  
  10 +// TODO @puhui999:改成 CrmStatisticsBusinessSummaryByEndStatusRespVO,按照结束状态
10 11 @Schema(description = "管理后台 - CRM 商机结束状态统计 Response VO")
11 12 @NoArgsConstructor
12 13 @AllArgsConstructor
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticFunnelRespVO.java
... ... @@ -5,6 +5,7 @@ import lombok.AllArgsConstructor;
5 5 import lombok.Data;
6 6 import lombok.NoArgsConstructor;
7 7  
  8 +// TODO @puhui999:改成 CrmStatisticFunnelSummaryRespVO 更有统计的味道
8 9 @Schema(description = "管理后台 - CRM 销售漏斗 Response VO")
9 10 @NoArgsConstructor
10 11 @AllArgsConstructor
... ... @@ -17,6 +18,7 @@ public class CrmStatisticFunnelRespVO {
17 18 @Schema(description = "商机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
18 19 private Long businessCount;
19 20  
  21 + // TODO @puhui999:这个改成 businessWinCount 可能会更合适点哈;
20 22 @Schema(description = "赢单数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
21 23 private Long winCount;
22 24  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsBusinessRespVO.java
... ... @@ -3,14 +3,12 @@ package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
3 3 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 4 import com.alibaba.excel.annotation.ExcelProperty;
5 5 import io.swagger.v3.oas.annotations.media.Schema;
6   -import lombok.AllArgsConstructor;
7 6 import lombok.Data;
8   -import lombok.NoArgsConstructor;
9 7  
10 8 import java.math.BigDecimal;
11 9 import java.time.LocalDateTime;
12   -import java.util.List;
13 10  
  11 +// TODO @puhui999:是不是可以删除哈?
14 12 @Schema(description = "管理后台 - CRM 商机 Response VO")
15 13 @Data
16 14 @ExcelIgnoreUnannotated
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsBusinessSummaryByDateRespVO.java
... ... @@ -15,6 +15,7 @@ public class CrmStatisticsBusinessSummaryByDateRespVO {
15 15 @Schema(description = "新增商机数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
16 16 private Integer businessCreateCount;
17 17  
  18 + // TODO @puhui999:是不是金额哈,不是数量
18 19 @Schema(description = "新增商机金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
19 20 private BigDecimal businessDealCount;
20 21  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsFunnelReqVO.java
... ... @@ -17,6 +17,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
17 17  
18 18 @Schema(description = "管理后台 - CRM 销售漏斗 Request VO")
19 19 @Data
  20 +// TODO @puhui999:不用写 EqualsAndHashCode、ToString,已经全局 lombok 啦
20 21 @EqualsAndHashCode(callSuper = true)
21 22 @ToString(callSuper = true)
22 23 public class CrmStatisticsFunnelReqVO extends PageParam {
... ... @@ -42,6 +43,7 @@ public class CrmStatisticsFunnelReqVO extends PageParam {
42 43 @InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
43 44 private Integer interval;
44 45  
  46 + // TODO @puhui999:这个全部前端传递哈;参考 CrmStatisticsCustomerReqVO
45 47 /**
46 48 * 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间
47 49 * 并作为参数传递给Mapper
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
... ... @@ -73,6 +73,7 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; {
73 73 .betweenIfPresent(CrmBusinessDO::getCreateTime, times));
74 74 }
75 75  
  76 + // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
76 77 default List<CrmBusinessDO> selectListByOwnerUserIdsAndEndStatusNotNull(Collection<Long> ownerUserIds, LocalDateTime[] times) {
77 78 return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
78 79 .in(CrmBusinessDO::getOwnerUserId, ownerUserIds)
... ... @@ -80,6 +81,7 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; {
80 81 .isNotNull(CrmBusinessDO::getEndStatus));
81 82 }
82 83  
  84 + // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
83 85 default List<CrmBusinessDO> selectListByOwnerUserIdsAndDate(Collection<Long> ownerUserIds, LocalDateTime[] times) {
84 86 return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
85 87 .in(CrmBusinessDO::getOwnerUserId, ownerUserIds)
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
... ... @@ -19,11 +19,9 @@ import org.apache.ibatis.annotations.Mapper;
19 19 import org.springframework.lang.Nullable;
20 20 import org.springframework.util.Assert;
21 21  
22   -import javax.management.ObjectName;
23 22 import java.time.LocalDateTime;
24 23 import java.util.Collection;
25 24 import java.util.List;
26   -import java.util.Set;
27 25  
28 26 /**
29 27 * 客户 Mapper
... ... @@ -188,6 +186,7 @@ public interface CrmCustomerMapper extends BaseMapperX&lt;CrmCustomerDO&gt; {
188 186 return selectCount(query);
189 187 }
190 188  
  189 + // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
191 190 default Long selectCountByOwnerUserIds(Collection<Long> ownerUserIds, LocalDateTime[] times){
192 191 return selectCount(new LambdaQueryWrapperX<CrmCustomerDO>()
193 192 .in(CrmCustomerDO::getOwnerUserId, ownerUserIds)
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
... ... @@ -199,11 +199,12 @@ public interface CrmBusinessService {
199 199 *
200 200 * @param ownerUserIds 负责人编号
201 201 * @param times 时间范围
202   - * @param endStatus 商机结束状态
  202 + * @param endStatus 商机结束状态,允许为空
203 203 * @return 商机数
204 204 */
205 205 Long getBusinessCountByOwnerUserIdsAndEndStatus(List<Long> ownerUserIds, LocalDateTime[] times, Integer endStatus);
206 206  
  207 + // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
207 208 /**
208 209 * 获得商机列表【数据统计】
209 210 *
... ... @@ -213,6 +214,7 @@ public interface CrmBusinessService {
213 214 */
214 215 List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndEndStatusNotNull(List<Long> ownerUserIds, LocalDateTime[] times);
215 216  
  217 + // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
216 218 /**
217 219 * 获得商机列表【数据统计】
218 220 *
... ... @@ -223,10 +225,11 @@ public interface CrmBusinessService {
223 225 List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndDate(List<Long> ownerUserIds, LocalDateTime[] times);
224 226  
225 227 /**
226   - * 商机分页【数据统计】
  228 + * 获得商机分页,目前用于【数据统计】
  229 + *
227 230 * @param ownerUserIds 负责人编号
228 231 * @param times 时间范围
229   - * @param pageNo 页码
  232 + * @param pageNo 页码 TODO @puhui999:直接传递 CrmStatisticsFunnelReqVO 吧,虽然有点耦合,但是更清晰一点;
230 233 * @param pageSize 数量
231 234 * @return 商机分页
232 235 */
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
... ... @@ -383,6 +383,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
383 383 return businessMapper.selectCountByOwnerUserIdsAndEndStatus(convertSet(ownerUserIds), times, endStatus);
384 384 }
385 385  
  386 + // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
386 387 @Override
387 388 public List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndEndStatusNotNull(List<Long> ownerUserIds, LocalDateTime[] times) {
388 389 if (CollUtil.isEmpty(ownerUserIds)) {
... ... @@ -391,6 +392,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
391 392 return businessMapper.selectListByOwnerUserIdsAndEndStatusNotNull(convertSet(ownerUserIds), times);
392 393 }
393 394  
  395 + // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
394 396 @Override
395 397 public List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndDate(List<Long> ownerUserIds, LocalDateTime[] times) {
396 398 if (CollUtil.isEmpty(ownerUserIds)) {
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelServiceImpl.java
... ... @@ -51,6 +51,7 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
51 51 @Resource
52 52 private DeptApi deptApi;
53 53  
  54 + // TODO @puhui999:貌似想了下,可能还是得按照;;;
54 55 @Override
55 56 public CrmStatisticFunnelRespVO getFunnelSummary(CrmStatisticsFunnelReqVO reqVO) {
56 57 // 1. 获得用户编号数组
... ... @@ -76,6 +77,7 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
76 77 return Collections.emptyList();
77 78 }
78 79  
  80 + // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
79 81 // 2.1 获得用户负责的商机
80 82 List<CrmBusinessDO> businessList = businessService.getBusinessListByOwnerUserIdsAndEndStatusNotNull(reqVO.getUserIds(), reqVO.getTimes());
81 83 // 2.2 统计各阶段数据
... ... @@ -99,6 +101,7 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
99 101 }
100 102  
101 103 // 2. 按天统计,获取分项统计数据
  104 + // TODO @puhui999:可以这个统计,返回的时候,就把数量、金额一起统计好;
102 105 List<CrmStatisticsBusinessSummaryByDateRespVO> businessCreateCountList = funnelMapper.selectBusinessCreateCountGroupByDate(reqVO);
103 106 List<CrmBusinessDO> businessList = businessService.getBusinessListByOwnerUserIdsAndDate(reqVO.getUserIds(), reqVO.getTimes());
104 107 Map<String, BigDecimal> businessDealCountMap = businessList.stream().collect(Collectors.groupingBy(business ->
... ... @@ -128,7 +131,7 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
128 131 if (CollUtil.isEmpty(pageVO.getUserIds())) {
129 132 return PageResult.empty();
130 133 }
131   -
  134 + // 2. 执行查询
132 135 return businessService.getBusinessPageByDate(pageVO.getUserIds(), pageVO.getTimes(), pageVO.getPageNo(), pageVO.getPageSize());
133 136 }
134 137  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsFunnelMapper.xml
... ... @@ -4,15 +4,16 @@
4 4  
5 5 <select id="selectBusinessCreateCountGroupByDate"
6 6 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO">
7   - SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time, COUNT(*) AS businessCreateCount
  7 + SELECT
  8 + DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
  9 + COUNT(*) AS businessCreateCount
8 10 FROM crm_business
9 11 WHERE deleted = 0
10 12 AND owner_user_id IN
11   - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
12   - #{userId}
13   - </foreach>
14   - AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
15   - #{times[1],javaType=java.time.LocalDateTime}
  13 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  14 + #{userId}
  15 + </foreach>
  16 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
16 17 GROUP BY time
17 18 </select>
18 19  
... ...