Dependency-scan项目介绍:
获取企业版Github中每个团队项目依赖的版本并与父类依赖版本进行对比,将对比结果存入数据库中。通过手动请求和定时设置两种方式触发该任务,并且设计接口可实现手动中止扫描。
- gradle与maven的区别
- gradle是构建工具,为了构建,带有依赖缓存功能(不是包管理功能)
- api的依赖可以继承,但implementation的依赖不可以,complieOnly只参与编译
- maven是依赖包管理工具,通过插件带有一定的构建能力。
- gradle是构建工具,为了构建,带有依赖缓存功能(不是包管理功能)
- sdkman是一个 SDK 管理工具,其主要管理 JVM 生态下的各种 SDK,如JDK。
- jfog是一个二进制包(jar包)管理工具,第一次导入(不是运行)dependency-scan项目时,会自动下载相应版本的jar包。
lombok:
- @RequiredArgsConstructor 生成一个包含所有non-null和final字段的构造方法
- @AllArgsConstructor 生成一个包含所有字段的构造方法,即全参构造方法
- @NoArgsConstructor 生成一个不包含任何字段的构造方法,即无参构造方法
- @Data: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, @Setter on all non-final fields, and @RequiredArgsConstructor!
- 自动装配:@RequiredArgsConstructor 代替 @AutoWired 写法 Link
- 手动添加无参构造方法,并用@Tolerate修饰该方法
- 在该类上添加 @NoArgsConstructor 和 @AllArgsConstructor 这两个注解
Jgit:
- 使用try-with-resources即
try(resource){}
,括号内的代码执行结束后会自动关闭括号内的资源,Git类又实现了Closeable
接口,因此恰好用try-with-resources.
enum:
- status写法,
public enum ProjectLogStatus{PROCESSING,SUCCESS,FAILED}
的LogStatus.GOOD.name()
为GOOD
- 如果有两个参数,那么使用@Getter和@AllArgsConstructor
泛型:
- 方法:
public <T> T[] func(T[] args){}
- 类:
public class Test<T>{}
<T>
和 <?>
的区别,首先要区分开两种不同的场景:
第一,声明一个泛型类或泛型方法。
第二,使用泛型类或泛型方法。
类型参数<T>
主要用于第一种,声明泛型类或泛型方法。
无界通配符<?>
主要用于第二种,使用泛型类或泛型方法,多用于 <? extends Integer>
或 <? super Integer>
。
Link
- 通过 T 来 确保 泛型参数的一致性
- 类型参数可以多重限定而通配符不行
- 通配符可以使用超类限定而类型参数不行
nacos作为配置中心,配置阿里云上的数据库,通过setting.xml文件即可方法阿里云的nacos。
基于Github Action的CI/CD流程
- Kong 网关
- Istio 流量管理,Istio叠加在Kubernetes之上,可实现灰度发布(金丝雀发布)link
- Rancher 可以管理k8s集群,k8s可以管理容器
但公司是用Argo Rollouts实现滚动发布的。
dependency-scan项目作为一个定时的微服务项目,每十五天(也可以通过https://srv-dev.app.ikea.cn/dependency-scan/scanAll
接口)异步执行scanAllProjectOneByOne()
方法,其作用是扫描所有团队所有项目的依赖包是否与parentPom的版本一致,扫描结果存在pgsql里,数据库环境等参数均是通过nacos配置中心配置的。
https://srv-dev.app.ikea.cn/parentpom/scan
接口同步执行scanParentPomDependencies()
方法,作用是扫描父类依赖
微服务中存在生产者和消费者。在dependency-scan项目中,消费者是ScanController
类
@value 注解需要用在注册在spring中的类,如@Component。并且该注解是在 bean 实例化后,在属性填充过程中进行赋值的,static初始化要早于@value。并且该注解因此,@value不能给静态变量赋值,那么有三种另辟蹊径的初始化静态变量的方法:
- @value 用于
setParams(T params)
方法,并且该方法的内容是把 params 赋值给静态变量。 - @value 用于临时变量,@PostConstruct 用于init()方法,该方法将临时变量赋值给静态变量。
- 实现 InitializingBean 接口并重写
afterPropertiesSet()
方法,方法内容同上
@NacosValue 可以实现参数的自动刷新.
@PostConstruct 被注解的方法,在对象加载完依赖注入后执行。
在跨平台,路径分隔符最好使用File.separator
项目里面一环套一环!!
invoker 执行 dependency-check 库中的maven内部插件(二次开发的插件),相当于执行maven命令,解析maven依赖,调用InvocationOutputHandler
方法得到相应输出。再解析输出的字符串,最后得到版本号
用户与服务器之间之间交互的流量叫南北向流量,内部服务器之间的流量叫东西向流量,南北向和东西向所用的网关分别是:
- 南北向:kong(基于Nginx+Lua模块的API网关)
- 东西向:isitio
CI/CD:
- CI: Github Action
- CD: Argo CD
镜像和容器的区别:容器是镜像实例化后的对象,容器是基于镜像创建的。镜像只读,容器可读可写
数据库容器(例如mysql,pgsql)需要持久化到本地数据卷(volume)中
像redis,pgsql,mq都可以安装在docker里!再将每个容器映射到本地的特定端口!一个容器最好只提供一个服务!
第一次开发流程如何配置本地环境及参数:
虽然配置文件激活的是local环境,但是部署到云环境中时是以命令行的形式激活prod环境,因此并不需要修改参数文件里的任何内容。接着就是在本地nacos即(localhost:8848/nacos)中创建相应namespace和group。具体步骤可参考这里
nacos作为配置中心有很多好处,例如开发者访问不到生产环境数据库的账号密码,合理控制权限。
bootstrap和application加载顺序:
bootstrap.yml 是被一个父级的 Spring ApplicationContext 加载的。这个父级的 Spring ApplicationContext是先加载的,在加载application.yml 的 ApplicationContext之前。
sql,redis等数据库在docker中运行需要将数据持久化至本地。也可以挂载配置文件,如redis
- pgsql容器内持久化地址:
/var/lib/postgresql/data
- redis容器内持久化地址:
/data
@Bean注入要放在@Configuration里
早期dubbo是用redis做注册中心
对gradle依赖的处理是直接调用接口,具体的接口详见这里
dependency-scan 项目流程:(手动发送请求获得所有父类依赖版本的前提下)
- 项目启动时会自动从json文件(TODO:数据库)中加载待处理的repo,存入list中
- 手动发送请求触发任务后,遍历list,对于每一个repo,先记录一个log,接着clone目标仓库,并通过目标仓库中的根目录中是否存在pom.xml文件判断是maven项目还是gradle项目,进入不同的扫描流程
- 删除本地缓存
- 如果扫描出错,则根据错误类型选择是否重试,如果是GradleConnectionException则重试并加入retrylist中并且system.gc()释放一些内存(原因,经测试太过卡顿)
- 所有项目结束后再对retrylist进行递归重试。
递归方法retry()
伪代码:
public int retry(List list, int times){
if(times >= 3) return 0; // log.info(...)
int succ = 0;
for(item : list){
boolean result = scan(item);
if(result){
succ++;
delete item.id from list;
}
else retryList.add(id);
}
if(retryList > 0) succ += retry(list, times + 1);
return succ;
}
获取gradle项目依赖的流程:
- 检查
gradle.properties
配置文件是否存在,若存在则追加模板配置文件的内容,若不存在则生成模板配置文件。此步骤的目的是统一不同repo直接的变量名。 - 通过
ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(new File(path)).useGradleVersion("7.3").connect()
创建连接 - 通过
eclipseProject = connection.getModel(EclipseProject.class);
得到模型 - 最后直接通过
dependencies = eclipseProject.getClasspath().stream().map(GradleDependencyUtils::gradleModuleVersionToDependency).filter(Objects::nonNull).collect(Collectors.toList());
即可得到项目中所有依赖的名称和版本。
获取maven项目依赖的流程:根据已有的maven插件进行开发,调用InvocationOutputHandler
接口获取控制台输出内容,接着根据规律,找到maven项目依赖的名称与版本,存入数据库中
一般来说,k8s里的每个pod存储一个container,即单容器Pod。
除非应用耦合特别严重,一般不使用多容器Pod,一个Pod内的容器共享IP地址和端口范围,容器之间可以通过 localhost 互相访问。多容器Pod的使用场景及通信:
- Sidecar container 帮助主容器,例如构建日志监视器,加载文件和所需数据
- 代理,桥接和适配器 使主容器与外部世界联通,比如Apache http服务器或者nginx可承载静态文件.也可以做为一个web的反向代理服务器
http
和 https
,睁大眼睛分辨一下!!!!
为了统一接口规范,使用OpenAPI3的gradle插件导出yaml格式的文件(默认为json格式) OpenAPI3是一种规范,而swagger3是使用这种规范的工具。
日志:
应用中不可直接使用日志系统(log4j,logback)中的API,而应该依赖使用日志框架中的API,使用门面模式的日志框架,有利于保证日志处理方式统一。强制:门面 → slf4j , 日志框架 → logback
为什么需要日志门面:为了在应用中屏蔽掉底层日志框架的具体实现。这样的话,即使有一天要更换代码的日志框架,只需要修改jar包,最多再改改日志输出相关的配置文件就可以了。这就是解除了应用和日志框架之间的耦合。换言之,门面日志天然兼容了多种日志框架的。所以,底层框架的更换,日志门面几乎不需要改动。总的来说还是一个解耦的作用。
gradle配置lombok
需要在依赖加入以下两条,而不是一条:
- compileOnly 'org.projectlombok:lombok'
- annotationProcessor 'org.projectlombok:lombok'
- prod(Production environment):生产环境,面向外部用户的环境,正式环境,连接上互联网即可访问。
- sit(System Integration Test environment): 系统集成测试,开发人员自己测试流程是否走通。
- uat(User Acceptance Test environment): 用户验收测试环境,用于生产环境下的软件测试者测试使用。
- test: 测试环境,外部用户无法访问,专门给测试人员使用的,版本相对稳定。
- pre :灰度环境,外部用户可以访问,但是服务器配置相对低,其它和生产一样,外部用户可以访问,版本发布初期,正式版本发布前。
- dev (Development environment) : 开发环境,外部用户无法访问,开发人员使用,版本变动很大。
- fat (Feature Acceptance Test environment) : 功能验收测试环境,用于软件测试者测试使用
openapi-generator可以将yaml/json文件反向生成JAVA代码。
- @JsonProperty用法:该注解用于属性上,将属性的名称序列化另外一个名称。
- @JsonGetter/@JsonSetter用法:只能用在Getter/Setter方法上,用来反序列化为指定字段名和序列化时为指定字段名。
- @JsonIgnore:序列化时忽略该注释修饰的属性
Learned: 从英文官方文档中学习的能力更强了。
简单来说,是一种规范。
修改Dockerfile和action里的workflow.yml文件,使接口文档自动更新上传至allen网站。
Actions流程:
- 登录Artifactory,下载并配置Docker环境
- 根据Dockerfile内容生成新的openapi.yaml文件并将整体打包成镜像,上传至jfrog镜像库
- fetch镜像库里的相同镜像并生成容器,将容器里新生成的接口文档复制到action的runner里
- 提交修改,commit至原repo
Github Actions: 若要执行其他分支的workflow,那么需要在默认分支下有相同名称的workflow文件。
- variables:
$GITHUB_REF_NAME
/${GITHUB_REF_NAME}
- context:
${{secrets.RT_CDH_USERNAME}}
Revert, reset的区别:
- git revert是复原某次commit修改的内容
- git reset --soft //回退到指定commit,该commit之后的提交内容,保留工作目录,并把重置 HEAD 所带来的新的差异放进暂存区
- git reset --hard // 回退到指定commit,该commit之后的提交内容,工作区和暂存区的内容都被抹掉
- git reset 或 git reset --mixed // 会把差异放到工作区
apikey是一个密钥,通常用于只调用某个服务的情况,比如使用Google Translation的API。对方给予开发者apikey,开发者便能通过该key调用对方的api。当不需要身份认证时,apikey是更好的选择。
apikey支持header、body和query三种认证方式。
docker多阶段构造:
多次使用from声明,每次FROM指令可以使用不同的基础镜像,并且每次FROM指令都会开始新的构建,我们可以选择将一个阶段的构建结果复制到另一个阶段,在最终的镜像中只会留下最后一次构建的结果。
dockerfile常见命令:
- FROM: 用于指定基础镜像
- RUN: 在当前镜像的顶层上新建层执行命令,同时提交执行结果。提交的结果会在接下来的dockerfile处理。
- ENTRYPOINT/CMD: 通过CMD指令来指示docker run命令运行镜像时要执行的命令。Dockerfile只允许使用一次CMD命令,且在脚本末尾,可被覆盖。组合使用ENTRYPOINT和CMD, ENTRYPOINT指定默认的运行命令, CMD指定默认的运行参数。 当组合使用时,确保一定用的是Exec表示法。例如
ENTRYPOINT ["/bin/ping","-c","3"] CMD ["localhost"]
- ARG/ENV: 设置环境变量,但ARG只作用于镜像构建阶段,ENV一直保留在镜像中。
- ADD/COPY: 把目标文件夹的内容添加至镜像中。例如,
ADD . /app/user
。COPY指令和ADD指令的唯一区别在于:是否支持从远程URL获取资源。ADD是支持的,COPY不支持。 - EXPOSE: 对外暴露内服务的端口,比如spring项目是8808(如果在application里改port的话,则不同),mysql是3306
镜像默认存放位置:/var/lib/docker
Github Action Runner:
把runner想象成一个文件夹,repo里的所有文件都在该文件夹下,像拉取镜像、生成容器等操作并不会改变当前文件夹里的内容,新增任意文件并执行action的commit则会同步到repo。总之,任意runner文件夹的文件修改commit的话会同步到repo。
SSO(Single sign-on)是一种思想,其实现的框架(或者说解决方案)是CAS(Central Authentication Service)
自定义注解的使用场景:根据反射获取注解的内容,从而进行拦截;与切面结合使用。
元注解:负责注解其他注解的注解
@Documented
:会被 JavaDoc 工具提取成文档@Target
:描述了注解修饰的对象范围- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述方法变量
- TYPE:用于描述类、接口或enum类型
- FILED: 用于成员变量(包括枚举常量)
@Retention
:表示注解保留的时间- SOURCE:在源文件中有效,编译过程中会被忽略,做一些检查性的操作,比如 @Override 和 @SuppressWarnings
- CLASS:随源文件一起编译在class文件中,运行时忽略,比如生成一些辅助代码(如 ButterKnife)
- RUNTIME:在运行时有效,在这个定义下,才能通过注解反射获取到注解
@Inherited
:指定该注解可以被继承,某个类被该注解修饰的注解修饰,则其子类将自动具有该注解。@Repeatable
:它允许在相同的程序元素中重复注解@Native
:修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用,不常使用。
dependencyManagement 和 dependencies 的区别:
- dependencyManagement:声明依赖(及版本),一般用于父Pom,子模块需要依赖使用dependencies引入依赖即可,但如果不写版本则由dependencyManagement决定,可避免无效继承
- dependencies:引入依赖,父或子Pom都可用,子模块默认引用该依赖
maven同一个项目一个子模块引用另一个子模块中类的方法:
被引用子模块中添加版本号,引用模块中的dependency添加该模块 ,其scope须指定为compile,参考这里
构造块赋值、直接给成员变量赋值和通过构造方法赋值:
- 前两者赋值方法(构造块赋值、直接赋值)的顺序与代码顺序是对应的
- 构造方法赋值永远是最后执行的,即如果有构造方法赋值,最后结果就是赋该值。
Springboot底层默认采用jackson序列化。布尔变量的名称不能以is开头,否则会造成序列化解析错误。
Springboot2.4版本后,默认不自动加载bootstrap.yml!!!!解决方案
加载先后顺序:logback.xml -> application.properties -> logback-spring.xml -> application.yml
自动装配:多用@RequiredArgsConstructor修饰类代替@Autowired修饰成员变量,但成员变量需要用final修饰
RPC(Remote Procedure Call)远程过程调用协议。rpc用于内部各微服务之间的函数调用,http一般用于api接口与前端交互
微服务调用结构:
- api(Interface): 提供接口的url,方法名和其他(Springdoc),例如
public interface CustomerApi {
@GetMapping("/xx")
Result<CustomerVo> getCustomerByCid();
}
- client(Interface): 继承api接口并开启@FeignClient,内容为空。(子接口继承父接口,默认获得父类所有方法。)好处
@FeignClient(name = "family-customer-client", url = "${family.customer.url}")
public interface CustomerClient extends CustomerApi {
}
- controller(Class): 继承api接口并实现。例如,
public class V2CustomerController implements CustomerApi {
public Result<CustomerVo> getCustomerByCid() {
return Result.ok();
}
}
Mybatis-plus并不会自动生成mapper.xml文件,plus是mybatis的一个增强版,至于mapper.xml的生成,还是需要借助mybatisX或generator来生成。
Oauth2.0 四种授权模式
springcloud-gateway拥有路由转发的作用,通过负载均衡的方式将请求转发至不同微服务端口的接口。
SPI有待学习!
IDEA中libraries和dependency的区别:
- libraries的jar包可供整个project选择
- dependency的jar包只是提供给单个module
bean的自动装配,先记录一下。
当项目需要导入本地jar包做测试用时,在idea中的project structure的modules中点击加号键即可导入本地jar包,前提是pom文件里需要写该依赖的groupid等内容。使用<scope>system</scope>
的方式指定路径时,将不会引入本地jar包的所有依赖。因此还是使用前者的方法会比较好。
基于Logbook和logstash,重写formatter和writer进行二次开发,实现Log日志统一方案。在实现过程中,最好按着源码的思想来写,不能使用@Slf4j(默认调用LoggerFactory.getLogger(getClass())
),要使用LoggerFactory.getLogger(Logbook.class)的方法去调用日志类,这样能做到控制台输出统一为org.zalando.logbook
,方便logstash收集日志,否则会输出当前类。
ElasticSearch, Logstash, Kinaba:(ELK)
ELK实现的日志采集的核心是,通过 logstash 将应用系统的日志通过 input 收集,然后通过内部整理,通过 output 输出到 Elasticsearch 中,其实就是建立了一个 index,然后 kibana作为可视化平台,将 ES 的index进行输出到平台,通过图表的方式进行展示。
使用 @ConditionalOnClass, @ConditionalOnMissingBean
等注解修饰类或方法时是满足一定条件的情况下才生效,生效针对类来说是执行无参构造方法(在autoconfiguration类中,.class文件中会自动编译生成无参构造方法),针对方法来说是执行该方法。
@AutoConfiguration
自动配置的顺序:
- 先按照配置类的全类名字符串进行排序
- 再按照配置类上的
@AutoConfigureOrder
注解排序 - 最后按照
@AutoConfigureBefore
,@AutoConfigureAfter
两个注解排序
mysql5.7 timestamp格式下无法设置默认为null。解决方案:在配置文件中修改sql_mode以及新增explicit_defaults_for_timestamp = ON
JWT其实不需要在后端存储!!!
第一个项目中,我把token存到redis中,用redis的过期时间来验证token的有效性,但实际上将过期时间放入token中,每次验证是否过期,那么后端就不再需要存储token了。这也是第一个项目的不足之处。
使用scanBasePackages时如果添加别的包,那么还需要添加自己的本包!!!因为默认如果不指定scanBasePackages,默认会扫描当前启动类所在的包路径以及子包的路径,如果指定后默认这个将不会再起作用,所以指定时,要考虑扫描的所有的需要spring管理的类
Spring会扫描@Configuration下的所有bean并加载,并且autoconfiguration只扫描jar包的所有配置,而不扫描本地package
autoconfiguration和configuration的区别:这里
redis的两种序列化策略。一种是String的序列化策略,一种是JDK的序列化策略。
总共有以下几个序列化类:
- Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSerializer实际上是一样的
- JacksonJsonRedisSerializer: 序列化object对象为json字符串
- JdkSerializationRedisSerializer: 序列化java对象(被序列化的对象必须实现Serializable接口),无法转义成对象。
- StringRedisSerializer: 简单的字符串序列化
- GenericToStringSerializer:类似StringRedisSerializer的字符串序列化
- GenericJackson2JsonRedisSerializer:类似Jackson2JsonRedisSerializer,但使用时构造函数不用特定的类参考以上序列化,自定义序列化类。
maven常见命令:
- maven clean。对项目进行清理,清理的过程中会删除删除target目录下编译的内容。
- maven compile。编译项目源代码。
- maven test。对项目的运行测试。
- maven packet。可以打包后的文件存放到项目的 target 目录下,打包好的文件通常都是编译后生成的class文件。
- maven install。在本地仓库生成仓库的安装包可以供其他项目引用,同时打包后的文件存放到项目的 target 目录下。
对项目打包有三种打包方式,pom打包,jar包和war包。打包方式在pom.xml文件中进行指定。
pom工程一般是聚合工程,代表父工程,负责管理jar包的版本、maven插件的版本等,主要做统一的依赖管理。
jar包就是普通的打包方式,可以是pom工程的子工程。
Spring Boot 3.0 Migration Guide
Springboot 3.0.x 使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
而不是 spring.factories
进行自动装配!!!!气死我了!!
gradle clean xxx命令是先clean build文件夹再进行xxx操作。
@ResponseStatus
用于修改response中的 Status Code
。一切和Spring有关的组件一定要让Spring扫描到!!!
另外该注解通常用于修改捕获全局异常后的状态码!以此遵循RESTful API规范!
docker中容器的启动顺序和容器内分配到的ip有关系,先启动的先分配ip地址!
如果修改 import static
导入的变量发生了变化,需要进行重新编译才可以生效。比如,
String TEST = test
改为 String TEST = test1
,那么需要在所有用到 TEST
变量的文件里修改内容使其重新编译生成文件!!!这很重要!!否则不会文件内容不会发生变化!!!所以教训就是不要轻易修改静态变量的值!
RPC(Remote Procudure Call)远程过程调用协议,采用客户端/服务端的模式,通常包含传输协议和序列化协议。
- 传输协议:gRPC使用http2协议,dubbo使用自定义报文的tcp协议。
- 序列化协议:基于文本编码的xml json,也有二进制编码的protobuf hessian。
简单来说,RPC的目的是做到不同服务间调用方法就像同一服务间调用本地方法一样。
rpc和http的关系? rpc可以通过http协议实现,也可以通过tcp协议实现,并没有一个明确的关系。