Apollo 源码解析 —— Portal 创建 Namespace

namespace命令空间 K8s支持多个虚拟集群,就是将一个物理集群虚拟成很多个虚拟集群。这些虚拟集群被称为命名空间。namespace是k8s集群级别的资源,可以给不同的用户,租户,环境或者项目创建对应的命名空间。例如,可以为test,devlopment,production环境分别创建格子的命名空间。在k8s中,大部分资源都可以打标签。类似别名,方便记忆。... 阅读详情

点击上方“芋道源码”,选择“设为星标”

做积极的人,而不是积极废人!

源码精品专栏

 

摘要: 原创出处 http://www.iocoder.cn/Apollo/portal-create-namespace/ 「芋道源码」欢迎转载,保留摘要,谢谢!

  • 1. 概述

  • 2. 实体

  • 3. Portal 侧

  • 4. Admin Service 侧

  • 666. 彩蛋


阅读源码最好的方式,是使用 IDEA 进行调试 Apollo 源码,不然会一脸懵逼。

胖友可以点击「芋道源码」扫码关注,回复 git018 关键字
获得艿艿添加了中文注释的 Apollo 源码地址。

阅读源码很孤单,加入源码交流群,一起坚持!

1. 概述

老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》  ,特别是 《Apollo 官方 wiki 文档 —— 核心概念之“Namespace”》 。

本文分享 Portal 创建 Namespace 的流程,整个过程涉及 Portal、Admin Service ,如下图所示:

流程

下面,我们先来看看 AppNamespace 和 Namespace 的实体结构

老艿艿:因为 Portal 是管理后台,所以从代码实现上,和业务系统非常相像。也因此,本文会略显啰嗦。

2. 实体

2.1 AppNamespace

在 apollo-common 项目中,com.ctrip.framework.apollo.common.entity.AppNamespace ,继承 BaseEntity 抽象类,App Namespace 实体。代码如下:

@Entity
@Table(name = "AppNamespace")
@SQLDelete(sql = "Update AppNamespace set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class AppNamespace extends BaseEntity {

    /**
     * AppNamespace 名
     */
    @Column(name = "Name", nullable = false)
    private String name;
    /**
     * App 编号
     */
    @Column(name = "AppId", nullable = false)
    private String appId;
    /**
     * 格式
     *
     * 参见 {@link ConfigFileFormat}
     */
    @Column(name = "Format", nullable = false)
    private String format;
    /**
     * 是否公用的
     */
    @Column(name = "IsPublic", columnDefinition = "Bit default '0'")
    private boolean isPublic = false;
    /**
     * 备注
     */
    @Column(name = "Comment")
    private String comment;
}
  • appId 字段,App 编号,指向对应的 App 。App : AppNamespace = 1 : N 。

  • format 字段,格式。在 com.ctrip.framework.apollo.core.enums.ConfigFileFormat 枚举类中,定义了五种类型。代码如下:

    public enum ConfigFileFormat {
    
        Properties("properties"), XML("xml"), JSON("json"), YML("yml"), YAML("yaml");
    
        private String value;
    
        // ... 省略了无关的代码
    }
    
  • isPublic 字段,是否公用的。

    Namespace的获取权限分为两种:

    这里的获取权限是相对于 Apollo 客户端来说的。

    • private (私有的):private 权限的 Namespace ,只能被所属的应用获取到。一个应用尝试获取其它应用 private 的 Namespace ,Apollo 会报 “404” 异常。

    • public (公共的):public 权限的 Namespace ,能被任何应用获取。

2.2 Namespace

在 apollo-biz 项目中, com.ctrip.framework.apollo.biz.entity.Namespace ,继承 BaseEntity 抽象类,Cluster Namespace 实体,是配置项的集合,类似于一个配置文件的概念。代码如下:

@Entity
@Table(name = "Namespace")
@SQLDelete(sql = "Update Namespace set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Namespace extends BaseEntity {

    /**
     * App 编号 {@link com.ctrip.framework.apollo.common.entity.App#appId}
     */
    @Column(name = "appId", nullable = false)
    private String appId;
    /**
     * Cluster 名 {@link Cluster#name}
     */
    @Column(name = "ClusterName", nullable = false)
    private String clusterName;
    /**
     * AppNamespace 名 {@link com.ctrip.framework.apollo.common.entity.AppNamespace#name}
     */
    @Column(name = "NamespaceName", nullable = false)
    private String namespaceName;
    
}    

2.3 AppNamespace vs. Namespace

关系图如下:

ER 图

数据流向如下:

  1. 在 App 下创建 AppNamespace 后,自动给 App 下每个 Cluster 创建 Namespace 。

  2. 在 App 下创建 Cluster 后,根据 App 下 每个 AppNamespace 创建 Namespace 。

  3. 可删除 Cluster 下的 Namespace 。

总结来说:

  1. AppNamespace 是 App 下的每个 Cluster 默认创建的 Namespace 。

  2. Namespace 是 每个 Cluster 实际拥有的 Namespace 。

2.4 类型

Namespace 类型有三种:

  • 私有类型:私有类型的 Namespace 具有 private 权限。

  • 公共类型:公共类型的 Namespace 具有 public 权限。公共类型的 Namespace 相当于游离于应用之外的配置,且通过 Namespace 的名称去标识公共 Namespace ,所以公共的 Namespace 的名称必须全局唯一。

  • 关联类型:关联类型又可称为继承类型,关联类型具有 private 权限。关联类型的Namespace 继承于公共类型的Namespace,用于覆盖公共 Namespace 的某些配置。

在 Namespace 实体中,找不到 类型的字段呀?!通过如下逻辑判断:

Namespace => AppNamespace
if (AppNamespace.isPublic) {
    return "公共类型";
}
if (Namespace.appId == AppNamespace.appId) {
    return "私有类型";
}
return "关联类型";

3. Portal 侧

3.1 NamespaceController

在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.controller.NamespaceController ,提供 AppNamespace 和 Namespace  的 API 。

在创建 Namespace的界面中,点击【提交】按钮,调用创建 AppNamespace 的 API 。

创建 Namespace

代码如下:

  1: @RestController
  2: public class NamespaceController {
  3: 
  4:     @Autowired
  5:     private ApplicationEventPublisher publisher;
  6:     @Autowired
  7:     private AppNamespaceService appNamespaceService;
  8:     @Autowired
  9:     private PortalConfig portalConfig;
 10: 
 11:     @PreAuthorize(value = "@permissionValidator.hasCreateAppNamespacePermission(#appId, #appNamespace)")
 12:     @RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST)
 13:     public AppNamespace createAppNamespace(@PathVariable String appId, @RequestBody AppNamespace appNamespace) {
 14:         // 校验 AppNamespace 的 `appId` 和 `name` 非空。
 15:         RequestPrecondition.checkArgumentsNotEmpty(appNamespace.getAppId(), appNamespace.getName());
 16:         // 校验 AppNamespace 的 `name` 格式正确。
 17:         if (!InputValidator.isValidAppNamespace(appNamespace.getName())) {
 18:             throw new BadRequestException(String.format("Namespace格式错误: %s",
 19:                     InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE + " & "
 20:                             + InputValidator.INVALID_NAMESPACE_NAMESPACE_MESSAGE));
 21:         }
 22:         // 保存 AppNamespace 对象到数据库
 23:         AppNamespace createdAppNamespace = appNamespaceService.createAppNamespaceInLocal(appNamespace);
 24:         // 赋予权限,若满足如下任一条件:
 25:         // 1. 公开类型的 AppNamespace 。
 26:         // 2. 私有类型的 AppNamespace ,并且允许 App 管理员创建私有类型的 AppNamespace 。
 27:         if (portalConfig.canAppAdminCreatePrivateNamespace() || createdAppNamespace.isPublic()) {
 28:             // 授予 Namespace Role
 29:             assignNamespaceRoleToOperator(appId, appNamespace.getName());
 30:         }
 31:         // 发布 AppNamespaceCreationEvent 创建事件
 32:         publisher.publishEvent(new AppNamespaceCreationEvent(createdAppNamespace));
 33:         // 返回创建的 AppNamespace 对象
 34:         return createdAppNamespace;
 35:     }
 36: 
 37: }
  • POST apps/{appId}/appnamespaces 接口,Request Body 传递 JSON 对象。

  • @PreAuthorize(...) 注解,调用 PermissionValidator#hasCreateAppNamespacePermission(appId, appNamespace) 方法,校验是否有创建 AppNamespace 的权限。后续文章,详细分享。

  • 第 15 行:调用 RequestPrecondition#checkArgumentsNotEmpty(String... args) 方法,校验 AppNamespace 的 appId 和 name 非空。

  • 第 16 至 21 行:调用 InputValidator#isValidAppNamespace(name) 方法,校验 AppNamespace 的 name 格式正确,符合 [0-9a-zA-Z_.-]+" 和 [a-zA-Z0-9._-]+(?<!\.(json|yml|yaml|xml|properties))$ 格式。

  • 第 23 行:调用 AppNamespaceService#createAppNamespaceInLocal(AppNamespace) 方法,保存 AppNamespace 对象到 Portal DB 数据库。在 「3.2 AppNamespaceService」 中,详细解析。

  • 第 27 至 30 行:调用 #assignNamespaceRoleToOperator(String appId, String namespaceName) 方法,授予 Namespace Role ,需要满足如下任一条件。

    • 1、 公开类型的 AppNamespace 。

    • 2、私有类型的 AppNamespace ,并且允许 App 管理员创建私有类型的 AppNamespace 。

      admin.createPrivateNamespace.switch 【在 ServerConfig 表】

      是否允许项目管理员创建 private namespace 。设置为 true 允许创建,设置为 false 则项目管理员在页面上看不到创建 private namespace 的选项。并且,项目管理员不允许创建 private namespace 。

  • 第 32 行:调用 ApplicationEventPublisher#publishEvent(AppNamespaceCreationEvent) 方法,发布 com.ctrip.framework.apollo.portal.listener.AppNamespaceCreationEvent 事件。

  • 第 38 行:返回创建的 AppNamespace 对象。

3.2 AppNamespaceService

在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.service.AppNamespaceService ,提供 AppNamespace 的 Service 逻辑。

#createAppNamespaceInLocal(AppNamespace) 方法,保存 AppNamespace 对象到 Portal DB 数据库。代码如下:

  1: @Autowired
  2: private UserInfoHolder userInfoHolder;
  3: @Autowired
  4: private AppNamespaceRepository appNamespaceRepository;
  5: @Autowired
  6: private RoleInitializationService roleInitializationService;
  7: @Autowired
  8: private AppService appService;
  9: 
 10: @Transactional
 11: public AppNamespace createAppNamespaceInLocal(AppNamespace appNamespace) {
 12:     String appId = appNamespace.getAppId();
 13:     // 校验对应的 App 是否存在。若不存在,抛出 BadRequestException 异常
 14:     // add app org id as prefix
 15:     App app = appService.load(appId);
 16:     if (app == null) {
 17:         throw new BadRequestException("App not exist. AppId = " + appId);
 18:     }
 19:     // 拼接 AppNamespace 的 `name` 属性。
 20:     StringBuilder appNamespaceName = new StringBuilder();
 21:     // add prefix postfix
 22:     appNamespaceName
 23:             .append(appNamespace.isPublic() ? app.getOrgId() + "." : "") // 公用类型,拼接组织编号
 24:             .append(appNamespace.getName())
 25:             .append(appNamespace.formatAsEnum() == ConfigFileFormat.Properties ? "" : "." + appNamespace.getFormat());
 26:     appNamespace.setName(appNamespaceName.toString());
 27:     // 设置 AppNamespace 的 `comment` 属性为空串,若为 null 。
 28:     if (appNamespace.getComment() == null) {
 29:         appNamespace.setComment("");
 30:     }
 31:     // 校验 AppNamespace 的 `format` 是否合法
 32:     if (!ConfigFileFormat.isValidFormat(appNamespace.getFormat())) {
 33:         throw new BadRequestException("Invalid namespace format. format must be properties、json、yaml、yml、xml");
 34:     }
 35:     // 设置 AppNamespace 的创建和修改人
 36:     String operator = appNamespace.getDataChangeCreatedBy();
 37:     if (StringUtils.isEmpty(operator)) {
 38:         operator = userInfoHolder.getUser().getUserId(); // 当前登录管理员
 39:         appNamespace.setDataChangeCreatedBy(operator);
 40:     }
 41:     appNamespace.setDataChangeLastModifiedBy(operator);
 42:     // 公用类型,校验 `name` 在全局唯一
 43:     // unique check
 44:     if (appNamespace.isPublic() && findPublicAppNamespace(appNamespace.getName()) != null) {
 45:         throw new BadRequestException(appNamespace.getName() + "已存在");
 46:     }
 47:     // 私有类型,校验 `name` 在 App 下唯一
 48:     if (!appNamespace.isPublic() && appNamespaceRepository.findByAppIdAndName(appNamespace.getAppId(), appNamespace.getName()) != null) {
 49:         throw new BadRequestException(appNamespace.getName() + "已存在");
 50:     }
 51:     // 保存 AppNamespace 到数据库
 52:     AppNamespace createdAppNamespace = appNamespaceRepository.save(appNamespace);
 53:     // 初始化 Namespace 的 Role 们
 54:     roleInitializationService.initNamespaceRoles(appNamespace.getAppId(), appNamespace.getName(), operator);
 55:     return createdAppNamespace;
 56: }
  • 第 15 至 18 行:调用 AppService.load(appId) 方法,获得对应的 App 对象。当校验 App 不存在时,抛出 BadRequestException 异常。

  • 第 19 至 26 行:拼接并设置 AppNamespace 的 name 属性。

  • 第 27 至 30 行:设置 AppNamespace 的 comment 属性为空串,若为 null 。

  • 第 31 至 34 行:校验 AppNamespace 的 format 是否合法。

  • 第 35 至 41 行:设置 AppNamespace 的创建和修改人。

  • 第 42 至 46 行:若 AppNamespace 为公用类型,校验 name 在全局唯一,否则抛出 BadRequestException 异常。#findPublicAppNamespace(name) 方法,代码如下:

    public AppNamespace findPublicAppNamespace(String namespaceName) {
        return appNamespaceRepository.findByNameAndIsPublic(namespaceName, true);
    }
    
  • 第 47 至 50 行:若 AppNamespace 为私有类型,校验 name 在 App 唯一否则抛出 BadRequestException 异常。

  • 第 52 行:调用 AppNamespaceRepository#save(AppNamespace) 方法,保存 AppNamespace 到数据库。

  • 第 54 行:初始化 Namespace 的 Role 们。详解解析,见 《Apollo 源码解析 —— Portal 认证与授权(二)之授权》 。


#createDefaultAppNamespace(appId) 方法,创建并保存 App 下默认的 "application" 的 AppNamespace 到数据库。代码如下:

@Transactional
public void createDefaultAppNamespace(String appId) {
    // 校验 `name` 在 App 下唯一
    if (!isAppNamespaceNameUnique(appId, ConfigConsts.NAMESPACE_APPLICATION)) {
        throw new BadRequestException(String.format("App already has application namespace. AppId = %s", appId));
    }
    // 创建 AppNamespace 对象
    AppNamespace appNs = new AppNamespace();
    appNs.setAppId(appId);
    appNs.setName(ConfigConsts.NAMESPACE_APPLICATION); // `application`
    appNs.setComment("default app namespace");
    appNs.setFormat(ConfigFileFormat.Properties.getValue());
    // 设置 AppNamespace 的创建和修改人为当前管理员
    String userId = userInfoHolder.getUser().getUserId();
    appNs.setDataChangeCreatedBy(userId);
    appNs.setDataChangeLastModifiedBy(userId);
    // 保存 AppNamespace 到数据库
    appNamespaceRepository.save(appNs);
}
  • 在 App 创建时,会调用该方法。

3.3 AppNamespaceRepository

在 apollo-portal 项目中,com.ctrip.framework.apollo.common.entity.App.AppNamespaceRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 AppNamespace 的数据访问,即 DAO 。

代码如下:

public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNamespace, Long> {

  AppNamespace findByAppIdAndName(String appId, String namespaceName);

  AppNamespace findByName(String namespaceName);

  AppNamespace findByNameAndIsPublic(String namespaceName, boolean isPublic);

  List<AppNamespace> findByIsPublicTrue();

}

3.4 AppNamespaceCreationEvent

com.ctrip.framework.apollo.portal.listener.AppNamespaceCreationEvent ,实现 org.springframework.context.ApplicationEvent 抽象类,AppNamespace 创建事件。

代码如下:

public class AppNamespaceCreationEvent extends ApplicationEvent {

  public AppNamespaceCreationEvent(Object source) {
    super(source);
  }

  public AppNamespace getAppNamespace() {
    Preconditions.checkState(source != null);
    return (AppNamespace) this.source;
  }

}
  • 构造方法,将 AppNamespace 对象作为方法参数传入。

  • #getAppNamespace() 方法,获得事件对应的 AppNamespace 对象。

3.4.1 CreationListener

com.ctrip.framework.apollo.portal.listener.CreationListener ,对象创建监听器,目前监听 AppCreationEvent 和 AppNamespaceCreationEvent 事件。

我们以  AppNamespaceCreationEvent 举例子,代码如下:

@EventListener
public void onAppNamespaceCreationEvent(AppNamespaceCreationEvent event) {
    // 将 AppNamespace 转成 AppNamespaceDTO 对象
    AppNamespaceDTO appNamespace = BeanUtils.transfrom(AppNamespaceDTO.class, event.getAppNamespace());
    // 获得有效的 Env 数组
    List<Env> envs = portalSettings.getActiveEnvs();
    // 循环 Env 数组,调用对应的 Admin Service 的 API ,创建 AppNamespace 对象。
    for (Env env : envs) {
        try {
            namespaceAPI.createAppNamespace(env, appNamespace);
        } catch (Throwable e) {
            logger.error("Create appNamespace failed. appId = {}, env = {}", appNamespace.getAppId(), env, e);
            Tracer.logError(String.format("Create appNamespace failed. appId = %s, env = %s", appNamespace.getAppId(), env), e);
        }
    }
}

3.5 NamespaceAPI

com.ctrip.framework.apollo.portal.api.NamespaceAPI ,实现 API 抽象类,封装对 Admin Service 的 AppNamespace 和 Namespace 两个模块的 API 调用。代码如下:

NamespaceAPI
  • 使用 restTemplate ,调用对应的 API 接口。

4. Admin Service 侧

4.1 AppNamespaceController

在 apollo-adminservice 项目中, com.ctrip.framework.apollo.adminservice.controller.AppNamespaceController ,提供 AppNamespace 的 API 。

#create(AppNamespaceDTO) 方法,创建 AppNamespace 。代码如下:

  1: @RestController
  2: public class AppNamespaceController {
  3: 
  4:     @Autowired
  5:     private AppNamespaceService appNamespaceService;
  8: 
  9:     /**
 10:      * 创建 AppNamespace
 11:      *
 12:      * @param appNamespace AppNamespaceDTO 对象
 13:      * @return AppNamespace 对象
 14:      */
 15:     @RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST)
 16:     public AppNamespaceDTO create(@RequestBody AppNamespaceDTO appNamespace) {
 17:         // 将 AppNamespaceDTO 转换成 AppNamespace 对象
 18:         AppNamespace entity = BeanUtils.transfrom(AppNamespace.class, appNamespace);
 19:         // 判断 `name` 在 App 下是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 BadRequestException 异常。
 20:         AppNamespace managedEntity = appNamespaceService.findOne(entity.getAppId(), entity.getName());
 21:         if (managedEntity != null) {
 22:             throw new BadRequestException("app namespaces already exist.");
 23:         }
 24:         // 设置 AppNamespace 的 format 属性为 "properties",若为 null 。
 25:         if (StringUtils.isEmpty(entity.getFormat())) {
 26:             entity.setFormat(ConfigFileFormat.Properties.getValue());
 27:         }
 28:         // 保存 AppNamespace 对象到数据库
 29:         entity = appNamespaceService.createAppNamespace(entity);
 30:         // 将保存的 AppNamespace 对象,转换成 AppNamespaceDTO 返回
 31:         return BeanUtils.transfrom(AppNamespaceDTO.class, entity);
 32:     }
 33:     
 34:     // ... 省略其他接口和属性
 35: }
  • POST /apps/{appId}/appnamespaces 接口,Request Body 传递 JSON 对象。

  • 第 22 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将 AppNamespaceDTO 转换成 AppNamespace对象。

  • 第 20 至 23 行:调用 AppNamespaceService#findOne(appId, name) 方法,校验 name  在 App 下,是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 BadRequestException 异常。

  • 第 24 至 27 行:设置 AppNamespace 的 format 属性为 "properties",若为 null 。

  • 第 29 行:调用 AppNamespaceService#createAppNamespace(AppNamespace) 方法,保存 AppNamespace 对象到数据库。

  • 第 30 至 32 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将保存的 AppNamespace 对象,转换成 AppNamespaceDTO 返回。

4.2 AppNamespaceService

在 apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.AppNamespaceService ,提供 AppNamespace 的 Service 逻辑给 Admin Service 和 Config Service 。

#save(AppNamespace) 方法,保存 AppNamespace 对象到数据库中。代码如下:

  1: @Autowired
  2: private AppNamespaceRepository appNamespaceRepository;
  3: @Autowired
  4: private NamespaceService namespaceService;
  5: @Autowired
  6: private ClusterService clusterService;
  7: @Autowired
  8: private AuditService auditService;
  9: 
 10: @Transactional
 11: public AppNamespace createAppNamespace(AppNamespace appNamespace) {
 12:     // 判断 `name` 在 App 下是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 ServiceException 异常。
 13:     String createBy = appNamespace.getDataChangeCreatedBy();
 14:     if (!isAppNamespaceNameUnique(appNamespace.getAppId(), appNamespace.getName())) {
 15:         throw new ServiceException("appnamespace not unique");
 16:     }
 17:     // 保护代码,避免 App 对象中,已经有 id 属性。
 18:     appNamespace.setId(0);// protection
 19:     appNamespace.setDataChangeCreatedBy(createBy);
 20:     appNamespace.setDataChangeLastModifiedBy(createBy);
 21:     // 保存 AppNamespace 到数据库
 22:     appNamespace = appNamespaceRepository.save(appNamespace);
 23:     // 创建 AppNamespace 在 App 下,每个 Cluster 的 Namespace 对象。
 24:     instanceOfAppNamespaceInAllCluster(appNamespace.getAppId(), appNamespace.getName(), createBy);
 25:     // 记录 Audit 到数据库中
 26:     auditService.audit(AppNamespace.class.getSimpleName(), appNamespace.getId(), Audit.OP.INSERT, createBy);
 27:     return appNamespace;
 28: }
  • 第 12 至 16 行:调用 #isAppNamespaceNameUnique(appId, name) 方法,判断 name 在 App 下是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 ServiceException 异常。代码如下:

    public boolean isAppNamespaceNameUnique(String appId, String namespaceName) {
        Objects.requireNonNull(appId, "AppId must not be null");
        Objects.requireNonNull(namespaceName, "Namespace must not be null");
        return Objects.isNull(appNamespaceRepository.findByAppIdAndName(appId, namespaceName));
    }
    
  • 第 18 行:置“空” AppNamespace 的编号,防御性编程,避免 AppNamespace 对象中,已经有 id 属性。

  • 第 22 行:调用 AppNamespaceRepository#save(AppNamespace) 方法,保存 AppNamespace 对象到数据库中。

  • 第 24 行:调用 #instanceOfAppNamespaceInAllCluster(appId, namespaceName, createBy) 方法,创建 AppNamespace 在 App 下,每个 Cluster 的 Namespace 对象。代码如下:

    private void instanceOfAppNamespaceInAllCluster(String appId, String namespaceName, String createBy) {
        // 获得 App 下所有的 Cluster 数组
        List<Cluster> clusters = clusterService.findParentClusters(appId);
        // 循环 Cluster 数组,创建并保存 Namespace 到数据库
        for (Cluster cluster : clusters) {
            Namespace namespace = new Namespace();
            namespace.setClusterName(cluster.getName());
            namespace.setAppId(appId);
            namespace.setNamespaceName(namespaceName);
            namespace.setDataChangeCreatedBy(createBy);
            namespace.setDataChangeLastModifiedBy(createBy);
            namespaceService.save(namespace);
        }
    }
    
  • 第 26 行:记录 Audit 到数据库中。

4.3 AppNamespaceRepository

com.ctrip.framework.apollo.biz.repository.AppNamespaceRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 AppNamespace 的数据访问 给 Admin Service 和 Config Service 。代码如下:

public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNamespace, Long>{

  AppNamespace findByAppIdAndName(String appId, String namespaceName);

  List<AppNamespace> findByAppIdAndNameIn(String appId, Set<String> namespaceNames);

  AppNamespace findByNameAndIsPublicTrue(String namespaceName);

  List<AppNamespace> findByNameInAndIsPublicTrue(Set<String> namespaceNames);

  List<AppNamespace> findByAppIdAndIsPublic(String appId, boolean isPublic);

  List<AppNamespace> findByAppId(String appId);

  List<AppNamespace> findFirst500ByIdGreaterThanOrderByIdAsc(long id);

}

4.4 NamespaceService

在 apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.NamespaceService ,提供 Namespace 的 Service 逻辑给 Admin Service 和 Config Service 。

#save(Namespace) 方法,保存 Namespace 对象到数据库中。代码如下:

  1: @Autowired
  2: private NamespaceRepository namespaceRepository;
  3: @Autowired
  4: private AuditService auditService;
  5: 
  6: @Transactional
  7: public Namespace save(Namespace entity) {
  8:     // 判断是否已经存在。若是,抛出 ServiceException 异常。
  9:     if (!isNamespaceUnique(entity.getAppId(), entity.getClusterName(), entity.getNamespaceName())) {
 10:         throw new ServiceException("namespace not unique");
 11:     }
 12:     // 保护代码,避免 Namespace 对象中,已经有 id 属性。
 13:     entity.setId(0);//protection
 14:     // 保存 Namespace 到数据库
 15:     Namespace namespace = namespaceRepository.save(entity);
 16:     // 记录 Audit 到数据库中
 17:     auditService.audit(Namespace.class.getSimpleName(), namespace.getId(), Audit.OP.INSERT, namespace.getDataChangeCreatedBy());
 18:     return namespace;
 19: }
  • 第 8 至 11 行:调用 #isNamespaceUnique(appId, cluster, namespace) 方法,校验是否已经存在。若是,抛出 ServiceException 异常。代码如下:

    public boolean isNamespaceUnique(String appId, String cluster, String namespace) {
        Objects.requireNonNull(appId, "AppId must not be null");
        Objects.requireNonNull(cluster, "Cluster must not be null");
        Objects.requireNonNull(namespace, "Namespace must not be null");
        return Objects.isNull(namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, cluster, namespace));
    }
    
  • 第 12 行:置“空” Namespace 的编号,防御性编程,避免 Namespace 对象中,已经有 id 属性。

  • 第 15 行:调用 NamespaceRepository#save(AppNamespace) 方法,保存 Namespace 对象到数据库中。

  • 第 17 行:记录 Audit 到数据库中。


#instanceOfAppNamespaces(appId, clusterName, createBy) 方法,创建并保存 App 下指定 Cluster 的 Namespace 到数据库。代码如下:

@Transactional
public void instanceOfAppNamespaces(String appId, String clusterName, String createBy) {
    // 获得所有的 AppNamespace 对象
    List<AppNamespace> appNamespaces = appNamespaceService.findByAppId(appId);
    // 循环 AppNamespace 数组,创建并保存 Namespace 到数据库
    for (AppNamespace appNamespace : appNamespaces) {
        Namespace ns = new Namespace();
        ns.setAppId(appId);
        ns.setClusterName(clusterName);
        ns.setNamespaceName(appNamespace.getName());
        ns.setDataChangeCreatedBy(createBy);
        ns.setDataChangeLastModifiedBy(createBy);
        namespaceRepository.save(ns);
        // 记录 Audit 到数据库中
        auditService.audit(Namespace.class.getSimpleName(), ns.getId(), Audit.OP.INSERT, createBy);
    }
}
  • 在 App 创建时,传入 Cluster 为 default ,此时只有 1 个 AppNamespace 对象。

  • 在 Cluster 创建时,传入自己,此处可以有多个 AppNamespace 对象。

4.5 NamespaceRepository

com.ctrip.framework.apollo.biz.repository.NamespaceRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 Namespace 的数据访问 给 Admin Service 和 Config Service 。代码如下:

public interface NamespaceRepository extends PagingAndSortingRepository<Namespace, Long> {

  List<Namespace> findByAppIdAndClusterNameOrderByIdAsc(String appId, String clusterName);

  Namespace findByAppIdAndClusterNameAndNamespaceName(String appId, String clusterName, String namespaceName);

  @Modifying
  @Query("update Namespace set isdeleted=1,DataChange_LastModifiedBy = ?3 where appId=?1 and clusterName=?2")
  int batchDelete(String appId, String clusterName, String operator);

  List<Namespace> findByAppIdAndNamespaceName(String appId, String namespaceName);

  List<Namespace> findByNamespaceName(String namespaceName, Pageable page);

  int countByNamespaceNameAndAppIdNot(String namespaceName, String appId);

}

666. 彩蛋

类似于 App 的创建,AppNamespace 也存在跨系统同步的一致性问题。但是,目前暂未提供补偿机制,如果 Portal 创建 AppNamespace 成功,而调用远程 Admin Service 失败,则会出现不一致的情况。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

