From 244bae35fc196cbe1341439ea3d2b098456b486d Mon Sep 17 00:00:00 2001 From: chenwenjian Date: Sat, 20 Apr 2024 16:55:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(REQ-2106):=20=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/dto/JoinedWorkspaceOuJob.java | 11 ++ .../api/enums/MaterialTargetUserTypeEnum.java | 142 +++++++++++++++--- .../request/ListMaterialByBannerCodeReq.java | 11 +- .../service/impl/MaterialServiceImpl.java | 40 +++-- 4 files changed, 166 insertions(+), 38 deletions(-) diff --git a/banner/banner-api/src/main/java/cn/axzo/nanopart/api/dto/JoinedWorkspaceOuJob.java b/banner/banner-api/src/main/java/cn/axzo/nanopart/api/dto/JoinedWorkspaceOuJob.java index 69b2b1c4..e5677c0b 100644 --- a/banner/banner-api/src/main/java/cn/axzo/nanopart/api/dto/JoinedWorkspaceOuJob.java +++ b/banner/banner-api/src/main/java/cn/axzo/nanopart/api/dto/JoinedWorkspaceOuJob.java @@ -16,6 +16,7 @@ import java.util.Map; @NoArgsConstructor @AllArgsConstructor public class JoinedWorkspaceOuJob { + /** * 加入的项目部及在该项目部下担任的所有岗位 */ @@ -26,4 +27,14 @@ public class JoinedWorkspaceOuJob { */ private Map> ouJobMap; + /** + * 加入的单位及在该单位加入的项目部 + */ + private Map> ouWorkspaceMap; + + /** + * 加入的项目部及在该项目部加入的单位 + */ + private Map> workspaceOuMap; + } \ No newline at end of file diff --git a/banner/banner-api/src/main/java/cn/axzo/nanopart/api/enums/MaterialTargetUserTypeEnum.java b/banner/banner-api/src/main/java/cn/axzo/nanopart/api/enums/MaterialTargetUserTypeEnum.java index d1cdd0a6..030f176c 100644 --- a/banner/banner-api/src/main/java/cn/axzo/nanopart/api/enums/MaterialTargetUserTypeEnum.java +++ b/banner/banner-api/src/main/java/cn/axzo/nanopart/api/enums/MaterialTargetUserTypeEnum.java @@ -8,9 +8,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; /** @@ -27,30 +25,130 @@ public enum MaterialTargetUserTypeEnum { ALL("ALL", "全部用户") { @Override - public boolean isDeliverRequired(List workspaceIds, List ouIds, List jobCodes, JoinedWorkspaceOuJob workspaceOuJob) { + public boolean isDeliverRequired(List workspaceIds, List ouIds, List jobCodes, + Long loginWorkspaceId, Long loginOuid, + JoinedWorkspaceOuJob workspaceOuJob) { return true; } }, PROJECT("PROJECT", "按照项目部") { @Override - public boolean isDeliverRequired(List workspaceIds, List ouIds, List jobCodes, JoinedWorkspaceOuJob workspaceOuJob) { - // 若workspaceIds和jobCodes都为空,则表示不限制,否则需要满足配置的workspaceIds和jobCodes与用户加入的workspaceJob的key,value均存在交集 + public boolean isDeliverRequired(List workspaceIds, List ouIds, List jobCodes, + Long loginWorkspaceId, Long loginOuid, + JoinedWorkspaceOuJob workspaceOuJob) { + Map> workspaceJobMap = workspaceOuJob.getWorkspaceJobMap(); - log.info("投放项目部:{},投放岗位岗位:{},用户加入项目部及担任岗位:{}", JSONUtil.toJsonStr(workspaceIds), JSONUtil.toJsonStr(jobCodes), JSONUtil.toJsonStr(workspaceJobMap)); - return CollectionUtils.isEmpty(workspaceIds) ? CollectionUtils.isEmpty(jobCodes) || jobCodes.stream().anyMatch(jobCode -> workspaceJobMap.values().stream().flatMap(List::stream).distinct().collect(Collectors.toList()).contains(jobCode)) - : workspaceIds.stream().anyMatch(workspaceId -> CollectionUtils.isEmpty(jobCodes) || workspaceJobMap.get(workspaceId).stream().anyMatch(jobCodes::contains)); + log.info("投放项目部:{},投放岗位岗位:{},当前登录单位:{},用户加入项目部及担任岗位:{},单位加入的项目部:{},项目部下的单位:{}", + JSONUtil.toJsonStr(ouIds), JSONUtil.toJsonStr(jobCodes), + JSONUtil.toJsonStr(loginOuid), + JSONUtil.toJsonStr(workspaceJobMap), + JSONUtil.toJsonStr(workspaceOuJob.getOuWorkspaceMap()), + JSONUtil.toJsonStr(workspaceOuJob.getWorkspaceOuMap())); + + // 配置全部项目部 + if (CollectionUtils.isEmpty(workspaceIds)) { + // 未加入任何项目部 + if (Objects.isNull(workspaceJobMap) || workspaceJobMap.isEmpty()){ + return false; + } + + if (Objects.isNull(loginOuid) || loginOuid == 0L){ + if (CollectionUtils.isEmpty(jobCodes)){ + return true; + } + return jobCodes.stream().anyMatch(jobCode -> workspaceJobMap.values().stream().anyMatch(jobCodeList -> jobCodeList.contains(jobCode))); + }else { + // 登录了具体的单位且以纯单位身份登录 + return !CollectionUtils.isEmpty(workspaceOuJob.getOuWorkspaceMap().get(loginOuid)); + } + } + + // 若配置了具体项目部 + if (Objects.isNull(loginOuid) || loginOuid == 0L) { + // 加入的全部项目部与配置的项目部有交集 + if (workspaceIds.stream().anyMatch(workspaceJobMap::containsKey)){ + if (CollectionUtils.isEmpty(jobCodes)){ + return true; + } + return workspaceIds.stream().anyMatch(workspaceId -> workspaceJobMap.get(workspaceId).stream().anyMatch(jobCodes::contains)); + } + return false; + } else { + // 给定了具体登录单位 + // 登录了具体的单位且以纯单位身份登录 + if (CollectionUtils.isEmpty(workspaceOuJob.getOuWorkspaceMap().get(loginOuid))){ + return false; + } + // 以项目部下单位登录 + // 加入的全部项目部与配置的项目部有交集 + if (workspaceIds.stream().anyMatch(workspaceJobMap::containsKey)){ + if (CollectionUtils.isEmpty(jobCodes)){ + return true; + } + return workspaceIds.stream().anyMatch(workspaceId -> workspaceJobMap.get(workspaceId).stream().anyMatch(jobCodes::contains)); + } + return false; + } } }, - UNIT("UNIT", "按照企业") { + UNIT("UNIT", "按照单位") { @Override - public boolean isDeliverRequired(List workspaceIds, List ouIds, List jobCodes, JoinedWorkspaceOuJob workspaceOuJob) { - // 若workspaceIds和jobCodes都为空,则表示不限制,否则需要满足配置的workspaceIds和jobCodes与用户加入的workspaceJob的key,value均存在交集 + public boolean isDeliverRequired(List workspaceIds, List ouIds, List jobCodes, + Long loginWorkspaceId, Long loginOuid, + JoinedWorkspaceOuJob workspaceOuJob) { + Map> ouJobMap = workspaceOuJob.getOuJobMap(); - log.info("投放单位:{},投放岗位岗位:{},用户加入单位及担任岗位:{}", JSONUtil.toJsonStr(workspaceIds), JSONUtil.toJsonStr(jobCodes), JSONUtil.toJsonStr(ouJobMap)); - return CollectionUtils.isEmpty(ouIds) ? CollectionUtils.isEmpty(jobCodes) || jobCodes.stream().anyMatch(jobCode -> ouJobMap.values().stream().flatMap(List::stream).distinct().collect(Collectors.toList()).contains(jobCode)) - : ouIds.stream().anyMatch(ouId -> CollectionUtils.isEmpty(jobCodes) || ouJobMap.get(ouId).stream().anyMatch(jobCodes::contains)); + log.info("投放单位:{},投放岗位岗位:{},当前登录单位:{},用户加入单位及担任岗位:{}", + JSONUtil.toJsonStr(ouIds), JSONUtil.toJsonStr(jobCodes), + JSONUtil.toJsonStr(loginOuid), + JSONUtil.toJsonStr(ouJobMap)); + // 判断配置的单位和岗位是否为空 + if (CollectionUtils.isEmpty(ouIds)) { + // 若配置的单位为空(即配置了全部单位) + if (CollectionUtils.isEmpty(jobCodes)) { + // 若配置的岗位也为空(即配置了所有岗位),则不限制投放 + return true; + } else { + // 若配置了具体岗位,检查用户加入的所有单位下的岗位是否包含任一配置岗位 + if (Objects.isNull(loginOuid) || loginOuid == 0L){ + return jobCodes.stream().anyMatch(jobCode -> ouJobMap.values().stream() + .flatMap(List::stream) + .distinct() + .collect(Collectors.toList()) + .contains(jobCode)); + }else { + return jobCodes.stream().anyMatch(jobCode -> ouJobMap.get(loginOuid).stream().anyMatch(jobCodes::contains)); + } + + } + } + + // 若配置了具体单位 + if (Objects.isNull(loginOuid) || loginOuid == 0L) { + // 若登录单位为空(工人APP登录),检查用户加入的单位是否与配置单位有交集 + if (ouIds.stream().anyMatch(ouJobMap::containsKey)){ + if (CollectionUtils.isEmpty(jobCodes)){ + return true; + } + return ouIds.stream().anyMatch(o -> ouJobMap.get(o).stream().anyMatch(jobCodes::contains)); + } + return false; + } else { + // 若登录单位不为空(管理APP或CMS登录),检查登录单位是否与配置单位有交集 + if (!ouIds.contains(loginOuid)) { + return false; + } + + if (CollectionUtils.isEmpty(jobCodes)) { + // 若配置了所有岗位,由于登录单位已满足条件,此时无需再检查岗位 + return true; + } else { + // 若配置了具体岗位,检查登录单位下担任的岗位是否包含任一配置岗位 + return !CollectionUtils.isEmpty(ouJobMap.get(loginOuid)) && jobCodes.stream().anyMatch(jobCode -> ouJobMap.get(loginOuid).stream().anyMatch(jobCodes::contains)); + } + } } }; @@ -61,13 +159,17 @@ public enum MaterialTargetUserTypeEnum { /** * 根据目标人权类型确认是否需要投放 * - * @param workspaceIds 配置的投放项目部 - * @param ouIds 配置的投放单位 - * @param jobCodes 配置的投放岗位 - * @param workspaceOuJob 用户参与的项目部和单位及其岗位 + * @param workspaceIds 配置的投放项目部 + * @param ouIds 配置的投放单位 + * @param jobCodes 配置的投放岗位 + * @param loginWorkspaceId 当前登录的项目部 + * @param loginOuid 当前登录的单位 + * @param workspaceOuJob 用户参与的项目部和单位及其岗位 * @return 是否需要投放 */ - public abstract boolean isDeliverRequired(List workspaceIds, List ouIds, List jobCodes, JoinedWorkspaceOuJob workspaceOuJob); + public abstract boolean isDeliverRequired(List workspaceIds, List ouIds, List jobCodes, + Long loginWorkspaceId, Long loginOuid, + JoinedWorkspaceOuJob workspaceOuJob); public static MaterialTargetUserTypeEnum getByCode(String code) { if (StringUtils.isEmpty(code)) { diff --git a/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/ListMaterialByBannerCodeReq.java b/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/ListMaterialByBannerCodeReq.java index 2203cab5..ba4d5766 100644 --- a/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/ListMaterialByBannerCodeReq.java +++ b/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/ListMaterialByBannerCodeReq.java @@ -7,7 +7,6 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import javax.validation.constraints.NotBlank; -import java.util.List; /** * @author chenwenjian @@ -33,20 +32,20 @@ public class ListMaterialByBannerCodeReq { private Long personId; /** - * 项目部id,用于筛选相关运营素材是否配置给了该项目部人员 + * 当前登录项目部id,用于筛选相关运营素材是否配置给了该项目部人员 */ - private List workspaceIds; + private Long workspaceId; /** - * 单位id,用于筛选相关运营素材是否配置给了该单位人员 + * 当前登录单位id,用于筛选相关运营素材是否配置给了该单位人员 */ - private List ouIds; + private Long ouId; /** * 岗位编码,搭配{@code workspaceIds}或{@code unitIds}使用 * 用于筛选相关运营素材是否配置给了指定项目部下指定岗位人员 * 或指定单位下指定岗位人员 */ - private List jobCodes; + private String jobCode; } diff --git a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/MaterialServiceImpl.java b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/MaterialServiceImpl.java index ebf91b4f..45493b84 100644 --- a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/MaterialServiceImpl.java +++ b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/MaterialServiceImpl.java @@ -104,7 +104,7 @@ public class MaterialServiceImpl extends ServiceImpl impl @Transactional(rollbackFor = Exception.class) @Override public List listMaterialByBannerCode(ListMaterialByBannerCodeReq req, String traceId) { - // 获取广告位并校验 + // 获取广告位并校验(是否存在,是否启用,是否在有效期) if (Objects.isNull(validateBanner(req.getBannerCode()))) { return Collections.emptyList(); } @@ -118,17 +118,17 @@ public class MaterialServiceImpl extends ServiceImpl impl // 获取当前登录用户加入的所有项目部,单位及其在项目部或在单位下担任的岗位 JoinedWorkspaceOuJob personJoinedWorkspaceOuJob = getPersonJoinedWorkspaceOuJob(req.getPersonId()); - if (Objects.isNull(personJoinedWorkspaceOuJob)) { - // 理论上不会走到这里,因为登录用户必然加入了一个项目部或单位 - throw new ServiceException("数据异常"); - } // 根据素材投放规则进行过滤 List list = materialList.stream() .filter(m -> { // 投放人群过滤 - return m.getTargetUserType().isDeliverRequired(m.getWorkspaceIds(), m.getOuIds(), m.getJobCodes(), personJoinedWorkspaceOuJob) + return m.getTargetUserType() + .isDeliverRequired(m.getWorkspaceIds(), m.getOuIds(), m.getJobCodes(), + req.getWorkspaceId(), req.getOuId(), + personJoinedWorkspaceOuJob) && + // 投放频次过滤 filterByDisplayFrequency(req, m); }) .collect(Collectors.toList()); @@ -316,15 +316,15 @@ public class MaterialServiceImpl extends ServiceImpl impl * * @param req {@link ListMaterialByBannerCodeReq} * @param m {@link Material} - * @return true:符合投放规则;false:不符合投放规则 + * @return true:符合投放规则,即为达到最大投放次数限制;false:不符合投放规则,已到达最大投放次数限制 */ private boolean filterByDisplayFrequency(ListMaterialByBannerCodeReq req, Material m) { boolean displayFrequencyFilter; String key = buildMaterialDisplayFrequencyKey(m, req.getPersonId()); if (RedisClient.KeyOps.hasKey(key)) { int frequency = Integer.parseInt(RedisClient.StringOps.get(key)); - log.info("{}已投放次数:{},素材最大投放次数:{}", req.getPersonId(), frequency, m.getMaxDisplayFrequency()); - displayFrequencyFilter = frequency < m.getMaxDisplayFrequency(); + log.info("{素材投放key:{},value:{}", key, frequency); + displayFrequencyFilter = MaterialDisplayFrequencyTypeEnum.NO_LIMIT.getCode().equals(m.getDisplayFrequencyType().getCode()) || frequency < m.getMaxDisplayFrequency(); if (displayFrequencyFilter) { // 本次需要投放 RedisClient.StringOps.incrBy(key, 1L); @@ -332,7 +332,9 @@ public class MaterialServiceImpl extends ServiceImpl impl } else { displayFrequencyFilter = true; RedisClient.StringOps.set(key, String.valueOf(1)); - RedisClient.KeyOps.expire(key, materialDisplayFrequencyKeyTtl, TimeUnit.DAYS); + // key失效时间,有效期天数加上配置的有效期天数 + long intervalDays = (m.getEndTime().getTime() - m.getStartTime().getTime()) / (1000 * 60 * 60 * 24); + RedisClient.KeyOps.expire(key, intervalDays + materialDisplayFrequencyKeyTtl, TimeUnit.DAYS); } return displayFrequencyFilter; } @@ -430,10 +432,22 @@ public class MaterialServiceImpl extends ServiceImpl impl // Map> Map> ouIdToTopNodeIdMap = genericQuery.stream() - .filter(c -> c.getWorkspaceId() == 1) + .filter(c -> c.getWorkspaceType() == 1) .collect(Collectors.groupingBy(CooperateShipResp::getOrganizationalUnitId, Collectors.mapping(CooperateShipResp::getOrganizationalNodeId, Collectors.toList()))); + // 单位加入的项目部,Map> + Map> ouIdToWorkspaceMap = genericQuery.stream() + .filter(c -> c.getWorkspaceType() == 2) + .collect(Collectors.groupingBy(CooperateShipResp::getOrganizationalUnitId, + Collectors.mapping(CooperateShipResp::getWorkspaceId, Collectors.toList()))); + + // 项目部下的单位 Map + Map> workspaceToOuIdMap = genericQuery.stream() + .filter(c -> c.getWorkspaceType() == 2) + .collect(Collectors.groupingBy(CooperateShipResp::getWorkspaceId, + Collectors.mapping(CooperateShipResp::getOrganizationalUnitId, Collectors.toList()))); + // Map Map> workspaceToJobCodeMap = workspaceToTopNodeIdMap.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, @@ -454,7 +468,7 @@ public class MaterialServiceImpl extends ServiceImpl impl .distinct() .collect(Collectors.toList()))); - return new JoinedWorkspaceOuJob(workspaceToJobCodeMap, ouIdToJobCodeMap); + return new JoinedWorkspaceOuJob(workspaceToJobCodeMap, ouIdToJobCodeMap,ouIdToWorkspaceMap,workspaceToOuIdMap); } /** @@ -481,6 +495,8 @@ public class MaterialServiceImpl extends ServiceImpl impl LocalDate now = LocalDate.now(); String suffix = ""; switch (material.getDisplayFrequencyType()) { + case NO_LIMIT: + suffix = String.join("_", MaterialDisplayFrequencyTypeEnum.NO_LIMIT.getCode()); case VALIDITY_PERIOD: suffix = String.join("_", "P", DateUtil.format(material.getStartTime(), datePattern), DateUtil.format(material.getEndTime(), datePattern)); break;