简介
一个基于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-sponsor和ad-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
- 参数校验
创造一个关键词限制,需要依赖一个已经存在的推广单元,所以我们需要获取新建的关键词限制所依赖的所有推广单元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();
}
- 根据请求对象获取实体类(entity)
AdUnitKeywordRequest request -> List<AdUnitKeyword> unitKeywords
List<AdUnitKeyword> unitKeywords = new ArrayList<>();
request.getUnitKeywords().forEach(i -> unitKeywords.add(
new AdUnitKeyword(i.getUnitId(), i.getKeyword())
));
- 将实体类对象添加到数据库中
List<Long> ids = unitKeywordRepository.saveAll(unitKeywords).stream()
.map(AdUnitKeyword::getId)
.collect(Collectors.toList());
- 返回响应
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));
- 获取unit_keyword表中所有数据,得到
List<AdUnitKeyword> unitKeywords List<AdUnitKeyword> unitKeywords->List<AdUnitKeywordTable> unitKeywordTablesList<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}
数据路径
- 数据本来都待在DB中
- 用JPA把数据读到内存中
AdUnitKeyword(AdUnitKeyword类定义在ad-dao的entity包下,类参数与DB字段对应) AdUnitKeyword转为AdUnitKeywordTable(AdUnitKeywordTable中仅保留索引中需要的数据)- 将
AdUnitKeywordTable以JSON格式序列化到文件中 - 数据都到了文件中