此博客用于个人学习,来源于网上,对知识点进行一个整理。
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();
}
}