面试中所谈的项目中的亮点

前言

我是从18年11月份入职的,一直做的是Java开发,起初和大部分人一样都是CRUD,直到去年年底一个小项目让我做了技术经理,虽然我在项目上受到了比较大的打击(做己方的话如果真的遇到一个很难对付的甲方简直让人崩溃),但也确实让我获得很多技术上的成长

写这篇博客主要为了回顾一下自己过去做过的项目,也梳理一下面试中谈到项目这块可以说出的亮点,如果对看到这篇博客的你有帮助那就更好了O(∩_∩)O

下文中提到的项目上的亮点如下

1)、使用反射+枚举的方式记录变更日志

2)、使用策略模式+工厂模式封装银企直连服务

3)、基于GitLab CI实现CI、CD流程,以及jib打包自签名问题的解决

4)、使用Rancher搭建Kubernetes集群

5)、基于OpenShift平台部署Apollo配置中心遇到的问题,以及对Apollo实现原理、Eureka服务发现原理的思考

6)、对接单点登录,以及OAuth授权码模式的思考

1、使用反射+枚举的方式记录变更日志

业务需求

对比原单据和变更后的单据生成变更日志,即哪些字段发生了变更并且记录变更前和变更后的值

使用注解的主要达到两个效果

1)、只有该注解标注的属性才会生成变更日志

2)、标注相应字段来设置属性的描述信息(前台页面上的中文描述)

工具类核心方法传参是两个Object对象,主要实现逻辑如下:

1)、只有两个对象都是同一类型才有可比性

2)、通过反射循环获取父类Class对象,直到父类为null的时候说明到达了最上层的Object类,在循环中获取当前类的Field放入到List中

3)、循环所有Field的List,使用setAccessible()方法设置对象的访问权限,保证对private的属性的访问,判断只有使用注解标注的属性才会生成变更日志,通过isEquals()方法判断变更前后的属性值是否一致,不一致生成变更日志对象放入List中

这里调用一个新声明的isEquals()方法主要是为了方便子类根据不同的业务逻辑去扩展,不是所有场景下都直接调用equals()方法即可,比如说BigDecimal如果使用equals()方法比较的是数值+精度,比如1.0和1其实数值相同,但精度不同,equals()方法返回false,但其实实际金额并未发生改变,所以当时实现的时候子类判断如果是BigDecimal类型,使用compareTo()方法来判断是否产生了变更

详细解决方案:https://blog.csdn.net/qq_40378034/article/details/104158786

2、使用策略模式+工厂模式封装银企直连服务

业务需求

资金系统需要对接10多家银企直连,根据传入的支付方式判断走哪一家银行,然后调用相关方法

实现思路

开发银企直连服务虽然是包含10多家银行,但业务逻辑上需要提供的方法是一致的(支付走单笔、支付走批量、查询支付结果、查询账户余额等等),首先根据业务需求写一个统一的Interface接口,每个Service实现这个统一的Interface

但根据传入的支付方式判断走哪一家银行,调用具体的哪个实现类,是通过了注入Map的方式,Spring会自动将Strategy接口的实现类注入到这个Map中,key为bean id,value值则为对应的策略实现类

@Component
public class StrategyFactory {
    //Spring会自动将Strategy接口的实现类注入到这个Map中,key为bean id,value值则为对应的策略实现类
    @Autowired
    private Map<String, Strategy> strategyMap;

    public Strategy getBy(String strategyName) {
        return strategyMap.get(strategyName);
    }
}

然后做了一个别名转换的处理

@Component
@PropertySource("classpath:application.properties")
@ConfigurationProperties(prefix = "strategy")
public class StrategyAliasConfig {
    private HashMap<String, String> aliasMap;

    public static final String DEFAULT_STATEGY_NAME = "defaultStrategy";

    public HashMap<String, String> getAliasMap() {
        return aliasMap;
    }

    public void setAliasMap(HashMap<String, String> aliasMap) {
        this.aliasMap = aliasMap;
    }

