第五章 广告投放系统——定义 Dao 接口和部分功能实现

此博客用于个人学习,来源于网上,对知识点进行一个整理。

1. 定义 Dao 接口:

上一篇实现了实体类,接下来进行 Dao 接口的编写。

1.1 用户 Dao 接口:

AdUserRepository:

public interface AdUserRepository extends JpaRepository<AdUser,Long> {

    /**
     * 根据用户名查找用户记录
     * @param username
     * @return
     */
    AdUser findByUsername(String username);
}

1.2 推广计划 Dao 接口:

AdPlanRepository:

public interface AdPlanRepository extends JpaRepository<AdPlan,Long> {

    /**
     * 根据 ID 和用户 ID 查询推广计划
     * @param id
     * @param userId
     * @return
     */
    AdPlan findByIdAndUserId(Long id,Long userId);

    /**
     * 根据 ID 和用户 ID 查询推广计划组
     * @param ids
     * @param userId
     * @return
     */
    List<AdPlan> findAllByIdAndUserId(List<Long> ids,Long userId);

    /**
     * 根据用户 ID 和推广计划名查询推广计划
     * @param userId
     * @param planName
     * @return
     */
    AdPlan findByUserIdAndPlanName(Long userId,String planName);

    /**
     * 根据推广计划状态查询推广计划组
     * @param status
     * @return
     */
    List<AdPlan> findAllByPlanStatus(Integer status);
}

1.3 推广单元 Dao 接口:

AdUnitRepository:

public interface AdUnitRepository extends JpaRepository<AdUnit,Long> {

    /**
     * 根据推广计划 ID 和推广单元名称查询推广单元
     * @param planId
     * @param userName
     * @return
     */
    AdUnit findByPlanIdAndUnitName(Long planId,String userName);

    /**
     * 根据推广单元状态查询推广单元组
     * @param unitStatus
     * @return
     */
    List<AdUnit> findAllByUnitStatus(Integer unitStatus);
}

1.4 创意 Dao 接口:

CreativeRepository:

public interface CreativeRepository extends JpaRepository<Creative,Long> {
}

1.5 其他 Dao 接口:

AdUnitKeyWordRepository:

public interface AdUnitKeyWordRepository extends JpaRepository<AdUnitKeyword,Long> {
}

AdUnitItRepository:

public interface AdUnitItRepository extends JpaRepository<AdUnitIt,Long> {
}

AdUnitDistrictRepository:

public interface AdUnitDistrictRepository extends JpaRepository<AdUnitDistrict,Long> {
}

CreativeUnitRepository:

public interface CreativeUnitRepository extends JpaRepository<CreativeUnit,Long> {
}

2. 用户账户服务功能实现:

目前,数据实体类和 Dao 接口已经完成,可以进行功能的实现,也就是完成 Service 的编写。首先实现处于最高层的用户服务功能的实现。

2.1 工具类:

定义一个工具类,通过算法由 username 获取 token。

public class CommonUtils {

    public static String md5(String value){
        return DigestUtils.md5Hex(value).toUpperCase();
    }
}

2.2 定义响应格式:

由于要进行数据的交互,所以需要定义一个 Request 和 Response 的响应格式。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserRequest {

    private String username;

    /**
     * 验证参数是否有效
     * @return
     */
    public boolean validate(){
        return !StringUtils.isEmpty(username);
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserResponse {

    private Long userId;
    private String username;
    private String token;
    private Date createTime;
    private Date updateTime;
}

2.3 错误信息类:

考虑到运行过程中可能出现的错误情况,需要我们定义一个记录各种错误信息的类。

public class Constants {

    public static class ErrorMsg{

        public static final String REQUEST_PARAM_ERROR = "请求参数错误";
        public static final String SAME_NAME_ERROR = "存在同名的用户";
    }
}

2.4 核心逻辑 Service:

前面构造的类都是为了核心的逻辑而建立的,以创建用户方法为例。首先,先定义一个 Service 接口,如下:

public interface IUserService {

    /**
     * 创建用户
     * @param request
     * @return
     * @throws Exception
     */
    CreateUserResponse createUser(CreateUserRequest request) throws AdException;
}

然后,实现该接口,函数的核心逻辑是——首先查询数据库中是否有与该新建用户名字相同的用户,如果有,抛出异常;如果没有,对数据库进行操作,新建一个用户。

@Slf4j
@Service
public class UserServiceImpl implements IUserService {

    private final AdUserRepository userRepository;

    @Autowired
    public UserServiceImpl(AdUserRepository userRepository){
        this.userRepository = userRepository;
    }

    @Override
    //往数据库进行操作,标记为事务
    @Transactional
    public CreateUserResponse createUser(CreateUserRequest request) throws AdException {
        //抛出请求参数错误的异常
        if (!request.validate()){
            throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
        }
        AdUser oldUser = userRepository.findByUsername(request.getUsername());
        //抛出相同用户的异常
        if (oldUser != null){
            throw new AdException(Constants.ErrorMsg.SAME_NAME_ERROR);
        }
        AdUser newUser = userRepository.save(new AdUser(
                request.getUsername(),
                CommonUtils.md5(request.getUsername())
        ));
        return new CreateUserResponse(
                newUser.getId(),newUser.getUsername(),newUser.getToken(),
                newUser.getCreateTime(),newUser.getUpdateTime()
        );
    }
}

3. 推广计划服务功能实现:

接下来,完成推广计划的服务功能。

3.1 工具类:

在刚才定义的工具类中添加功能实现将字符串类型转换为日期类型。

public class CommonUtils {

    private static String[] parsePatterns = {
            "yyyy-MM-dd","yyyy/MM/dd","yyyy.MM.dd"
    };

    public static String md5(String value){
        return DigestUtils.md5Hex(value).toUpperCase();
    }

    /**
     * 字符串类型转为日期类型
     * @param dateString
     * @return
     * @throws AdException
     */
    public static Date parseStringDate(String dateString) throws AdException {
        try {
            return DateUtils.parseDate(dateString,parsePatterns);
        }catch (Exception ex){
            throw new AdException(ex.getMessage());
        }
    }
}

3.2 定义响应格式:

由于要实现增删改查的功能,其中,增删改的请求类型可以是一样的,查询的请求类型单独建立一个类,以及响应的类,一共三个。

@Data
@NoArgsConstructor
@AllArgsConstructor
//增删改操作的请求
public class AdPlanRequest {

    private Long id;
    private Long userId;
    private String planName;
    private String startDate;
    private String endDate;

    /**
     * 对创建操作的输入参数进行校验
     * @return
     */
    public boolean createValidate(){
        return userId != null
                && !StringUtils.isEmpty(planName)
                && !StringUtils.isEmpty(startDate)
                && !StringUtils.isEmpty(endDate);
    }

    /**
     * 对更新操作的输入参数进行校验
     * @return
     */
    public boolean updateValidate(){
        return id != null && userId != null;
    }

    /**
     * 对删除操作的输入参数进行校验
     * @return
     */
    public boolean deleteValidate(){
        return id != null && userId != null;
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
//查询操作的请求
public class AdPlanGetRequest {

    private Long userId;
    private List<Long> ids;

    /**
     * 对查询操作的输入参数进行验证
     * @return
     */
    public boolean validate(){
        return userId != null && !CollectionUtils.isEmpty(ids);
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
//对创建更新操作的响应
public class AdPlanResponse {

    private Long id;
    private String planName;
}

3.3 错误信息类:

在刚才定义的数据类型中,添加更多的错误信息来满足后面 Service 的需要。

public class Constants {

    public static class ErrorMsg{

        public static final String REQUEST_PARAM_ERROR = "请求参数错误";
        public static final String SAME_NAME_ERROR = "存在同名的用户";
        public static final String CAN_NOT_FIND_RECORD = "找不到数据记录";
        public static final String SAME_NAME_PLAN_ERROR = "存在同名的推广计划";
    }
}

3.4 核心逻辑 Service:

这次实现的是增删改查四个功能,其中最重要的就是对输入参数的验证,于是其中验证和抛出异常的代码占比较高。

Service 接口:

public interface IAdPlanService {

    /**
     * 创建推广计划
     * @param request
     * @return
     * @throws AdException
     */
    AdPlanResponse createAdPlan(AdPlanRequest request) throws AdException;

    /**
     * 获取推广计划
     * @param request
     * @return
     * @throws AdException
     */
    List<AdPlan> getAdPlanByIds(AdPlanGetRequest request) throws AdException;

    /**
     * 更新推广计划
     * @param request
     * @return
     * @throws AdException
     */
    AdPlanResponse updateAdPlan(AdPlanRequest request) throws AdException;

    /**
     * 删除推广计划
     * @param request
     * @throws AdException
     */
    void deleteAdPlan(AdPlanRequest request) throws AdException;
}

Service 实现类:

@Service
public class AdPlanServiceImpl implements IAdPlanService {

    private final AdUserRepository userRepository;

    private final AdPlanRepository planRepository;

    @Autowired
    public AdPlanServiceImpl(AdUserRepository userRepository, AdPlanRepository planRepository) {
        this.userRepository = userRepository;
        this.planRepository = planRepository;
    }

    /**
     * 创建推广计划:先查询数据中是否有一样的推广计划,如果有就抛出异常,没有就创建一个新的推广计划
     * @param request
     * @return
     * @throws AdException
     */
    @Override
    @Transactional
    public AdPlanResponse createAdPlan(AdPlanRequest request) throws AdException {
        if (!request.createValidate()){
            throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
        }
        //确保关联的 User 是存在的
        Optional<AdUser> adUser = userRepository.findById(request.getUserId());
        if (!adUser.isPresent()){
            throw new AdException(Constants.ErrorMsg.CAN_NOT_FIND_RECORD);
        }
        AdPlan oldPlan = planRepository.findByUserIdAndPlanName(
                request.getUserId(),request.getPlanName()
        );
        if (oldPlan != null){
            throw new AdException(Constants.ErrorMsg.SAME_NAME_PLAN_ERROR);
        }
        AdPlan newAdPlan = planRepository.save(
                new AdPlan(request.getUserId(),request.getPlanName(),
                        CommonUtils.parseStringDate(request.getStartDate()),
                        CommonUtils.parseStringDate(request.getEndDate())
                        )
        );
        return new AdPlanResponse(newAdPlan.getId(),newAdPlan.getPlanName());
    }

    /**
     * 获取推广计划:验证输入参数是否合理后直接调用方法
     * @param request
     * @return
     * @throws AdException
     */
    @Override
    public List<AdPlan> getAdPlanByIds(AdPlanGetRequest request) throws AdException {
        if (!request.validate()){
            throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
        }
        return planRepository.findAllByIdAndUserId(
                request.getIds(),request.getUserId()
        );
    }

    /**
     * 更新推广计划,首先查询数据库中是否有符合条件的推广计划,没有就抛出异常;如果有的话就更新其各项信息
     * @param request
     * @return
     * @throws AdException
     */
    @Override
    @Transactional
    public AdPlanResponse updateAdPlan(AdPlanRequest request) throws AdException {
        if (!request.updateValidate()){
            throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
        }
        AdPlan plan = planRepository.findByIdAndUserId(
                request.getId(),request.getUserId()
        );
        if (plan == null){
            throw new AdException(Constants.ErrorMsg.CAN_NOT_FIND_RECORD);
        }
        if (request.getPlanName() != null){
            plan.setPlanName(request.getPlanName());
        }
        if (request.getStartDate() != null){
            plan.setStartDate(CommonUtils.parseStringDate(request.getStartDate()));
        }
        if (request.getEndDate() != null){
            plan.setEndDate(CommonUtils.parseStringDate(request.getEndDate()));
        }
        plan.setUpdateTime(new Date());
        plan = planRepository.save(plan);
        return new AdPlanResponse(plan.getId(),plan.getPlanName());
    }

    /**
     * 删除推广计划:通过传入的参数获取推广计划,将其状态设为无效
     * @param request
     * @throws AdException
     */
    @Override
    @Transactional
    public void deleteAdPlan(AdPlanRequest request) throws AdException {
        if (!request.deleteValidate()){
            throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
        }
        AdPlan plan = planRepository.findByIdAndUserId(
                request.getId(),request.getUserId()
        );
        if (plan == null){
            throw new AdException(Constants.ErrorMsg.CAN_NOT_FIND_RECORD);
        }

        plan.setPlanStatus(CommonStatus.INVALID.getStatus());
        plan.setUpdateTime(new Date());
        planRepository.save(plan);
    }
}

4. 推广单元服务功能实现:

接下来,完成推广单元的服务功能。

4.1 定义响应格式:

请求:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdUnitRequest {

    private Long planId;
    private String unitName;

    private Integer positionType;
    private Long budget;

    /**
     * 对输入参数进行验证
     * @return
     */
    public boolean createValidate(){
        return planId != null && !StringUtils.isEmpty(unitName) && positionType != null && budget != null;
    }
}

响应:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdUnitResponse {

    private Long id;
    private String unitName;
}

4.2 错误信息类:

在刚才定义的数据类型中,添加更多的错误信息来满足后面 Service 的需要。

public class Constants {

    public static class ErrorMsg{

        public static final String REQUEST_PARAM_ERROR = "请求参数错误";
        public static final String SAME_NAME_ERROR = "存在同名的用户";
        public static final String CAN_NOT_FIND_RECORD = "找不到数据记录";
        public static final String SAME_NAME_PLAN_ERROR = "存在同名的推广计划";
        public static final String SAME_NAME_UNIT_ERROR = "存在同名的推广单元";
    }
}

4.3 核心逻辑 Service:

与推广计划相似,先进行接口的编写,再对其实现,其中验证和抛出异常的代码占比较高。

Service 接口:

public interface IAdUnitService {

    AdUnitResponse createUnit(AdUnitRequest request) throws AdException;
}

Service 实现类:

@Service
public class AdUnitServiceImpl implements IAdUnitService {

    private final AdPlanRepository planRepository;
    private final AdUnitRepository unitRepository;

    @Autowired
    public AdUnitServiceImpl(AdPlanRepository planRepository, AdUnitRepository unitRepository) {
        this.planRepository = planRepository;
        this.unitRepository = unitRepository;
    }

    @Override
    public AdUnitResponse createUnit(AdUnitRequest request) throws AdException {

        if (!request.createValidate()){
            throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
        }
        Optional<AdPlan> adPlan = planRepository.findById(request.getPlanId());
        if (!adPlan.isPresent()){
            throw new AdException(Constants.ErrorMsg.CAN_NOT_FIND_RECORD);
        }
        AdUnit oldAdUnit = unitRepository.findByPlanIdAndUnitName(
                request.getPlanId(),request.getUnitName()
        );
        if (oldAdUnit != null){
            throw new AdException(Constants.ErrorMsg.SAME_NAME_UNIT_ERROR);
        }
        AdUnit newAdUnit = unitRepository.save(
                new AdUnit(request.getPlanId(),request.getUnitName(),request.getPositionType(),request.getBudget())
        );
        return new AdUnitResponse(newAdUnit.getId(),newAdUnit.getUnitName());
    }
}

5. 推广单元限制服务功能实现:

由于推广单元存在一些限制,需要我们定义更多的响应格式数据和实现与限制有关的方法。

5.1 定义响应格式:

关联词限制:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdUnitKeywordRequest {

    private List<UnitKeyword> unitKeywords;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class UnitKeyword{
        private Long unitId;
        private String keyword;
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdUnitKeywordResponse {

    private List<Long> ids;
}

地域限制:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdUnitItRequest {

    private List<UnitIt> unitIts;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class UnitIt{

        private Long unitId;
        private String itTag;
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdUnitItResponse {

    private List<Long> ids;
}

兴趣限制:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdUnitDistrictRequest {

    private List<UnitDistrict> unitDistricts;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class UnitDistrict {

        private Long unitId;
        private String province;
        private String city;
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdUnitDistrictResponse {

    private List<Long> ids;
}

5.2 核心逻辑 Service:

在原来的接口和实现类中,添加三个方法。

Service 接口:

public interface IAdUnitService {

    AdUnitResponse createUnit(AdUnitRequest request) throws AdException;

    AdUnitKeywordResponse createUnitKeyword(AdUnitKeywordRequest request) throws AdException;

    AdUnitItResponse createUnitIt(AdUnitItRequest request) throws AdException;

    AdUnitDistrictResponse createUnitDistrict(AdUnitDistrictRequest request) throws AdException;
}

Service 实现类:在实现方法之前,需要构造一个通用方法对输入的 ids 进行第一步校验。

@Service
public class AdUnitServiceImpl implements IAdUnitService {

    private final AdPlanRepository planRepository;
    private final AdUnitRepository unitRepository;

    private final AdUnitKeyWordRepository unitKeyWordRepository;
    private final AdUnitItRepository unitItRepository;
    private final AdUnitDistrictRepository unitDistrictRepository;

    @Autowired
    public AdUnitServiceImpl(AdPlanRepository planRepository, AdUnitRepository unitRepository,
                             AdUnitKeyWordRepository unitKeyWordRepository, AdUnitItRepository unitItRepository,
                             AdUnitDistrictRepository unitDistrictRepository) {
        this.planRepository = planRepository;
        this.unitRepository = unitRepository;
        this.unitKeyWordRepository = unitKeyWordRepository;
        this.unitItRepository = unitItRepository;
        this.unitDistrictRepository = unitDistrictRepository;
    }

    @Override
    public AdUnitResponse createUnit(AdUnitRequest request) throws AdException {

        if (!request.createValidate()){
            throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
        }
        Optional<AdPlan> adPlan = planRepository.findById(request.getPlanId());
        if (!adPlan.isPresent()){
            throw new AdException(Constants.ErrorMsg.CAN_NOT_FIND_RECORD);
        }
        AdUnit oldAdUnit = unitRepository.findByPlanIdAndUnitName(
                request.getPlanId(),request.getUnitName()
        );
        if (oldAdUnit != null){
            throw new AdException(Constants.ErrorMsg.SAME_NAME_UNIT_ERROR);
        }
        AdUnit newAdUnit = unitRepository.save(
                new AdUnit(request.getPlanId(),request.getUnitName(),request.getPositionType(),request.getBudget())
        );
        return new AdUnitResponse(newAdUnit.getId(),newAdUnit.getUnitName());
    }

    @Override
    public AdUnitKeywordResponse createUnitKeyword(AdUnitKeywordRequest request) throws AdException {
        List<Long> unitIds = request.getUnitKeywords().stream().map(AdUnitKeywordRequest.UnitKeyword::getUnitId).collect(Collectors.toList());
        if (!isRelatedUnitExist(unitIds)){
            throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
        }
        List<Long> ids = Collections.emptyList();
        List<AdUnitKeyword> unitKeywords = new ArrayList<>();
        if (!CollectionUtils.isEmpty(request.getUnitKeywords())){
            request.getUnitKeywords().forEach(i -> unitKeywords.add(
                    new AdUnitKeyword(i.getUnitId(),i.getKeyword())
            ));
            ids = unitKeyWordRepository.saveAll(unitKeywords).stream().map(AdUnitKeyword::getId).collect(Collectors.toList());
        }
        return new AdUnitKeywordResponse(ids);
    }

    @Override
    public AdUnitItResponse createUnitIt(AdUnitItRequest request) throws AdException {
        List<Long> unitIds = request.getUnitIts().stream().map(AdUnitItRequest.UnitIt::getUnitId).collect(Collectors.toList());
        if (!isRelatedUnitExist(unitIds)){
            throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
        }
        List<AdUnitIt> unitIts = new ArrayList<>();
        request.getUnitIts().forEach(i -> unitIts.add(
                new AdUnitIt(i.getUnitId(),i.getItTag())
        ));
        List<Long> ids = unitItRepository.saveAll(unitIts).stream().map(AdUnitIt::getId).collect(Collectors.toList());
        return new AdUnitItResponse(ids);
    }

    @Override
    public AdUnitDistrictResponse createUnitDistrict(AdUnitDistrictRequest request) throws AdException {
        List<Long> unitIds = request.getUnitDistricts().stream().map(AdUnitDistrictRequest.UnitDistrict::getUnitId).collect(Collectors.toList());
        if (!isRelatedUnitExist(unitIds)){
            throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
        }
        List<AdUnitDistrict> unitDistricts = new ArrayList<>();
        request.getUnitDistricts().forEach(d -> unitDistricts.add(
                new AdUnitDistrict(d.getUnitId(),d.getProvince(),d.getCity())
        ));
        List<Long> ids = unitDistrictRepository.saveAll(unitDistricts).stream().map(AdUnitDistrict::getId).collect(Collectors.toList());
        return new AdUnitDistrictResponse(ids);
    }

    /**
     * 对输入的 ID 进行校验
     * @param unitIds
     * @return
     */
    private boolean isRelatedUnitExist(List<Long> unitIds){
        if (CollectionUtils.isEmpty(unitIds)){
            return false;
        }
        return unitRepository.findAllById(unitIds).size() == new HashSet<>(unitIds).size();
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值