From e04a66daca197f818eff26e1dfe598799c4efbc9 Mon Sep 17 00:00:00 2001 From: chenwenjian Date: Thu, 11 Apr 2024 10:52:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(REQ-2106):=20=E6=8E=A5=E5=8F=A3=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/enums/MaterialTargetUserTypeEnum.java | 2 +- .../nanopart/api/request/CreateBannerReq.java | 6 +- .../api/request/CreateMaterialPutLogReq.java | 40 ++++ .../request/ListMaterialByBannerCodeReq.java | 5 +- .../server/controller/MaterialController.java | 6 +- .../server/domain/MaterialPutLog.java | 53 +++++ .../server/mapper/MaterialPutLogDao.java | 14 ++ .../server/service/MaterialPutLogService.java | 13 ++ .../server/service/MaterialService.java | 2 +- .../service/impl/BannerServiceImpl.java | 17 +- .../impl/MaterialPutLogServiceImpl.java | 35 ++++ .../service/impl/MaterialServiceImpl.java | 194 +++++++++++++----- .../cn/axzo/nanopart/NanopartApplication.java | 22 +- .../src/main/resources/application.yml | 2 +- 14 files changed, 327 insertions(+), 84 deletions(-) create mode 100644 banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/CreateMaterialPutLogReq.java create mode 100644 banner/banner-server/src/main/java/cn/axzo/nanopart/server/domain/MaterialPutLog.java create mode 100644 banner/banner-server/src/main/java/cn/axzo/nanopart/server/mapper/MaterialPutLogDao.java create mode 100644 banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/MaterialPutLogService.java create mode 100644 banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/MaterialPutLogServiceImpl.java 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 38fa3299..3597c7cc 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 @@ -54,7 +54,7 @@ public enum MaterialTargetUserTypeEnum { private final String desc; /** - * 是否需要投放 + * 根据目标人权类型确认是否需要投放 * * @param workspaceIds 配置的投放项目部 * @param ouIds 配置的投放单位 diff --git a/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/CreateBannerReq.java b/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/CreateBannerReq.java index 1942f586..34014e0f 100644 --- a/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/CreateBannerReq.java +++ b/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/CreateBannerReq.java @@ -4,7 +4,6 @@ import cn.axzo.nanopart.api.dto.AspectRatioDto; import cn.axzo.nanopart.api.enums.CarouselStatusEnum; import cn.axzo.nanopart.api.enums.PlatformTypeEnum; import cn.axzo.nanopart.api.enums.StatusEnum; -import com.alibaba.fastjson.JSONArray; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -17,6 +16,7 @@ import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.time.LocalTime; +import java.util.List; /** * @author chenwenjian @@ -33,6 +33,8 @@ public class CreateBannerReq { /** * 广告位名称 */ + @Length(max = 20) + @NotBlank(message = "广告位名称不能为空") private String name; /** @@ -52,7 +54,7 @@ public class CreateBannerReq { /** * 标签,内置标签:“弹窗”,“banner” */ - private JSONArray tag; + private List tag; /** * 轮播状态,取值:OPENED, CLOSED,默认为ClOSED diff --git a/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/CreateMaterialPutLogReq.java b/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/CreateMaterialPutLogReq.java new file mode 100644 index 00000000..448d924b --- /dev/null +++ b/banner/banner-api/src/main/java/cn/axzo/nanopart/api/request/CreateMaterialPutLogReq.java @@ -0,0 +1,40 @@ +package cn.axzo.nanopart.api.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * @author chenwenjian + * @version 1.0 + * @date 2024/4/11 10:38 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class CreateMaterialPutLogReq { + + /** + * 请求id + */ + private String requestId; + + /** + * 用户personId + */ + private Long personId; + + /** + * 广告位编码 + */ + private String bannerCode; + + /** + * 投放的素材id列表 + */ + private List materialIds; +} 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 87103b14..d49c8748 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 @@ -6,7 +6,9 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import java.util.List; /** @@ -30,7 +32,8 @@ public class ListMaterialByBannerCodeReq { /** * 当前登录人的personId */ - // @NotNull(message = "personId不能为空") + @NotNull(message = "personId不能为空") + @Min(value = 1, message = "请传入正确的personId") private Long personId; /** diff --git a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/controller/MaterialController.java b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/controller/MaterialController.java index f6a12d2f..684d40a5 100644 --- a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/controller/MaterialController.java +++ b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/controller/MaterialController.java @@ -17,6 +17,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; import java.util.List; /** @@ -31,6 +32,8 @@ public class MaterialController implements MaterialApi { private final MaterialService materialService; + private final HttpServletRequest request; + @Override public ApiPageResult page(PageMaterialReq req) { Page pageData = materialService.page(req); @@ -61,6 +64,7 @@ public class MaterialController implements MaterialApi { @Override public ApiResult> listMaterialByBannerCode(ListMaterialByBannerCodeReq req) { - return ApiResult.ok(materialService.listMaterialByBannerCode(req)); + String traceId = request.getHeader("traceId"); + return ApiResult.ok(materialService.listMaterialByBannerCode(req,traceId)); } } diff --git a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/domain/MaterialPutLog.java b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/domain/MaterialPutLog.java new file mode 100644 index 00000000..8b228394 --- /dev/null +++ b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/domain/MaterialPutLog.java @@ -0,0 +1,53 @@ +package cn.axzo.nanopart.server.domain; + +import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; +import cn.axzo.pokonyan.config.mybatisplus.type.LongListTypeHandler; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 素材投放日志记录 + * + * @author chenwenjian + * @version 1.0 + * @date 2024/4/10 18:36 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@TableName(value = "material_put_log", autoResultMap = true) +public class MaterialPutLog extends BaseEntity { + + /** + * 请求id + */ + @TableField(value = "request_id") + private String requestId; + + /** + * 用户personId + */ + @TableField(value = "person_id") + private Long personId; + + /** + * 广告位编码 + */ + @TableField(value = "banner_code") + private String bannerCode; + + /** + * 投放的素材id列表 + */ + @TableField(value = "material_ids", typeHandler = LongListTypeHandler.class) + private List materialIds; +} diff --git a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/mapper/MaterialPutLogDao.java b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/mapper/MaterialPutLogDao.java new file mode 100644 index 00000000..0e28378d --- /dev/null +++ b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/mapper/MaterialPutLogDao.java @@ -0,0 +1,14 @@ +package cn.axzo.nanopart.server.mapper; + +import cn.axzo.nanopart.server.domain.MaterialPutLog; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author chenwenjian + * @version 1.0 + * @date 2024/4/10 18:40 + */ +@Mapper +public interface MaterialPutLogDao extends BaseMapper { +} diff --git a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/MaterialPutLogService.java b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/MaterialPutLogService.java new file mode 100644 index 00000000..5dd70ab0 --- /dev/null +++ b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/MaterialPutLogService.java @@ -0,0 +1,13 @@ +package cn.axzo.nanopart.server.service; + +import cn.axzo.nanopart.api.request.CreateMaterialPutLogReq; + +/** + * @author chenwenjian + * @version 1.0 + * @date 2024/4/10 18:41 + */ +public interface MaterialPutLogService { + + Long create(CreateMaterialPutLogReq req); +} diff --git a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/MaterialService.java b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/MaterialService.java index 49b812de..4a993d32 100644 --- a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/MaterialService.java +++ b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/MaterialService.java @@ -28,7 +28,7 @@ public interface MaterialService { * @param req {@link ListMaterialByBannerCodeReq} * @return 根据优先级(priority)升序,创建时间(createAt)降序排序后的列表 */ - List listMaterialByBannerCode(ListMaterialByBannerCodeReq req); + List listMaterialByBannerCode(ListMaterialByBannerCodeReq req,String traceId); Page page(PageMaterialReq req); diff --git a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/BannerServiceImpl.java b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/BannerServiceImpl.java index 371b1112..cc050208 100644 --- a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/BannerServiceImpl.java +++ b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/BannerServiceImpl.java @@ -10,14 +10,14 @@ import cn.axzo.nanopart.api.request.UpdateBannerReq; import cn.axzo.nanopart.api.request.UpdateStatusReq; import cn.axzo.nanopart.api.response.DetailBannerResp; import cn.axzo.nanopart.api.response.PageBannerResp; -import cn.axzo.nanopart.server.mapper.BannerDao; import cn.axzo.nanopart.server.domain.Banner; +import cn.axzo.nanopart.server.mapper.BannerDao; import cn.axzo.nanopart.server.service.BannerOperationLogService; import cn.axzo.nanopart.server.service.BannerService; import cn.axzo.pokonyan.dao.converter.PageConverter; import cn.hutool.core.bean.BeanUtil; import com.alibaba.fastjson.JSONObject; -import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -49,10 +49,10 @@ public class BannerServiceImpl extends ServiceImpl implements */ @Override public Page page(PageBannerReq req) { - LambdaQueryChainWrapper pageLambdaQueryChain = buildPageLambdaQueryChain(req); + LambdaQueryWrapper pageLambdaQueryWrapper = buildPageLambdaQueryWrapper(req); - Page bannerPage = page(new Page<>(req.getPageNumber(), req.getPageSize()), pageLambdaQueryChain); - if (bannerPage.getTotal() == 0) { + Page bannerPage = page(new Page<>(req.getPageNumber(), req.getPageSize()), pageLambdaQueryWrapper); + if (Objects.isNull(bannerPage) || bannerPage.getTotal() == 0) { return new Page<>(); } return PageConverter.convert(bannerPage, record -> BeanUtil.toBean(record, PageBannerResp.class)); @@ -217,10 +217,11 @@ public class BannerServiceImpl extends ServiceImpl implements * 构建分页查询条件 * * @param req {@link PageBannerReq} - * @return {@link LambdaQueryChainWrapper} + * @return {@link LambdaQueryWrapper} */ - private LambdaQueryChainWrapper buildPageLambdaQueryChain(PageBannerReq req) { - return lambdaQuery() + private LambdaQueryWrapper buildPageLambdaQueryWrapper(PageBannerReq req) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper(); + return lambdaQueryWrapper .like(StringUtils.isNotEmpty(req.getName()), Banner::getName, req.getName()) .eq(Objects.nonNull(req.getTerminal()), Banner::getTerminal, req.getTerminal()) .eq(Objects.nonNull(req.getStatus()), Banner::getStatus, req.getStatus()) diff --git a/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/MaterialPutLogServiceImpl.java b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/MaterialPutLogServiceImpl.java new file mode 100644 index 00000000..1700f916 --- /dev/null +++ b/banner/banner-server/src/main/java/cn/axzo/nanopart/server/service/impl/MaterialPutLogServiceImpl.java @@ -0,0 +1,35 @@ +package cn.axzo.nanopart.server.service.impl; + +import cn.axzo.nanopart.api.request.CreateMaterialPutLogReq; +import cn.axzo.nanopart.server.domain.MaterialPutLog; +import cn.axzo.nanopart.server.mapper.MaterialPutLogDao; +import cn.axzo.nanopart.server.service.MaterialPutLogService; +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * @author chenwenjian + * @version 1.0 + * @date 2024/4/10 18:41 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MaterialPutLogServiceImpl extends ServiceImpl implements MaterialPutLogService { + + /** + * 创建 + * + * @param req {@link CreateMaterialPutLogReq} + * @return {@link Long} + */ + @Override + public Long create(CreateMaterialPutLogReq req) { + MaterialPutLog materialPutLog = BeanUtil.copyProperties(req, MaterialPutLog.class); + save(materialPutLog); + return materialPutLog.getId(); + } +} 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 f6d2430b..17d7c3d3 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 @@ -11,6 +11,7 @@ import cn.axzo.nanopart.api.enums.MaterialDisplayFrequencyTypeEnum; import cn.axzo.nanopart.api.enums.RecodeTypeEnum; import cn.axzo.nanopart.api.enums.StatusEnum; import cn.axzo.nanopart.api.request.CreateBannerOperationLogReq; +import cn.axzo.nanopart.api.request.CreateMaterialPutLogReq; import cn.axzo.nanopart.api.request.CreateMaterialReq; import cn.axzo.nanopart.api.request.DetailMaterialReq; import cn.axzo.nanopart.api.request.ListMaterialByBannerCodeReq; @@ -27,27 +28,37 @@ import cn.axzo.nanopart.server.rpc.OrganizationalJobGateway; import cn.axzo.nanopart.server.rpc.OrganizationalNodeUserGateway; import cn.axzo.nanopart.server.service.BannerOperationLogService; import cn.axzo.nanopart.server.service.BannerService; +import cn.axzo.nanopart.server.service.MaterialPutLogService; import cn.axzo.nanopart.server.service.MaterialService; +import cn.axzo.pokonyan.config.redis.RedisClient; import cn.axzo.pokonyan.dao.converter.PageConverter; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson.JSONObject; -import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -import java.time.LocalDateTime; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAdjusters; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -57,6 +68,7 @@ import java.util.stream.Collectors; */ @Slf4j @Service +@RefreshScope @RequiredArgsConstructor public class MaterialServiceImpl extends ServiceImpl implements MaterialService { @@ -64,13 +76,20 @@ public class MaterialServiceImpl extends ServiceImpl impl private final BannerOperationLogService bannerOperationLogService; + private final MaterialPutLogService materialPutLogService; + private final OrganizationalNodeUserGateway organizationalNodeUserGateway; private final OrganizationalJobGateway organizationalJobGateway; private final CooperateShipGateway cooperateShipGateway; - // private final + /** + * 素材投放频次缓存key有效期, + * 单位:天,默认为90天 + */ + @Value("${materialDisplayFrequencyKeyTtl: 90}") + private Long materialDisplayFrequencyKeyTtl; /** * 根据广告位编码(bannerCode)查询素材 @@ -81,15 +100,12 @@ public class MaterialServiceImpl extends ServiceImpl impl * @param req {@link ListMaterialByBannerCodeReq} * @return 根据优先级(priority)升序,创建时间(createAt)降序排序后的列表 */ + @Transactional(rollbackFor = Exception.class) @Override - public List listMaterialByBannerCode(ListMaterialByBannerCodeReq req) { + public List listMaterialByBannerCode(ListMaterialByBannerCodeReq req, String traceId) { // 获取广告位并校验 - Banner banner = bannerService.selectByCode(req.getBannerCode()); - if (Objects.isNull(banner)) { - throw new ServiceException("广告位不存在"); - } - if (banner.getStatus() != StatusEnum.ONLINE) { - throw new ServiceException("广告位已停用"); + if (Objects.isNull(validateBanner(req.getBannerCode()))) { + return Collections.emptyList(); } // 获取该广告位下已上架且在有效期内的所有素材 @@ -105,44 +121,30 @@ public class MaterialServiceImpl extends ServiceImpl impl throw new ServiceException("数据异常"); } - // 根据素材投放人群和投放规则频次进行过滤 + // 根据素材投放规则进行过滤 List list = materialList.stream() .filter(m -> { - m.getTargetUserType().isDeliverRequired(m.getWorkspaceIds(), m.getOuIds(), m.getJobCodes(), personJoinedWorkspaceOuJob); - - // 频次过滤,这个人 - // RedisClient.KeyOps.hasKey(); - return false; + // 投放人群过滤 + return m.getTargetUserType().isDeliverRequired(m.getWorkspaceIds(), m.getOuIds(), m.getJobCodes(), personJoinedWorkspaceOuJob) + && + filterByDisplayFrequency(req, m); }) .collect(Collectors.toList()); + if (CollectionUtils.isEmpty(list)) { + return Collections.emptyList(); + } + + // 记录素材投放日志 + CreateMaterialPutLogReq materialPutLogReq = new CreateMaterialPutLogReq(); + materialPutLogReq.setRequestId(traceId) + .setPersonId(req.getPersonId()) + .setBannerCode(req.getBannerCode()) + .setMaterialIds(list.stream().map(Material::getId).collect(Collectors.toList())); + materialPutLogService.create(materialPutLogReq); return BeanUtil.copyToList(list, MaterialResp.class); } - /** - * 构建素材投放频次记录缓存key - * - *