私彩视频开奖动画直播源码内部核心技术解析及修改可增加后台操控-持续更新文章! 其实在此很感谢能做出H5开奖系统的码农们,而我本人也喜欢分解此类的东西。 因为特殊原因,该东西本人就不做图片展示,以防他人商业化。 借用开奖摇奖代码首先分解首页index文件。代码如下:(后面持续在原有的作品上添加后台操作) 代码里写的//.....是我的注明代码的所需。 ​ &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;... 阅读详情

相关推荐

Windows 10/11 下用 Docker 一键部署 CVAT 标注工具(附 WSL2 配置指南)

本文详细指导如何在Windows 10/11系统上,通过配置WSL2和Docker Desktop,实现CVAT标注工具的一键部署。内容涵盖从环境准备、Docker安装、CVAT服务启动到性能优化与安全配置的全流程,为计算机视觉开发者提供稳定高效的本地标注平台解决方案。

weixin_28839601的博客 462

Apollo源码解析——创建NameSpace

文章目录一、概述二、创建流程1.页面流程2.代码流程 一、概述 Patrol创建Namespace的流程 二、创建流程 1.页面流程 2.代码流程 patrol模块下的NamespaceController#createAppNamespace 保存AppNamespace到数据库 发布AppNamespaceCreationEvent 创建事件 @PreAuthorize(value = "@permissionValidator.hasCreateAppNamespacePe

ZXMSH的博客 1279

浙政钉(专有钉钉)门户免登(超详细)

钉钉家族 介绍浙政钉门户免登之前,首先搞清楚几个软件的关系,软件分别是:钉钉,专有钉钉,浙政钉 钉钉 是阿里巴巴集团专为中小企业打造的沟通和协同的多端平台。 专有钉钉 原名 政务钉钉,有更开放的设计能力、集成能力、更结构化协同产品、更定制化开发平台。支持专有化部署,数据落本地,更安全、客户可对该产品持牌运营、结构化编排,协助构建本地应用中心、提供移动应用全面安全保障体系。 浙政钉 为规范浙政钉整体架构体系,按照统分结合原则,由省政府办公厅统一设计整体工作界面和系统框架,统筹指导全省统建应用建设,各单

一缕南风的博客 2万+

【Kubernetes资源篇】Namespace名称空间详解

可以通过Resource Quota来限制Namespace中资源的使用,资源配额是一种控制机制,可以限制Namespace中资源使用量,包括CPU、内存、存储等。命名空间namespace是k8s集群级别的资源,可以给不同的用户、租户、环境或项目创建对应的命名空间。如果Namespace进行了资源配额了,那么在创建Pod时必须使用resources进行限制。可以抽象的吧Namespace理解为,QQ分组,分组中存着不同的好友,好友就是容器。第二种:通过YAML资源清单进行创建Namespace。

秦子腾 2万+

ADS2020与SystemVue2020Update1.0同机安装避坑指南:解决License冲突,实现EVM联合仿真

本文详细介绍了ADS2020与SystemVue2020 Update1.0同机安装的完整解决方案,重点解决License冲突问题,实现EVM联合仿真。通过合理的安装顺序、路径规划和License服务配置,确保两款EDA工具和谐共存,为射频与微波电路设计提供高效仿真环境。

weixin_30271335的博客 348

华为OD机试真题2026双机位C卷 Python&JS 实现【明日之星选举】

给定一组选票votes。votes[i]代表第 i 张选票的内容,包含一个字符串"zhangsan,hanmei",表示推荐zhangsan为明日之星,反对 hanmei 称为明日之星。赞成或反对使用逗号(,)分割。 给定一个数组n,表示最后将筛选出赞成票最多的n个人,如果赞成票相同,则反对票越少,排序越靠前;如果赞成票和反对票都相同,则按照姓名字典序升序排序。

MISAYAONE的博客 318

Namespace

Namespace命令行创建;YAML文件创建;Namespace用例

K要努力的博客 526

181、LLC谐振变换器的PCB设计实战(文件归档)

