點擊上方“芋道源碼”,選擇“設爲星標”
做積極的人,而不是積極廢人!
源碼精品專欄
摘要: 原創出處 http://www.iocoder.cn/Apollo/portal-publish/ 「芋道源碼」歡迎轉載,保留摘要,謝謝!
1. 概述
2. 實體
3. Portal 側
4. Admin Service 側
666. 彩蛋
1. 概述
老艿艿:本系列假定胖友已經閱讀過 《Apollo 官方 wiki 文檔》 。
從本文開始,我們進入 Apollo 最最最核心的流程 配置發佈後的實時推送設計 。
在配置中心中,一個重要的功能就是配置發佈後實時推送到客戶端。下面我們簡要看一下這塊是怎麼設計實現的。
配置發佈 上圖簡要描述了配置發佈的大致過程:
用戶在 Portal 操作配置發佈
Portal 調用 Admin Service 的接口操作發佈
Admin Service 發佈配置後,發送 ReleaseMessage 給各個Config Service
Config Service 收到 ReleaseMessage 後,通知對應的客戶端
本文分享 Portal 發佈配置,對應上述第一、二步,大體流程如下:
???? 這個流程過程中,我們先不考慮灰度發佈,會涉及配置合併的過程。
老艿艿:因爲 Portal 是管理後臺,所以從代碼實現上,和業務系統非常相像。也因此,本文會略顯囉嗦。
2. 實體
2.1 Release
com.ctrip.framework.apollo.biz.entity.Release
,繼承 BaseEntity 抽象類,Release 實體。代碼如下:
@Entity
@Table(name = "Release")
@SQLDelete(sql = "Update Release set isDeleted = 1 where id = ?") // 標記刪除
@Where(clause = "isDeleted = 0")
public class Release extends BaseEntity {
/**
* Release Key
*
* 【TODO 6006】用途?
*/
@Column(name = "ReleaseKey", nullable = false)
private String releaseKey;
/**
* 標題
*/
@Column(name = "Name", nullable = false)
private String name;
/**
* 備註
*/
@Column(name = "Comment", nullable = false)
private String comment;
/**
* App 編號
*/
@Column(name = "AppId", nullable = false)
private String appId;
/**
* Cluster 名字
*/
@Column(name = "ClusterName", nullable = false)
private String clusterName;
/**
* Namespace 名字
*/
@Column(name = "NamespaceName", nullable = false)
private String namespaceName;
/**
* 配置 Map 字符串,使用 JSON 格式化成字符串
*/
@Column(name = "Configurations", nullable = false)
@Lob
private String configurations;
/**
* 是否被回滾(放棄)
*/
@Column(name = "IsAbandoned", columnDefinition = "Bit default '0'")
private boolean isAbandoned;
}
releaseKey
字段,【TODO 6006】用途?name
字段,發佈標題。comment
字段,發佈備註。appId
+clusterName
+namespaceName
字段,指向對應的 Namespace 記錄。configurations
字段,發佈時的完整配置 Map 字符串,使用 JSON 格式化成字符串。x
和
Commit.changeSets
字段,格式一致,只是它是變化配置 Map 字符串。例子如下:
{"huidu01":"huidu01"}
isAbandoned
字段,是否被回滾(放棄)。
2.2 ReleaseHistory
com.ctrip.framework.apollo.biz.entity.ReleaseHistory
,繼承 BaseEntity 抽象類,ReleaseHistory 實體,記錄每次 Release 相關的操作日誌。代碼如下:
@Entity
@Table(name = "ReleaseHistory")
@SQLDelete(sql = "Update ReleaseHistory set isDeleted = 1 where id = ?") // 標記刪除
@Where(clause = "isDeleted = 0")
public class ReleaseHistory extends BaseEntity {
/**
* App 編號
*/
@Column(name = "AppId", nullable = false)
private String appId;
/**
* Cluster 名字
*/
@Column(name = "ClusterName", nullable = false)
private String clusterName;
/**
* Namespace 名字
*/
@Column(name = "NamespaceName", nullable = false)
private String namespaceName;
/**
* Branch 名
*
* 主幹,使用 Cluster 名字
* 分支,使用子 Cluster 名字
*/
@Column(name = "BranchName", nullable = false)
private String branchName;
/**
* Release 編號
*/
@Column(name = "ReleaseId")
private long releaseId;
/**
* 上一次 Release 編號
*/
@Column(name = "PreviousReleaseId")
private long previousReleaseId;
/**
* 操作類型 {@link com.ctrip.framework.apollo.common.constants.ReleaseOperation}
*/
@Column(name = "Operation")
private int operation;
/**
* 操作 Context
*/
@Column(name = "OperationContext", nullable = false)
private String operationContext;
}
appId
+clusterName
+namespaceName
字段,指向對應的 Namespace 記錄。branchName
字段,Branch 名字。主幹,使用 Cluster 名字。
分支,使用子 Cluster 名字。
releaseId
字段,Release 編號。previousReleaseId
字段,上一次 Release 編號。operation
類型,操作類型。在com.ctrip.framework.apollo.common.constants.ReleaseOperation
類中,枚舉了所有發佈相關的操作類型。代碼如下:public interface ReleaseOperation { int NORMAL_RELEASE = 0; // 主幹發佈 int ROLLBACK = 1; // 回滾 int GRAY_RELEASE = 2; // 灰度發佈 int APPLY_GRAY_RULES = 3; // int GRAY_RELEASE_MERGE_TO_MASTER = 4; int MASTER_NORMAL_RELEASE_MERGE_TO_GRAY = 5; int MATER_ROLLBACK_MERGE_TO_GRAY = 6; int ABANDON_GRAY_RELEASE = 7; int GRAY_RELEASE_DELETED_AFTER_MERGE = 8; }
operationContext
字段,操作 Context 。
2.3 ReleaseMessage
下一篇文章,詳細分享。
3. Portal 側
3.1 ReleaseController
在 apollo-portal
項目中,com.ctrip.framework.apollo.portal.controller.ReleaseController
,提供 Release 的 API 。
在【發佈】的界面中,點擊【 發佈 】按鈕,調用發佈配置的 API 。
#createRelease(appId, env, clusterName, namespaceName, NamespaceReleaseModel)
方法,發佈配置。代碼如下:
1: @Autowired
2: private ReleaseService releaseService;
3: @Autowired
4: private ApplicationEventPublisher publisher;
5: @Autowired
6: private PortalConfig portalConfig;
7:
8: @PreAuthorize(value = "@permissionValidator.hasReleaseNamespacePermission(#appId, #namespaceName)")
9: @RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
10: public ReleaseDTO createRelease(@PathVariable String appId,
11: @PathVariable String env, @PathVariable String clusterName,
12: @PathVariable String namespaceName, @RequestBody NamespaceReleaseModel model) {
13: // 校驗 NamespaceReleaseModel 非空
14: checkModel(Objects.nonNull(model));
15: // 設置 PathVariable 變量到 NamespaceReleaseModel 中
16: model.setAppId(appId);
17: model.setEnv(env);
18: model.setClusterName(clusterName);
19: model.setNamespaceName(namespaceName);
20: // 若是緊急發佈,但是當前環境未允許該操作,拋出 BadRequestException 異常
21: if (model.isEmergencyPublish() && !portalConfig.isEmergencyPublishAllowed(Env.valueOf(env))) {
22: throw new BadRequestException(String.format("Env: %s is not supported emergency publish now", env));
23: }
24: // 發佈配置
25: ReleaseDTO createdRelease = releaseService.publish(model);
26:
27: // 創建 ConfigPublishEvent 對象
28: ConfigPublishEvent event = ConfigPublishEvent.instance();
29: event.withAppId(appId)
30: .withCluster(clusterName)
31: .withNamespace(namespaceName)
32: .withReleaseId(createdRelease.getId())
33: .setNormalPublishEvent(true)
34: .setEnv(Env.valueOf(env));
35: // 發佈 ConfigPublishEvent 事件
36: publisher.publishEvent(event);
37:
38: return createdRelease;
39: }
POST
/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases
接口,Request Body 傳遞 JSON 對象。@PreAuthorize(...)
註解,調用PermissionValidator#hasReleaseNamespacePermissio(appId, namespaceName)
方法,校驗是否有發佈配置的權限。後續文章,詳細分享。com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel
,Namespace 配置發佈 Model 。代碼如下:public class NamespaceReleaseModel implements Verifiable { /** * App 編號 */ private String appId; /** * Env 名字 */ private String env; /** * Cluster 名字 */ private String clusterName; /** * Namespace 名字 */ private String namespaceName; /** * 發佈標題 */ private String releaseTitle; /** * 發佈描述 */ private String releaseComment; /** * 發佈人 */ private String releasedBy; /** * 是否緊急發佈 */ private boolean isEmergencyPublish; @Override public boolean isInvalid() { return StringUtils.isContainEmpty(appId, env, clusterName, namespaceName, releaseTitle); // 校驗非空 } }
第 14 行:校驗 NamespaceReleaseModel 非空。
第 15 至 19 行:設置 PathVariable 變量到 NamespaceReleaseModel 中。
第 20 至 23 行:校驗若是緊急發佈,但是當前環境未允許該操作,拋出 BadRequestException 異常。
緊急發佈功能,可通過設置 PortalDB 的 ServerConfig 的
"emergencyPublish.supported.envs"
配置開啓對應的 Env 們。例如,emergencyPublish.supported.envs = dev
。第 25 行:調用
ReleaseService#publish(NamespaceReleaseModel)
方法,調用 Admin Service API ,發佈配置。第 27 至 36 行:創建 ConfigPublishEvent 對象,並調用
ApplicationEventPublisher#publishEvent(event)
方法,發佈 ConfigPublishEvent 事件。這部分,我們在後續文章分享。第 38 行:返回 ReleaseDTO 對象。
3.2 ReleaseService
在 apollo-portal
項目中,com.ctrip.framework.apollo.portal.service.ReleaseService
,提供 Release 的 Service 邏輯。
#publish(NamespaceReleaseModel)
方法,調用 Admin Service API ,發佈配置。代碼如下:
1: @Autowired
2: private UserInfoHolder userInfoHolder;
3: @Autowired
4: private AdminServiceAPI.ReleaseAPI releaseAPI;
5:
6: public ReleaseDTO publish(NamespaceReleaseModel model) {
7: Env env = model.getEnv();
8: boolean isEmergencyPublish = model.isEmergencyPublish();
9: String appId = model.getAppId();
10: String clusterName = model.getClusterName();
11: String namespaceName = model.getNamespaceName();
12: String releaseBy = StringUtils.isEmpty(model.getReleasedBy()) ? userInfoHolder.getUser().getUserId() : model.getReleasedBy();
13:
14: // 調用 Admin Service API ,發佈 Namespace 的配置。
15: ReleaseDTO releaseDTO = releaseAPI.createRelease(appId, env, clusterName, namespaceName,
16: model.getReleaseTitle(), model.getReleaseComment(),
17: releaseBy, isEmergencyPublish);
18: // 【TODO 6001】Tracer 日誌
19: Tracer.logEvent(TracerEventType.RELEASE_NAMESPACE, String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
20: return releaseDTO;
21: }
第14 至 17 行:調用
ReleaseAPI#createRelease(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, releaseBy, isEmergencyPublish)
方法,調用 Admin Service API ,發佈配置。第 19 行:【TODO 6001】Tracer 日誌
3.3 ReleaseAPI
com.ctrip.framework.apollo.portal.api.ReleaseAPI
,實現 API 抽象類,封裝對 Admin Service 的 Release 模塊的 API 調用。代碼如下:
4. Admin Service 側
4.1 ReleaseController
在 apollo-adminservice
項目中, com.ctrip.framework.apollo.adminservice.controller.ReleaseController
,提供 Release 的 API 。
#publish(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, releaseBy, isEmergencyPublish)
方法,發佈 Namespace 的配置。代碼如下:
1: @Autowired
2: private ReleaseService releaseService;
3: @Autowired
4: private NamespaceService namespaceService;
5: @Autowired
6: private MessageSender messageSender;
7:
8: @Transactional
9: @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
10: public ReleaseDTO publish(@PathVariable("appId") String appId,
11: @PathVariable("clusterName") String clusterName,
12: @PathVariable("namespaceName") String namespaceName,
13: @RequestParam("name") String releaseName,
14: @RequestParam(name = "comment", required = false) String releaseComment,
15: @RequestParam("operator") String operator,
16: @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish) {
17: // 校驗對應的 Namespace 對象是否存在。若不存在,拋出 NotFoundException 異常
18: Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
19: if (namespace == null) {
20: throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId, clusterName, namespaceName));
21: }
22: // 發佈 Namespace 的配置
23: Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish);
24:
25: // send release message
26: // 獲得 Cluster 名
27: Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
28: String messageCluster;
29: if (parentNamespace != null) { // 灰度發佈
30: messageCluster = parentNamespace.getClusterName();
31: } else {
32: messageCluster = clusterName; // 使用請求的 ClusterName
33: }
34: // 發送 Release 消息
35: messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName), Topics.APOLLO_RELEASE_TOPIC);
36:
37: // 將 Release 轉換成 ReleaseDTO 對象
38: return BeanUtils.transfrom(ReleaseDTO.class, release);
39: }
POST
/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases
接口,Request Body 傳遞 JSON 對象。第 17 至 21 行:校驗對應的 Namespace 對象是否存在。若不存在,拋出 NotFoundException 異常。
第 23 行:調用
ReleaseService#publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish)
方法,發佈 Namespace 的配置,返回 Release 對象。第 26 至 33 行:獲得發佈消息的 Cluster 名字。
這塊胖友可以先跳過,等看完後面灰度發佈相關的內容,在回過頭理解。
第 27 行:調用
NamespaceService#findParentNamespace(namespace)
方法,獲得父 Namespace 對象。代碼如下:@Autowired private ClusterService clusterService; @Autowired private ClusterService clusterService; public Namespace findParentNamespace(Namespace namespace) { String appId = namespace.getAppId(); String namespaceName = namespace.getNamespaceName(); // 獲得 Cluster Cluster cluster = clusterService.findOne(appId, namespace.getClusterName()); // 若爲子 Cluster if (cluster != null && cluster.getParentClusterId() > 0) { // 獲得父 Cluster Cluster parentCluster = clusterService.findOne(cluster.getParentClusterId()); // 獲得父 Namespace return findOne(appId, parentCluster.getName(), namespaceName); } return null; } public Namespace findOne(String appId, String clusterName, String namespaceName) { return namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName, namespaceName); }
第 29 至 30 行:若有父 Namespace 對象,說明是子 Namespace ( 灰度發佈 ),則使用父 Namespace 的 Cluster 名字。因爲,客戶端即使在灰度發佈的情況下,也是使用 父 Namespace 的 Cluster 名字。也就說,灰度發佈,對客戶端是透明無感知的。
第 32 行:使用請求的 Cluster 名字。
第 35 行:調用
MessageSender#sendMessage(String message, String channel)
方法,發送發佈消息。詳細實現,下一篇文章詳細解析。第 38 行:調用
BeanUtils#transfrom(Class<T> clazz, Object src)
方法,將 Release 轉換成 ReleaseDTO 對象。
4.2 ReleaseService
在 apollo-biz
項目中,com.ctrip.framework.apollo.biz.service.ReleaseService
,提供 Release 的 Service 邏輯給 Admin Service 和 Config Service 。
4.2.1 publish
#publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish)
方法,發佈 Namespace 的配置。代碼如下:
1: private Gson gson = new Gson();
2:
3: @Autowired
4: private ReleaseRepository releaseRepository;
5: @Autowired
6: private ItemService itemService;
7: @Autowired
8: private AuditService auditService;
9: @Autowired
10: private NamespaceLockService namespaceLockService;
11: @Autowired
12: private NamespaceService namespaceService;
13: @Autowired
14: private ReleaseHistoryService releaseHistoryService;
15:
16: @Transactional
17: public Release publish(Namespace namespace, String releaseName, String releaseComment, String operator, boolean isEmergencyPublish) {
18: // 校驗鎖定
19: checkLock(namespace, isEmergencyPublish, operator);
20: // 獲得 Namespace 的普通配置 Map
21: Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);
22: // 獲得父 Namespace
23: Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
24: // 若有父 Namespace ,則是子 Namespace ,進行灰度發佈
25: // branch release
26: if (parentNamespace != null) {
27: return publishBranchNamespace(parentNamespace, namespace, operateNamespaceItems, releaseName, releaseComment, operator, isEmergencyPublish);
28: }
29: // 獲得子 Namespace 對象
30: Namespace childNamespace = namespaceService.findChildNamespace(namespace);
31: // 獲得上一次,並且有效的 Release 對象
32: Release previousRelease = null;
33: if (childNamespace != null) {
34: previousRelease = findLatestActiveRelease(namespace);
35: }
36: // 創建操作 Context
37: // master release
38: Map<String, Object> operationContext = Maps.newHashMap();
39: operationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish);
40: // 主幹發佈
41: Release release = masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems, operator, ReleaseOperation.NORMAL_RELEASE, operationContext); // 是否緊急發佈。
42: // 若有子 Namespace 時,自動將主幹合併到子 Namespace ,並進行一次子 Namespace 的發佈
43: // merge to branch and auto release
44: if (childNamespace != null) {
45: mergeFromMasterAndPublishBranch(namespace, childNamespace, operateNamespaceItems,
46: releaseName, releaseComment, operator, previousRelease,
47: release, isEmergencyPublish);
48: }
49: return release;
50: }
第 19 行:調用
#checkLock(namespace, isEmergencyPublish, operator)
方法,校驗 NamespaceLock 鎖定。代碼如下:private void checkLock(Namespace namespace, boolean isEmergencyPublish, String operator) { if (!isEmergencyPublish) { // 非緊急發佈 // 獲得 NamespaceLock 對象 NamespaceLock lock = namespaceLockService.findLock(namespace.getId()); // 校驗鎖定人是否是當前管理員。若是,拋出 BadRequestException 異常 if (lock != null && lock.getDataChangeCreatedBy().equals(operator)) { throw new BadRequestException("Config can not be published by yourself."); } } }
第 21 行:調用
#getNamespaceItems(namespace)
方法,獲得 Namespace 的普通配置 Map 。代碼如下:private Map<String, String> getNamespaceItems(Namespace namespace) { // 讀取 Namespace 的 Item 集合 List<Item> items = itemService.findItemsWithoutOrdered(namespace.getId()); // 生成普通配置 Map 。過濾掉註釋和空行的配置項 Map<String, String> configurations = new HashMap<String, String>(); for (Item item : items) { if (StringUtils.isEmpty(item.getKey())) { continue; } configurations.put(item.getKey(), item.getValue()); } return configurations; }
第 23 行:調用
#findParentNamespace(namespace)
方法,獲得父 Namespace 對象。第 26 至 28 行:若有父 Namespace 對象,灰度發佈。詳細解析,見 《Apollo 源碼解析 —— Portal 灰度發佈》 。
第 30 行:調用
NamespaceService#findChildNamespace(namespace)
方法,獲得子 Namespace 對象。詳細解析,見 《Apollo 源碼解析 —— Portal 創建灰度》 。第 31 至 35 行:調用
#findLatestActiveRelease(Namespace)
方法,獲得上一次,並且有效的 Release 對象。代碼如下:public Release findLatestActiveRelease(Namespace namespace) { return findLatestActiveRelease(namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName()); } public Release findLatestActiveRelease(String appId, String clusterName, String namespaceName) { return releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(appId, clusterName, namespaceName); // IsAbandoned = False && Id DESC }
第 36 至 39 行:創建操作 Context 。
第 41 行:調用
#masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems, operator, releaseOperation, operationContext)
方法,主幹發佈配置。???? 創建的 Namespace ,默認就是主幹,而灰度發佈使用的是分支。第 42 至 48 行:調用
#mergeFromMasterAndPublishBranch(...)
方法,若有子 Namespace 時,自動將主幹合併到子 Namespace ,並進行一次子 Namespace 的發佈。第 49 行:返回 Release 對象。詳細解析,見 《Apollo 源碼解析 —— Portal 灰度發佈》 。
4.2.2 masterRelease
#masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems, operator, releaseOperation, operationContext)
方法,主幹發佈配置。代碼如下:
1: private Release masterRelease(Namespace namespace, String releaseName, String releaseComment,
2: Map<String, String> configurations, String operator,
3: int releaseOperation, Map<String, Object> operationContext) {
4: // 獲得最後有效的 Release 對象
5: Release lastActiveRelease = findLatestActiveRelease(namespace);
6: long previousReleaseId = lastActiveRelease == null ? 0 : lastActiveRelease.getId();
7: // 創建 Release 對象,並保存
8: Release release = createRelease(namespace, releaseName, releaseComment, configurations, operator);
9:
10: // 創建 ReleaseHistory 對象,並保存
11: releaseHistoryService.createReleaseHistory(namespace.getAppId(), namespace.getClusterName(),
12: namespace.getNamespaceName(), namespace.getClusterName(),
13: release.getId(), previousReleaseId, releaseOperation,
14: operationContext, operator);
15: return release;
16: }
第 5 行:調用
#findLatestActiveRelease(namespace)
方法,獲得最後、有效的 Release 對象。代碼如下:public Release findLatestActiveRelease(Namespace namespace) { return findLatestActiveRelease(namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName()); } public Release findLatestActiveRelease(String appId, String clusterName, String namespaceName) { return releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(appId, clusterName, namespaceName); // IsAbandoned = False && Id DESC }
第 8 行:調用
#createRelease(namespace, releaseName, releaseComment, configurations, operator)
方法,創建 Release 對象,並保存。第10 至 14 行:調用
ReleaseHistoryService#createReleaseHistory(appId, clusterName, namespaceName, branchName, releaseId, previousReleaseId, operation, operationContext, operator)
方法,創建 ReleaseHistory 對象,並保存。
4.2.3 createRelease
#createRelease(namespace, releaseName, releaseComment, configurations, operator)
方法,創建 Release 對象,並保存。代碼如下:
1: private Release createRelease(Namespace namespace, String name, String comment,
2: Map<String, String> configurations, String operator) {
3: // 創建 Release 對象
4: Release release = new Release();
5: release.setReleaseKey(ReleaseKeyGenerator.generateReleaseKey(namespace)); //【TODO 6006】Release Key 用途?
6: release.setDataChangeCreatedTime(new Date());
7: release.setDataChangeCreatedBy(operator);
8: release.setDataChangeLastModifiedBy(operator);
9: release.setName(name);
10: release.setComment(comment);
11: release.setAppId(namespace.getAppId());
12: release.setClusterName(namespace.getClusterName());
13: release.setNamespaceName(namespace.getNamespaceName());
14: release.setConfigurations(gson.toJson(configurations)); // 使用 Gson ,將配置 Map 格式化成字符串。
15: // 保存 Release 對象
16: release = releaseRepository.save(release);
17: // 釋放 NamespaceLock
18: namespaceLockService.unlock(namespace.getId());
19: // 記錄 Audit 到數據庫中
20: auditService.audit(Release.class.getSimpleName(), release.getId(), Audit.OP.INSERT, release.getDataChangeCreatedBy());
21: return release;
22: }
第 4 至 14 行:創建 Release 對象,並設置對應的屬性。
第 5 行:【TODO 6006】Release Key 用途?
第 14 行:調用
Gson#toJson(src)
方法,將配置 Map 格式化成字符串。第 16 行:調用
ReleaseRepository#save(Release)
方法,保存 Release 對象。第 18 行:調用
NamespaceLockService#unlock(namespaceId)
方法,釋放 NamespaceLock 。第 20 行:記錄 Audit 到數據庫中。
4.3 ReleaseRepository
com.ctrip.framework.apollo.biz.repository.ReleaseRepository
,繼承 org.springframework.data.repository.PagingAndSortingRepository
接口,提供 Release 的數據訪問 給 Admin Service 和 Config Service 。代碼如下:
public interface ReleaseRepository extends PagingAndSortingRepository<Release, Long> {
Release findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(@Param("appId") String appId, @Param("clusterName") String clusterName, @Param("namespaceName") String namespaceName);
Release findByIdAndIsAbandonedFalse(long id);
List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page);
List<Release> findByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page);
List<Release> findByReleaseKeyIn(Set<String> releaseKey);
List<Release> findByIdIn(Set<Long> releaseIds);
@Modifying
@Query("update Release set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3")
int batchDelete(String appId, String clusterName, String namespaceName, String operator);
// For release history conversion program, need to delete after conversion it done
List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdAsc(String appId, String clusterName, String namespaceName);
}
4.4 ReleaseHistoryService
在 apollo-biz
項目中,com.ctrip.framework.apollo.biz.service.ReleaseHistoryService
,提供 ReleaseHistory 的 Service 邏輯給 Admin Service 和 Config Service 。
#createReleaseHistory(appId, clusterName, namespaceName, branchName, releaseId, previousReleaseId, operation, operationContext, operator)
方法,創建 ReleaseHistory 對象,並保存。代碼如下:
1: private Gson gson = new Gson();
2:
3: @Autowired
4: private ReleaseHistoryRepository releaseHistoryRepository;
5: @Autowired
6: private AuditService auditService;
7:
8: @Transactional
9: public ReleaseHistory createReleaseHistory(String appId, String clusterName, String namespaceName, String branchName,
10: long releaseId, long previousReleaseId, int operation,
11: Map<String, Object> operationContext, String operator) {
12: // 創建 ReleaseHistory 對象
13: ReleaseHistory releaseHistory = new ReleaseHistory();
14: releaseHistory.setAppId(appId);
15: releaseHistory.setClusterName(clusterName);
16: releaseHistory.setNamespaceName(namespaceName);
17: releaseHistory.setBranchName(branchName);
18: releaseHistory.setReleaseId(releaseId); // Release 編號
19: releaseHistory.setPreviousReleaseId(previousReleaseId); // 上一個 Release 編號
20: releaseHistory.setOperation(operation);
21: if (operationContext == null) {
22: releaseHistory.setOperationContext("{}"); //default empty object
23: } else {
24: releaseHistory.setOperationContext(gson.toJson(operationContext));
25: }
26: releaseHistory.setDataChangeCreatedTime(new Date());
27: releaseHistory.setDataChangeCreatedBy(operator);
28: releaseHistory.setDataChangeLastModifiedBy(operator);
29: // 保存 ReleaseHistory 對象
30: releaseHistoryRepository.save(releaseHistory);
31: // 記錄 Audit 到數據庫中
32: auditService.audit(ReleaseHistory.class.getSimpleName(), releaseHistory.getId(), Audit.OP.INSERT, releaseHistory.getDataChangeCreatedBy());
33: return releaseHistory;
34: }
第 12 至 28 行:創建 ReleaseHistory 對象,並設置對應的屬性。
第 30 行:調用
ReleaseHistoryRepository#save(ReleaseHistory)
方法,保存 ReleaseHistory 對象。第 32 行:記錄 Audit 到數據庫中。
4.5 ReleaseHistoryRepository
com.ctrip.framework.apollo.biz.repository.ReleaseHistoryRepository
,繼承 org.springframework.data.repository.PagingAndSortingRepository
接口,提供 ReleaseHistory 的數據訪問 給 Admin Service 和 Config Service 。代碼如下:
public interface ReleaseHistoryRepository extends PagingAndSortingRepository<ReleaseHistory, Long> {
Page<ReleaseHistory> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String
clusterName, String namespaceName, Pageable pageable);
Page<ReleaseHistory> findByReleaseIdAndOperationOrderByIdDesc(long releaseId, int operation, Pageable pageable);
Page<ReleaseHistory> findByPreviousReleaseIdAndOperationOrderByIdDesc(long previousReleaseId, int operation, Pageable pageable);
@Modifying
@Query("update ReleaseHistory set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3")
int batchDelete(String appId, String clusterName, String namespaceName, String operator);
}
666. 彩蛋
T T 終於要到比較乾的地方啦。
歡迎加入我的知識星球,一起探討架構,交流源碼。加入方式,長按下方二維碼噢:
已在知識星球更新源碼解析如下:
最近更新《芋道 SpringBoot 2.X 入門》系列,已經 20 餘篇,覆蓋了 MyBatis、Redis、MongoDB、ES、分庫分表、讀寫分離、SpringMVC、Webflux、權限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能測試等等內容。
提供近 3W 行代碼的 SpringBoot 示例,以及超 4W 行代碼的電商微服務項目。
獲取方式:點“在看”,關注公衆號並回復 666 領取,更多內容陸續奉上。