广告投放模块 | 广告系统

577 阅读4分钟

简介

一个基于springcloud的分布式广告系统。

本文介绍ad-dao模块、ad-sponsor模块、ad-dump-data模块。

实体结构

展示广告系统实体(entity)的部分属性

广告主(user)

属性说明
id用户id

推广计划(plan)

属性说明
id计划id
user_id创建计划的用户id

推广单元(plan)

属性说明
id单元id
plan_id推广单元所属的推广计划

广告创意(creative)

属性说明
id创意id

创意与单元关系(creative_unit)

属性说明
id关系id
creative_id创意id
unit_id单元id

推广单元的关键词限制(unit_keyword)

属性说明
id关键词限制id
unit_id关键词限制所属推广单元id

推广单元的地域限制(unit_district)

属性说明
id地域限制id
unit_id地域限制所属推广单元id

推广单元的兴趣限制(unit_interest)

属性说明
id兴趣限制id
unit_id兴趣限制所属推广单元id

ad-dao

使用JPA实现。

因为ad-sponsorad-dump-data两个模块都需要访问DB,所以将访问DB的代码独立为一个模块,使用时用maven导入。

<dependency>
    <groupId>asia.daijizai.ad</groupId>
    <artifactId>ad-dao</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

JPA

JPA可以很easy的实现增、删、改和简单的查询,但是复杂查询不如用mybatis方便。

在JPA中:

  • 增:Repository.save(一个entity对象)
  • 改:先find到,拿到对象,再save(对象)
  • 删:find到,删除
  • 查:根据条件find到

鱼和熊掌兼得:同时使用 JPA 和 Mybatis | 徐靖峰|个人博客 (lexburner.github.io)

[Spring Boot] Spring Data JPA vs MyBatis | by Peter Lee | An Idea (by Ingenious Piece) | Medium

ad-sponsor

广告投放系统,实现对广告数据的存储。

以添加关键词限制unit_keyword为例。

controller

@Slf4j
@RestController
public class AdUnitOPController

@RestController = @Controller + @ResponseBody

@PostMapping("/create/unitKeyword")
public AdUnitKeywordResponse createUnitKeyword(@RequestBody AdUnitKeywordRequest request) throws AdException {
    log.info("ad-sponsor: createUnitKeyword -> {}", JSON.toJSONString(request));

    return adUnitService.createUnitKeyword(request);
}
  • @Responsebody注解表示该方法的返回的结果直接写入 HTTP 响应正文中,一般在异步获取数据时使用;
  • 在使用@RequestMapping后,返回值通常解析为跳转路径,加上@Responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP 响应正文中。例如,异步获取json数据,加上@Responsebody注解后,就会直接返回json数据。
  • @RequestBody注解则是将 HTTP 求正文插入方法中,使用适合的HttpMessageConverter将请求体写入某个对象。

详述 @ResponseBody 和 @RequestBody 注解的区别_CG国斌的博客-CSDN博客_@requestbody注解的作用

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdUnitKeywordRequest {

    private List<UnitKeyword> unitKeywords;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class UnitKeyword {

        private Long unitId;
        private String keyword;
    }
}

service

public AdUnitKeywordResponse createUnitKeyword(AdUnitKeywordRequest request) throws AdException
  1. 参数校验

创造一个关键词限制,需要依赖一个已经存在的推广单元,所以我们需要获取新建的关键词限制所依赖的所有推广单元id,即List<Long> unitIds,确保每一个unitId所对应的推广单元都是存在的。

List<Long> unitIds = request.getUnitKeywords().stream()
        .map(AdUnitKeywordRequest.UnitKeyword::getUnitId)
        .collect(Collectors.toList());
if (!isRelatedUnitExist(unitIds)) {
    throw new AdException(Constants.ErrorMsg.REQUEST_PARAM_ERROR);
}
private boolean isRelatedUnitExist(List<Long> unitIds) {

    if (CollectionUtils.isEmpty(unitIds)) {
        return false;
    }

    return unitRepository.findAllById(unitIds).size() == new HashSet<>(unitIds).size();
}
  1. 根据请求对象获取实体类(entity)

AdUnitKeywordRequest request -> List<AdUnitKeyword> unitKeywords

List<AdUnitKeyword> unitKeywords = new ArrayList<>();
request.getUnitKeywords().forEach(i -> unitKeywords.add(
        new AdUnitKeyword(i.getUnitId(), i.getKeyword())
));
  1. 将实体类对象添加到数据库中
List<Long> ids = unitKeywordRepository.saveAll(unitKeywords).stream()
        .map(AdUnitKeyword::getId)
        .collect(Collectors.toList());
  1. 返回响应
return new AdUnitKeywordResponse(ids);
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdUnitKeywordResponse {

    private List<Long> id;
}

ad-dump-data

这个微服务只干一件事,将数据库中的数据转存到自定义文件中,用于ad-search模块中全量索引的构建。

关键词限制(unit_keyword)为例,数据库中的每一张表都要进行相同的操作。

public class DConstant {
	public static final String DATA_ROOT_DIR = "E:/java-idea/ad/mysql_data/";

    // 各个表数据的存储文件名
	public static final String AD_UNIT_KEYWORD = "ad_unit_keyword.data";
}
dumpAdUnitKeywordTable(String.format("%s%s", DConstant.DATA_ROOT_DIR, DConstant.AD_UNIT_KEYWORD));
  1. 获取unit_keyword表中所有数据,得到List<AdUnitKeyword> unitKeywords
  2. List<AdUnitKeyword> unitKeywords -> List<AdUnitKeywordTable> unitKeywordTables
  3. List<AdUnitKeywordTable> unitKeywordTables序列化为JSON格式后存入文件中
private void dumpAdUnitKeywordTable(String fileName) {

    List<AdUnitKeyword> unitKeywords = keywordRepository.findAll();
    if (CollectionUtils.isEmpty(unitKeywords)) {
        return;
    }

    List<AdUnitKeywordTable> unitKeywordTables = new ArrayList<>();
    unitKeywords.forEach(k -> unitKeywordTables.add(
            new AdUnitKeywordTable(
                    k.getUnitId(),
                    k.getKeyword()
            )
    ));

    Path path = Paths.get(fileName);
    try (BufferedWriter writer = Files.newBufferedWriter(path)) {
        for (AdUnitKeywordTable unitKeywordTable : unitKeywordTables) {
            writer.write(JSON.toJSONString(unitKeywordTable));
            writer.newLine();
        }
        writer.close();
    } catch (IOException ex) {
        log.error("dumpAdUnitItTable error");
    }
}

ad_unit_keyword.data文件中数据:

{"keyword":"宝马","unitId":10}
{"keyword":"奥迪","unitId":10}
{"keyword":"大众","unitId":10}

数据路径

  1. 数据本来都待在DB中
  2. 用JPA把数据读到内存中AdUnitKeywordAdUnitKeyword类定义在ad-daoentity包下,类参数与DB字段对应)
  3. AdUnitKeyword转为AdUnitKeywordTableAdUnitKeywordTable中仅保留索引中需要的数据)
  4. AdUnitKeywordTable以JSON格式序列化到文件中
  5. 数据都到了文件中