    public String of(String entNum) {
        return aliasMap.get(entNum);
    }
}

配置文件application.properties

strategy.aliasMap.strategy1=concreteStrategy1
strategy.aliasMap.strategy2=concreteStrategy2
@Component
public class StrategyFactory {
    @Autowired
    private StrategyAliasConfig strategyAliasConfig;

    //Spring会自动将Strategy接口的实现类注入到这个Map中,key为bean id,value值则为对应的策略实现类
    @Autowired
    private Map<String, Strategy> strategyMap;

    //找不到对应的策略类,使用默认的
    public Strategy getBy(String strategyName) {
        String name = strategyAliasConfig.of(strategyName);
        if (name == null) {
            return strategyMap.get(StrategyAliasConfig.DEFAULT_STATEGY_NAME);
        }
        Strategy strategy = strategyMap.get(name);
        if (strategy == null) {
            return strategyMap.get(StrategyAliasConfig.DEFAULT_STATEGY_NAME);
        }
        return strategy;

    }
}

详细解决方案:https://blog.csdn.net/qq_40378034/article/details/104121363

3、基于GitLab CI实现CI、CD流程,以及jib打包自签名问题的解决

项目上实现的主要流程如下

提交代码到dev-latest之后,在GitLab上打tag来触发CI、CD的流程,步骤一先通过jib生成镜像推送到镜像私库(如果是一个完整的流程可以在步骤一之前添加单元测试、Sonarqube代码扫描的流程),步骤二通过Rancher的devops镜像(这里使用的docker runner)使用Rancher的命令行执行发布

主要遇到的坑

客户提供的Harbor仓库用的是自签名Https证书,而Jib使用JVM的已批准CA证书列表来验证SSL证书,最后是通过一个工具类生成证书相关的文件然后放到JAVA_HOME/jre/lib/security目录下,达到的效果就是证书在JRE级别受信任

详细解决方案:https://blog.csdn.net/qq_40378034/article/details/104750483

4、使用Rancher搭建Kubernetes集群

在这里插入图片描述

使用Ranche搭建Kubernetes集群时,每个主机有三种角色可以选择:EtcdControl PlaneWorker

1)、Etcd

etcd节点的主要功能是数据存储,它负责存储Rancher Server的数据和集群状态。Kubernetes集群的状态保存在etcd中,etcd节点运行etcd数据库。etcd数据库组件是一个分布式的键值对存储系统,用于存储Kubernetes的集群数据,例如集群协作相关和集群状态相关的数据

etcd更新集群状态前,需要集群中的所有节点通过quorum投票机制完成投票。假设集群中有n个节点,至少需要n/2+1n/2 + 1(向下取整) 个节点同意,才被视为多数集群同意更新集群状态。例如一个集群中有3个etcd节点,quorum投票机制要求至少两个节点同意,才会更新集群状态

2)、Control Plane

Control Plane节点上运行的工作负载包括:Kubernetes API Server、Scheduler和Controller Mananger。这些节点负载执行日常任务,从而确保集群状态和集群配置相匹配。因为etcd节点保存了集群的全部数据,所以Control Plane节点是无状态的

3)、Worker

Worker节点运行以下应用:

  • Kubelet:监控节点状态的Agent,确保容器处于健康状态
  • 工作负载:承载应用和其他类型的部署的容器和Pod

5、基于OpenShift平台部署Apollo配置中心遇到的问题,以及对Apollo实现原理的思考

部署架构及遇到的问题

Apollo相关的Config Service、Admin Service和Portal都是封装在一个镜像中,作为一个独立的服务,但没有注册到Apollo自身的Eureka Server,而是和其他应用服务共用一个Eureka Server(为保证Apollo高可用)

当时部署是基于OpenShift平台,我们对K8S相关的知识也不太熟悉,最后达到的效果是服务间可以通过服务名(Service Name)调用,但是不能通过注册到Eureka Server中每个Pod的IP来访问服务

Eureka中注册的信息

Application IP:Port
APOLLO-ADMINSERVICE 192.169.0.1:8090
APOLLO-CONFIGSERVICE 192.169.0.1:8080

如果其他服务通过服务名Apollo+端口8080来访问Config Service是可以访问的,但是不能通过192.169.0.1:8080访问

解决方案

问题1

实际上如果我们注册到Eureka中的注册的服务地址是服务名Apollo+端口这样的形式,那么问题就解决了

Application IP:Port
APOLLO-ADMINSERVICE Apollo:8090
APOLLO-CONFIGSERVICE Apollo:8080

Eureka注册时有一个参数可以定制完整的服务URL,这个服务URL就是服务间通过服务名在Eureka中获取的服务地址,配置参数为eureka.instance.homePageUrl=http://Apollo:8080,这样从Eureka Server中拿到的服务地址就是http://Apollo:8080

问题2

那本地开发时是要指定Apollo的地址来拿到配置信息,指定的是Meta Server的地址apollo.meta=http://IP:Port,Meta Server再找到Eureka Server,获取服务地址,由于上面的改造,我们拿到的是http://Apollo:8080这种形式的,但是本地开发并不能访问

这时本地配置改为apollo.configService=http://域名:8080直接指定Config Service的地址来跳过Meta Server的服务发现

Apollo实现原理

在这里插入图片描述

  • Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
  • Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal
  • Config Service和Admin Service都是多实例、无状态部署,所以需要将自己注册到Eureka中并保持心跳
  • 在Eureka之上架了一层Meta Server用于封装Eureka的服务发现接口
  • Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Client侧会做load balance、错误重试
  • Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧会做load balance、错误重试

在这里插入图片描述

1)、客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送

2)、客户端还会定时从Apollo配置中心服务端拉取应用的最新配置(推拉结合

  • 这是一个fallback机制,为了防止推送机制失效导致配置不更新
  • 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
  • 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟

3)、客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中

4)、客户端会把从服务端获取到的配置在本地文件系统缓存一份

在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置

Mac/Linux/opt/data/{appId}/config-cache

WindowsC:\opt\data{appId}\config-cache

文件名格式如下

{appId}+{cluster}+{namespace}.properties

6、对接单点登录,以及OAuth2.0授权码模式的思考

业务需求

取消我们系统的登录页,使用统一登录平台进行登录,登录后跳转到系统的首页,采用的是OAuth 2.0的授权码模式

流程图

下面我们系统简称为A网站,统一登录平台简称为B网站

在这里插入图片描述

第一步,A网站提供一个链接,用户点击后就会跳转到B网站,授权用户数据给A网站使用。下面就是A网站跳转B网站的一个示意链接

https://b.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read

response_type参数表示要求返回授权码(code),client_id参数让B知道是谁在请求,redirect_uri参数是B接受或拒绝请求后的跳转网址,scope参数表示要求的授权范围(这里是只读)

在这里插入图片描述

第二步,用户跳转后,B网站会要求用户登录,然后询问是否同意给予A网站授权。用户表示同意,这时B网站就会跳回redirect_uri参数指定的网址。跳转时,会传回一个授权码,就像下面这样

https://a.com/callback?code=AUTHORIZATION_CODE

code参数就是授权码

在这里插入图片描述

第三步,A网站拿到授权码以后,就可以在后端,向B网站请求令牌

https://b.com/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL

上面 URL 中,client_id参数和client_secret参数用来让B确认A的身份(client_secret参数是保密的,因此只能在后端发请求),grant_type参数的值是AUTHORIZATION_CODE,表示采用的授权方式是授权码,code参数是上一步拿到的授权码,redirect_uri参数是令牌颁发后的回调网址

在这里插入图片描述

第四步,B网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri指定的网址,发送一段JSON数据

{    
  "access_token":"ACCESS_TOKEN",
  "token_type":"bearer",
  "expires_in":2592000,
  "refresh_token":"REFRESH_TOKEN",
  "scope":"read",
  "uid":100101,
  "info":{...}
}

在这里插入图片描述

OAuth2.0的四种方式:http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章