微服务配置中心 Apollo 解析——Portal 创建 Item
总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获
}
hasContent
#hasContent() 方法,判断是否有变化。当且仅当有变化才记录 Commit。代码如下:
public boolean hasContent() {
return !createItems.isEmpty() || !updateItems.isEmpty() || !deleteItems.isEmpty();
}
build
#build() 方法,构建 Item 变化的 JSON 字符串。代码如下:
public String build() {
// 因为事务第一段提交并没有更新时间,所以build时统一更新
Date now = new Date();
for (Item item : createItems) {
item.setDataChangeLastModifiedTime(now);
}
for (ItemPair item : updateItems) {
item.newItem.setDataChangeLastModifiedTime(now);
}
for (Item item : deleteItems) {
item.setDataChangeLastModifiedTime(now);
}
// JSON 格式化成字符串
return gson.toJson(this);
}
- 例子如下:
// 已经使用 http://tool.oschina.net/codeformat/json/ 进行格式化,实际是**紧凑型**
{
“createItems”: [ ],
“updateItems”: [
{
“oldItem”: {
“namespaceId”: 32,
“key”: “key4”,
“value”: “value4123”,
“comment”: “123”,
“lineNum”: 4,
“id”: 15,
“isDeleted”: false,
“dataChangeCreatedBy”: “apollo”,
“dataChangeCreatedTime”: “2018-04-27 16:49:59”,
“dataChangeLastModifiedBy”: “apollo”,
“dataChangeLastModifiedTime”: “2018-04-27 22:37:52”
},
“newItem”: {
“namespaceId”: 32,
“key”: “key4”,
“value”: “value41234”,
“comment”: “123”,
“lineNum”: 4,
“id”: 15,
“isDeleted”: false,
“dataChangeCreatedBy”: “apollo”,
“dataChangeCreatedTime”: “2018-04-27 16:49:59”,
“dataChangeLastModifiedBy”: “apollo”,
“dataChangeLastModifiedTime”: “2018-04-27 22:38:58”
}
}
],
“deleteItems”: [ ]
}
3. Portal 侧
================
3.1 ItemController
在 apollo-portal 项目中,
com.ctrip.framework.apollo.portal.controller.ItemController,提供 Item 的 API 。
在【添加配置项】的界面中,点击【提交】按钮,调用创建 Item 的 API 。
添加配置项
#createItem(appId, env, clusterName, namespaceName, ItemDTO) 方法,创建 Item 对象。代码如下:
1: @RestController
2: public class ItemController {
3:
4: @Autowired
5: private ItemService configService;
6: @Autowired
7: private UserInfoHolder userInfoHolder;
8:
9: @PreAuthorize(value = “@permissionValidator.hasModifyNamespacePermission(#appId, #namespaceName)”)
10: @RequestMapping(value = “/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/item”, method = RequestMethod.POST)
11: public ItemDTO createItem(@PathVariable String appId, @PathVariable String env,
12: @PathVariable String clusterName, @PathVariable String namespaceName,
13: @RequestBody ItemDTO item) {
14: // 校验 Item 格式正确
15: checkModel(isValidItem(item));
16: // protect
17: item.setLineNum(0);
18: item.setId(0);
19: // 设置 ItemDTO 的创建和修改人为当前管理员
20: String userId = userInfoHolder.getUser().getUserId();
21: item.setDataChangeCreatedBy(userId);
22: item.setDataChangeLastModifiedBy(userId);
23: // protect
24: item.setDataChangeCreatedTime(null);
25: item.setDataChangeLastModifiedTime(null);
26: // 保存 Item 到 Admin Service
27: return configService.createItem(appId, Env.valueOf(env), clusterName, namespaceName, item);
28: }
29:
30: // … 省略 deleteCluster 接口
31: }
-
POST `/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/item` 接口,Request Body 传递 JSON 对象。
-
@PreAuthorize(…) 注解,调用 PermissionValidator#hasModifyNamespacePermission(appId, namespaceName) 方法,校验是否有修改 Namespace 的权限。后续文章,详细分享。
-
com.ctrip.framework.apollo.common.dto.ItemDTO ,Item DTO 。代码如下:
public class ItemDTO extends BaseDTO {
/**
* Item 编号
*/
private long id;
/**
* Namespace 编号
*/
private long namespaceId;
/**
* 键
*/
private String key;
/**
* 值
*/
private String value;
/**
* 备注
*/
private String comment;
/**
* 行数
*/
private int lineNum;
}
- 第 14 行:调用 #isValidItem(ItemDTO) 方法,校验 Item 格式正确。代码如下:
private boolean isValidItem(ItemDTO item) {
return Objects.nonNull(item) // 非空
&& !StringUtils.isContainEmpty(item.getKey()); // 键非空
}
-
第 16 至 18 行 && 第 23 至 25 行:防御性编程,这几个参数不需要从 Portal 传递。
-
第 19 至 22 行:设置 ItemDTO 的创建和修改人为当前管理员。
-
第 27 行:调用 ConfigService#createItem(appId, Env, clusterName, namespaceName, ItemDTO) 方法,保存 Item 到 Admin Service 中。
3.2 ItemService
在 apollo-portal 项目中,
com.ctrip.framework.apollo.portal.service.ItemService ,提供 Item 的 Service 逻辑。
#createItem(appId, env, clusterName, namespaceName, ItemDTO) 方法,创建并保存 Item 到 Admin Service 。代码如下:
1: @Autowired
2: private AdminServiceAPI.NamespaceAPI namespaceAPI;
3: @Autowired
4: private AdminServiceAPI.ItemAPI itemAPI;
5:
6: public ItemDTO createItem(String appId, Env env, String clusterName, String namespaceName, ItemDTO item) {
7: // 校验 NamespaceDTO 是否存在。若不存在,抛出 BadRequestException 异常
8: NamespaceDTO namespace = namespaceAPI.loadNamespace(appId, env, clusterName, namespaceName);
9: if (namespace == null) {
10: throw new BadRequestException(“namespace:” + namespaceName + " not exist in env:" + env + “, cluster:” + clusterName);
11: }
12: // 设置 ItemDTO 的 `namespaceId`
13: item.setNamespaceId(namespace.getId());
14: // 保存 Item 到 Admin Service
15: ItemDTO itemDTO = itemAPI.createItem(appId, env, clusterName, namespaceName, item);
16: // 【TODO 6001】Tracer 日志
17: Tracer.logEvent(TracerEventType.MODIFY_NAMESPACE, String.format(“%s+%s+%s+%s”, appId, env, clusterName, namespaceName));
18: return itemDTO;
19: }
-
第 7 至11 行:调用 NamespaceAPI#loadNamespace(appId, Env, clusterName, namespaceName) 方法,校验 Namespace 是否存在。若不存在,抛出 BadRequestException 异常。注意,此处是远程调用 Admin Service 的 API 。
-
第 12 行:设置 ItemDTO 的 namespaceId 。
-
第 15 行:调用 NamespaceAPI#createItem(appId, Env, clusterName, namespaceName, ItemDTO) 方法,保存 Item 到 Admin Service 。
-
第 17 行:【TODO 6001】Tracer 日志
3.3 ItemAPI
com.ctrip.framework.apollo.portal.api.ItemAPI ,实现 API 抽象类,封装对 Admin Service 的 Item 模块的 API 调用。代码如下:
ItemAPI
4. Admin Service 侧
=======================
4.1 ItemController
在 apollo-adminservice 项目中,
com.ctrip.framework.apollo.adminservice.controller.ItemController ,提供 Item 的 API 。
#create(appId, clusterName, namespaceName, ItemDTO) 方法,创建 Item ,并记录 Commit 。代码如下:
1: @RestController
2: public class ItemController {
3:
4: @Autowired
5: private ItemService itemService;
6: @Autowired
7: private CommitService commitService;
8:
9: @PreAcquireNamespaceLock
10: @RequestMapping(path = “/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items”, method = RequestMethod.POST)
11: public ItemDTO create(@PathVariable(“appId”) String appId,
12: @PathVariable(“clusterName”) String clusterName,
13: @PathVariable(“namespaceName”) String namespaceName,
14: @RequestBody ItemDTO dto) {
15: // 将 ItemDTO 转换成 Item 对象
16: Item entity = BeanUtils.transfrom(Item.class, dto);
17: // 创建 ConfigChangeContentBuilder 对象
18: ConfigChangeContentBuilder builder = new ConfigChangeContentBuilder();
19: // 校验对应的 Item 是否已经存在。若是,抛出 BadRequestException 异常。
20: Item managedEntity = itemService.findOne(appId, clusterName, namespaceName, entity.getKey());
21: if (managedEntity != null) {
22: throw new BadRequestException(“item already exist”);
23: } else {
24: // 保存 Item 对象
25: entity = itemService.save(entity);
26: // 添加到 ConfigChangeContentBuilder 中
27: builder.createItem(entity);
28: }
29: // 将 Item 转换成 ItemDTO 对象
30: dto = BeanUtils.transfrom(ItemDTO.class, entity);
31: // 创建 Commit 对象
32: Commit commit = new Commit();
33: commit.setAppId(appId);
34: commit.setClusterName(clusterName);
35: commit.setNamespaceName(namespaceName);
36: commit.setChangeSets(builder.build()); // ConfigChangeContentBuilder 构造变更
37: commit.setDataChangeCreatedBy(dto.getDataChangeLastModifiedBy());
38: commit.setDataChangeLastModifiedBy(dto.getDataChangeLastModifiedBy());
39: // 保存 Commit 对象
40: commitService.save(commit);
41: return dto;
42: }
43:
44: // … 省略其他接口和属性
45: }
-
POST `/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items` 接口,Request Body 传递 JSON 对象。
-
第 16 行:调用 BeanUtils#transfrom(Class clazz, Object src) 方法,将 ItemDTO 转换成 Item 对象。
-
第 18 行:创建 ConfigChangeContentBuilder 对象。
-
第 19 至 22 行:调用 ItemService#findOne(appId, clusterName, namespaceName, key) 方法,校验对应的 Item 是否已经存在。若是,抛出 BadRequestException 异常。
-
第 25 行:调用 ItemService#save(Item) 方法,保存 Item 对象。
-
第 27 行:调用 ConfigChangeContentBuilder#createItem(Item) 方法,添加到 ConfigChangeContentBuilder 中。
-
第 30 行:调用 BeanUtils#transfrom(Class clazz, Object src) 方法,将 Item 转换成 ItemDTO 对象。
-
第 31 至 38 行:创建 Commit 对象。
-
第 40 行:调用 CommitService#save(Commit) 方法,保存 Commit 对象。
4.2 ItemService
在 apollo-biz 项目中,
com.ctrip.framework.apollo.biz.service.ItemService ,提供 Item 的 Service 逻辑给 Admin Service 和 Config Service 。
#save(Item) 方法,保存 Item 对象 。代码如下:
1: @Autowired
2: private ItemRepository itemRepository;
3: @Autowired
4: private AuditService auditService;
5:
6: @Transactional
7: public Item save(Item entity) {
8: // 校验 Key 长度
9: checkItemKeyLength(entity.getKey());
10: // 校验 Value 长度
11: checkItemValueLength(entity.getNamespaceId(), entity.getValue());
12: // protection
13: entity.setId(0);
14: // 设置 Item 的行号,以 Namespace 下的 Item 最大行号 + 1 。
15: if (entity.getLineNum() == 0) {
16: Item lastItem = findLastOne(entity.getNamespaceId());
17: int lineNum = lastItem == null ? 1 : lastItem.getLineNum() + 1;
18: entity.setLineNum(lineNum);
19: }
20: // 保存 Item
21: Item item = itemRepository.save(entity);
22: // 记录 Audit 到数据库中
23: auditService.audit(Item.class.getSimpleName(), item.getId(), Audit.OP.INSERT, item.getDataChangeCreatedBy());
24: return item;
25: }
-
第 9 行:调用 #checkItemKeyLength(key) 方法,校验 Key 长度。
-
可配置 “item.value.length.limit” 在 ServerConfig 配置最大长度。
-
默认最大长度为 128 。
-
第 11 行:调用 #checkItemValueLength(namespaceId, value) 方法,校验 Value 长度。
-
全局可配置 “item.value.length.limit” 在 ServerConfig 配置最大长度。
-
自定义配置 “namespace.value.length.limit.override” 在 ServerConfig 配置最大长度。
-
默认最大长度为 20000 。
-
第 14 至 19 行:设置 Item 的行号,以 Namespace 下的 Item 最大行号 + 1 。#findLastOne(namespaceId) 方法,获得最大行号的 Item 对象,代码如下:
public Item findLastOne(long namespaceId) {
return itemRepository.findFirst1ByNamespaceIdOrderByLineNumDesc(namespaceId);
}
-
第 21 行:调用 ItemRepository#save(Item) 方法,保存 Item 。
-
第 23 行:记录 Audit 到数据库中
4.3 ItemRepository
com.ctrip.framework.apollo.biz.repository.ItemRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 Item 的数据访问 给 Admin Service 和 Config Service 。代码如下:
public interface ItemRepository extends PagingAndSortingRepository<Item, Long> {
Item findByNamespaceIdAndKey(Long namespaceId, String key);
List findByNamespaceIdOrderByLineNumAsc(Long namespaceId);
List findByNamespaceId(Long namespaceId);
List findByNamespaceIdAndDataChangeLastModifiedTimeGreaterThan(Long namespaceId, Date date);
Item findFirst1ByNamespaceIdOrderByLineNumDesc(Long namespaceId);
@Modifying
@Query(“update Item set isdeleted=1,DataChange_LastModifiedBy = ?2 where namespaceId = ?1”)
int deleteByNamespaceId(long namespaceId, String operator);
}
4.4 CommitService
在 apollo-biz 项目中,
com.ctrip.framework.apollo.biz.service.CommitService ,提供 Commit 的 Service 逻辑给 Admin Service 和 Config Service 。
#save(Commit) 方法,保存 Item 对象 。代码如下:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
总结
总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。
如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
(img-xBqKLQn1-1712921837785)]
[外链图片转存中…(img-wBYqip0q-1712921837786)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
总结
总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。
如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。
[外链图片转存中…(img-fmjTyfP4-1712921837786)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
更多推荐
所有评论(0)