PCB设计实战经验总结:LLC谐振变换器的关键要点 本文通过实际案例总结了LLC谐振变换器PCB设计中的核心经验和常见误区。重点包括: 文件命名规范(项目代号_板卡类型_版本号_日期格式) 四层板标准层叠结构设计要点 谐振回路走线原则(宽度、长度控制) 地线处理技巧(功率地/控制地隔离) 散热设计注意事项(MOSFET过孔阵列、变压器布局) 完整的文件归档清单(Gerber、钻孔、装配图等) 作者特别强调,良好的设计归档习惯能显著降低生产问题概率,建议采用双重备份机制。文中多个真实案例(如谐振频率偏移、EM

Jack010169的博客 43

Apollo分布式配置中心(二)

上一篇:上一篇已经知道Apollo是什么东西了,接下来实践一下。

weixin_43522117的博客 1995

YOLOV8改进:添加Wiseiou,有效涨点

【代码】YOLOV8改进:添加Wiseiou,有效涨点。

AIcurator的博客 2391

【StarRocks】-- DATETIME 与 TIMESTAMP 区别详解

本文详细解析了StarRocks中DATETIME/DATETIMEV2与TIMESTAMP的区别及使用场景。DATETIME系列存储人类可读时间,不携带时区;TIMESTAMP实际指Unix时间戳,建议用BIGINT存储。关键差异包括:精度选择(DATETIMEV2支持微秒级)、时区处理(仅函数计算时影响)、存储方式等。针对不同场景提供选型建议:单时区展示用DATETIMEV2,跨时区计算用BIGINT存储Epoch。特别强调与MySQL迁移时的注意事项,并给出常见问题解决方案和最佳实践清单,包括统一入口

欢迎来到我的博客,一起探索代码里的世界! 498

3、Apollo源码解析 - Portal创建 AppNamespace

摘要:文章描述了Portal和AdminService两端创建AppNamespace的流程。Portal端首先检查权限,创建AppNamespace表记录并授权,然后发送创建事件通知;AdminService端接收事件后,进行唯一性检查并插入相关数据库表(AppNamespace和Namespace),同时为应用所有集群创建对应的Namespace记录。

weixin_38687619的博客 112

基于MATLAB的数字滤波器之低通滤波器

本文首先介绍了数字低通滤波器的概念和原理,其次论述了低通滤波器的技术指标和设计步骤,最后给出了数字低通滤波器应用于信号去噪的MATLAB代码。

总结论文中的算法并给出论文的复现代码 3万+

Apollo 源码解析

Apollo 源码解析 —— 调试环境搭建 Apollo 源码解析 —— Portal 创建 App Apollo 源码解析 —— Portal 创建 Cluster Apollo 源码解析 —— Portal 创建 Namespace Apollo 源码解析 —— Portal 关联 Namespace Apollo 源码解析 —— Portal 创建 Item Apollo 源...

HIP的博客 5126

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

【知其然,知其所以然】配置中心 Apollo源码剖析

第2章 Apollo源码剖析 能力目标 能够基于Git导入Apollo源码 能够基于IDEA实现DEBUG分析APP创建 掌握Namespace创建过程 掌握Item创建过程 掌握灰度发布创建过程 1:namespace创建、灰度发布配置、Item创建作为自学 2:客户端剖析 ​ 通信->Http、轮询机制 ​ 配置文件优先级、缓存、关联关系 ​ 刷新机制【注解解析】 1 Apollo源码搭建 在上一章我们已经学习了Apollo项目实战,为了更进一步学习Apollo、

博学谷狂野架构师的博客 1545

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

5、Apollo源码解析 - Portal创建单个Item

本文介绍了配置管理系统中Item的创建流程。Item是Namespace的最小配置单元,支持properties、yml等多种格式。创建过程主要涉及权限校验、格式验证、唯一性检查等步骤,最终将加密后的配置项写入数据库并记录变更历史。

weixin_38687619的博客 275

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

微服务配置中心 Apollo解析——Portal 关联 Namespace

第 25 行:调用 NamespaceService#createNamespace(Env, NamespaceDTO) 方法,创建并保存 Namespace 到 Admin Service 中。第 13 行:调用 NamespaceAPI#createNamespace(Env, NamespaceDTO) 方法,创建 Namespace 到 Admin Service。#create(appId, clusterName, NamespaceDTO) 方法,创建 Namespace。

2501_90403983的博客 898

Apollo 源码解析 —— Portal 关联 Namespace

点击上方“芋道源码”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏原创 | Java 2020 超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络...

芋艿V 354

Apollo 源码解析 —— Portal 创建灰度

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 8:55 更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2020 超神之路,很肝~...

芋艿V 209

Apollo 源码解析 —— Portal 创建 Cluster

1. 概述 老艿艿:本系列假定胖友已经阅读过《Apollo 官方 wiki 文档》。 本文分享Portal 创建 Cluster的流程,整个过程涉及 Portal、Admin Service ,如下图所示: 下面,我们先来看看 Cluster 的实体结构 老艿艿:因为 Portal 是管理后台,所以从代码实现上,和业务系统非常相像。也因此,本文会略显啰嗦。 2. Cluster com.ctrip.framework.apollo.biz.entity.Cluster,继承...

weixin_42073629的博客 288

Apollo 源码解析 —— Portal 创建 Item

1. 概述 老艿艿:本系列假定胖友已经阅读过《Apollo 官方 wiki 文档》。 Item ,配置项,是 Namespace 下最小颗粒度的单位。在 Namespace 分成五种类型:propertiesymlyamljsonxml。其中: properties:每一行配置对应一条Item 记录。 后四者:无法进行拆分,所以一个Namespace仅仅对应一条Item 记录。 本文先分享Portal 创建类型为 properties 的 Namespace 的 Ite...

weixin_42073629的博客 561
上一篇: 传说中最经典的外包案例
下一篇: 芋道 RocketMQ 安装部署
公众号-芋道源码
博客等级 码龄10年 1629粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值