- * 1.含义:指定素材给指定人按照投放频次类型投放次数 - * 2.组成:namespace:bannerCode:materialId:personId:频次类型转换后的值 - * 3.频次类型转换规则示例:投放频次类型参考{@link MaterialDisplayFrequencyTypeEnum} - * 有效期内:VALIDITY_PERIOD -> 有效期 - * 每天:EVERY_DAY -> 当天 - * 每周:EVERY_WEEK -> 当周第一天 - * 每月:EVERY_MONTH -> 当月第一天 - *

- * - * @param material 素材信息 - * @param personId 投放人Id - * @return key - */ - public static String buildMaterialDisplayFrequencyKey(Material material, Long personId) { - LocalDateTime now = LocalDateTime.now(); - - // return String.format("nanopart:%s:%s", materialId, bannerId); - return null; - } - /** * 分页查询 * @@ -151,7 +153,7 @@ public class MaterialServiceImpl extends ServiceImpl impl */ @Override public Page page(PageMaterialReq req) { - LambdaQueryChainWrapper pageQueryLambdaWrapper = buildPageQueryLambdaWrapper(req); + LambdaQueryWrapper pageQueryLambdaWrapper = buildPageQueryLambdaWrapper(req); Page materialPage = page(new Page<>(req.getPageNumber(), req.getPageSize()), pageQueryLambdaWrapper); if (materialPage.getTotal() == 0) { @@ -266,14 +268,68 @@ public class MaterialServiceImpl extends ServiceImpl impl return BeanUtil.copyProperties(getById(req.getId()), MaterialResp.class); } + /** + * 对于给定bannerCode校验其存在性和状态有效性 + * + * @param bannerCode 广告位code + * @return 若存在且有效返回广告位对象,否则否则返回null + */ + private Banner validateBanner(String bannerCode) { + if (StringUtils.isEmpty(bannerCode)) { + return null; + } + Banner banner = bannerService.selectByCode(bannerCode); + if (Objects.isNull(banner)) { + log.warn("广告位不存在,广告位code:{}", bannerCode); + return null; + } + if (banner.getStatus() != StatusEnum.ONLINE) { + log.warn("广告位已停用,广告位code:{}", bannerCode); + return null; + } + LocalTime now = LocalTime.now(); + // 当前不在广告位配置的展示时间范围内 + if (now.isBefore(banner.getStartTime()) || now.isAfter(banner.getEndTime())) { + log.warn("当前时间不在广告位配置的展示时间范围内,广告位code:{},当前时间:{},广告位开始时间:{},广告位结束时间:{}", bannerCode, now, banner.getStartTime(), banner.getEndTime()); + return null; + } + return banner; + } + + /** + * 根据素材投放频次规则进行过滤 + * + * @param req {@link ListMaterialByBannerCodeReq} + * @param m {@link Material} + * @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)); + displayFrequencyFilter = frequency < m.getMaxDisplayFrequency(); + if (displayFrequencyFilter) { + // 本次需要投放 + RedisClient.StringOps.incrBy(key, 1L); + } + } else { + displayFrequencyFilter = true; + RedisClient.StringOps.set(key, String.valueOf(1)); + RedisClient.KeyOps.expire(key, materialDisplayFrequencyKeyTtl, TimeUnit.DAYS); + } + return displayFrequencyFilter; + } + /** * 构建分页查询条件 * * @param req {@link PageMaterialReq} - * @return {@link LambdaQueryChainWrapper} + * @return {@link LambdaQueryWrapper} */ - private LambdaQueryChainWrapper buildPageQueryLambdaWrapper(PageMaterialReq req) { - return lambdaQuery() + private LambdaQueryWrapper buildPageQueryLambdaWrapper(PageMaterialReq req) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + return lambdaQueryWrapper .eq(Material::getIsDelete, 0) .eq(StringUtils.isNotEmpty(req.getBannerCode()), Material::getBannerCode, req.getBannerCode()) .like(StringUtils.isNotEmpty(req.getName()), Material::getName, req.getName()) @@ -289,16 +345,17 @@ public class MaterialServiceImpl extends ServiceImpl impl * 构建通过BannerCode查询素材的wrapper * * @param req {@link ListMaterialByBannerCodeReq} - * @return {@link LambdaQueryChainWrapper} + * @return {@link LambdaQueryWrapper} */ - private LambdaQueryChainWrapper buildListByBannerCodeQueryWrapper(ListMaterialByBannerCodeReq req) { + private LambdaQueryWrapper buildListByBannerCodeQueryWrapper(ListMaterialByBannerCodeReq req) { Date now = new Date(System.currentTimeMillis()); - return lambdaQuery() + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + return lambdaQueryWrapper .eq(Material::getIsDelete, 0) .eq(Material::getBannerCode, req.getBannerCode()) .eq(Material::getStatus, StatusEnum.ONLINE) - .ge(Material::getStartTime, now) - .le(Material::getEndTime, now) + .le(Material::getStartTime, now) + .ge(Material::getEndTime, now) .orderByAsc(Material::getPriority) .orderByDesc(Material::getCreateAt); } @@ -384,4 +441,43 @@ public class MaterialServiceImpl extends ServiceImpl impl return new JoinedWorkspaceOuJob(workspaceToJobCodeMap, ouIdToJobCodeMap); } + /** + * 构建素材投放频次记录缓存key + * + *

+ * 1.含义:指定素材给指定人按照投放频次类型投放次数 + * 2.组成:namespace:bannerCode:materialId:personId:频次类型转换后的值 + * 3.频次类型转换规则示例:投放频次类型参考{@link MaterialDisplayFrequencyTypeEnum} + * 有效期内:VALIDITY_PERIOD -> 有效期 + * 每天:EVERY_DAY -> 当天 + * 每周:EVERY_WEEK -> 当周第一天 + * 每月:EVERY_MONTH -> 当月第一天 + *

+ * + * @param material 素材信息 + * @param personId 投放人Id + * @return key + */ + public static String buildMaterialDisplayFrequencyKey(Material material, Long personId) { + String prefix = String.format("nanopart:%s:%s:%s:", material.getBannerCode(), material.getId(), personId); + String datePattern = "yyyy_MM_dd"; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(datePattern); + LocalDate now = LocalDate.now(); + String suffix = ""; + switch (material.getDisplayFrequencyType()) { + case VALIDITY_PERIOD: + suffix = String.join("_", "P", DateUtil.format(material.getStartTime(), datePattern), DateUtil.format(material.getEndTime(), datePattern)); + break; + case EVERY_DAY: + suffix = String.join("_", "D", now.format(formatter)); + break; + case EVERY_WEEK: + suffix = String.join("_", "W", now.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).format(formatter)); + break; + case EVERY_MONTH: + suffix = String.join("_", "M", now.with(TemporalAdjusters.firstDayOfMonth()).format(formatter)); + } + return prefix + suffix; + } + } diff --git a/nanopart-server/src/main/java/cn/axzo/nanopart/NanopartApplication.java b/nanopart-server/src/main/java/cn/axzo/nanopart/NanopartApplication.java index 3481d504..63d7b540 100644 --- a/nanopart-server/src/main/java/cn/axzo/nanopart/NanopartApplication.java +++ b/nanopart-server/src/main/java/cn/axzo/nanopart/NanopartApplication.java @@ -6,37 +6,19 @@ import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; -import org.springframework.core.env.Environment; @Slf4j @MapperScan(value = {"cn.axzo.**.mapper"}) @SpringBootApplication @EnableFeignClients(basePackages = { - "cn.axzo.nanopart.api" + "cn.axzo" }) @EnableAspectJAutoProxy() @Import(RocketMQEventConfiguration.class) public class NanopartApplication { public static void main(String[] args) { - ConfigurableApplicationContext run = SpringApplication.run(NanopartApplication.class, args); - Environment env = run.getEnvironment(); - log.info( - "--------------------------------------------------------------------------------------------------------------------\n" + - "Application 【{}】 is running on 【{}】 environment!\n" + - "Api Local: \thttp://127.0.0.1:{}\n" + - "Mysql: \t{}\t username:{}\n" + - "Redis: \t{}:{}\t database:{}\n", - env.getProperty("spring.application.name"), - env.getProperty("spring.profiles.active"), - env.getProperty("server.port"), - env.getProperty("spring.datasource.url"), - env.getProperty("spring.datasource.username"), - env.getProperty("spring.redis.host"), - env.getProperty("spring.redis.port"), - env.getProperty("spring.redis.database") + - "\n----------------------------------------------------------"); + SpringApplication.run(NanopartApplication.class, args); } } diff --git a/nanopart-server/src/main/resources/application.yml b/nanopart-server/src/main/resources/application.yml index 1fd01c81..644e549c 100644 --- a/nanopart-server/src/main/resources/application.yml +++ b/nanopart-server/src/main/resources/application.yml @@ -21,7 +21,7 @@ mybatis-plus: logic-delete-value: id #逻辑已删除值(默认为 1) logic-not-delete-value: 0 #逻辑未删除值(默认为 0) logic-delete-field: isDelete #逻辑删除字段 - type-enums-package: cn.axzo.nanopart.api.constant.enums,cn.axzo.nanopart.api.enums + type-enums-package: cn.axzo.nanopart.api.constant.enums;cn.axzo.nanopart.api.enums logging: level: