能力说明:
精通JVM运行机制,包括类生命、内存模型、垃圾回收及JVM常见参数;能够熟练使用Runnable接口创建线程和使用ExecutorService并发执行任务、识别潜在的死锁线程问题;能够使用Synchronized关键字和atomic包控制线程的执行顺序,使用并行Fork/Join框架;能过开发使用原始版本函数式接口的代码。
暂时未有相关云产品技术能力~
今日下午,因给业务部门演示一个小功能点的使用,由于该功能数据异常未能达到预期效果,而终止了演示,并且叫开发人员进行数据的可靠性进行自查,同时回到工位后的我也打开了电脑去查看数据,发现数据并未被定时跑批或是跑批终止,于是上线拉取关键日志,原定15分钟定时执行的任务,却并没有执行。难道是定时任务出问题了?项目背景由于是单体应用部署多个节点,并没有使用XXL-JOB这种,为了控制定时任务多节点只能一次执行,采用了SchedulerLock的方式(基于分布式锁)来实现定时任务的执行。一开始怀疑Redis分布式锁出现了死锁问题,导致定时任务无法抢占到锁资源,没有执行定时任务,但在我观察了分布式锁后,发现并没有问题,而且这个方案已经上线2个多月,如果有问题早发生了。此时我观察了下生产2台服务器的CPU,均达到了100%,此时我知道大概率是代码存在死循环了,为了搞清真相,开始排查问题原因。这里我采用2种方式去排查jstack命令(网上非常多的吹牛案例均采用此方案)Arthas工具(阿里开源诊断工具)定时任务代码如下,此处代码并无问题 /** * 潜客分配每15分钟执行一次 * * lockAtMostFor:最长释放时间 * lockAtLeastFor:拥有锁的最小时间 */ @Scheduled(cron = "0 0/15 * * * ?") @SchedulerLock(name = "startDistribution", lockAtMostFor = "20M", lockAtLeastFor = "12M") public void startDistribution() throws Exception { log.info("【分配潜客名单】开始执行"); //业务代码 log.info("【分配潜客名单】执行结束"); }Jstack排查使用top命令观察CPU占用高的进程根据进程ID进一步查看占用线程# 命令:top -H -p PID $ top -H -p 1379将线程ID转换为16进制串输出(用于抓取线程ID堆栈信息)# 命令 printf "%x\n" 线程ID $ printf "%x\n" 1449 5a9使用jstack命令抓取堆栈信息(利用16进制)# 命令:jstack 进程ID | grep 线程ID16进制 -A行数 $ jstack 1379 | grep 5a9 -A90输出结果:[tfuser@web01 root]$ jstack 1379 | grep 5a9 -A90 "Job-Thread-3" #29 prio=5 os_prio=0 tid=0x00007f6ec6aee000 nid=0x5a9 runnable [0x00007f6e351f5000] java.lang.Thread.State: RUNNABLE at com.tifang.market.service.impl.MarketWeightServiceImpl.startDistribution(MarketWeightServiceImpl.java:154) at com.tifang.market.service.impl.MarketWeightServiceImpl$$FastClassBySpringCGLIB$$3b5113e6.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:684) at com.tifang.market.service.impl.MarketWeightServiceImpl$$EnhancerBySpringCGLIB$$53eb6d9c.startDistribution(<generate at com.tifang.core.quartz.MarketWeightTask.startDistribution(MarketWeightTask.java:62) at com.tifang.core.quartz.MarketWeightTask$$FastClassBySpringCGLIB$$d4f16575.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at net.javacrumbs.shedlock.spring.aop.MethodProxyScheduledLockAdvisor$LockingInterceptor$$Lambda$806/1180241420.call(U at net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor.executeWithLock(DefaultLockingTaskExecutor.java:73) at net.javacrumbs.shedlock.spring.aop.MethodProxyScheduledLockAdvisor$LockingInterceptor.invoke(MethodProxyScheduledLo at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) at com.tifang.core.quartz.MarketWeightTask$$EnhancerBySpringCGLIB$$cceb1e7a.startDistribution(<generated>) at sun.reflect.GeneratedMethodAccessor3355.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)到此,我们已经获取到了详细的信息,此时我们应该转场到Java代码中去查看该行代码...其实代码中俨然已经存在了问题,不知道大家能不能一眼看出,接下来我们再用神器Arthas来排查阿里Arthas阿里Arthas号称Bug排查神器,功能非常强大,此文我不做过多介绍,后续有时间单独拎一篇文章对其详细讲解。这里,我只用到了2个命令就能定位到错误问题代码位置。在服务器上下载arthas-boot.jar$ wget https://arthas.gitee.io/arthas-boot.jar授予执行权限$ chmod 777 ./arthas-boot.jar使用生产服务采用同一用户启动arthas,并选择对应的生产服务$ java -jar arthas-boot.jar [INFO] arthas-boot version: 3.3.9 [INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER * 1: 1944 /data/app/tfteacher/tfteacher.jar 2: 8349 /data/app/tfoaserver/tfoaserver.jar选择对应的生产服务,这里我需要调试第二个java服务,所以我输入2,接下来会进入到arthas的用户进程命令中2 [INFO] local lastest version: 3.3.9, remote lastest version: 3.4.0, try to download from remote. [INFO] Start download arthas from remote server: https://arthas.aliyun.com/download/3.4.0?mirror=aliyun [INFO] Download arthas success. [INFO] arthas home: /home/tfuser/.arthas/lib/3.4.0/arthas [INFO] Try to attach process 8349 [INFO] Attach process 8349 success. [INFO] arthas-client connect 127.0.0.1 3658 ,---. ,------. ,--------.,--. ,--. ,---. ,---. / O \ | .--. ''--. .--'| '--' | / O \ ' .-' | .-. || '--'.' | | | .--. || .-. |`. `-. | | | || |\ \ | | | | | || | | |.-' | `--' `--'`--' '--' `--' `--' `--'`--' `--'`-----' wiki https://arthas.aliyun.com/doc tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html version 3.4.0 pid 8349 time 2020-09-03 21:09:41 [arthas@8349]$ //此处的用户发生变化,变为了arthas重点来了,这里我输入thread来查看当前服务的线程情况[arthas@8349]$ thread可以看到,上图ID为22的线程,占用着99%的CPU资源,并且已经持续运行654分钟,太吓人了,10多个小时了此处可以输入很多命令,详见arthas教程打印出线程ID为22的详细信息,看看究竟干了什么见不得人的事[arthas@8349]$ thread 22 "Job-Thread-1" Id=22 RUNNABLE at com.tifang.market.service.impl.MarketWeightServiceImpl.startDistribution(MarketWeightServiceImpl.java:154) at com.tifang.market.service.impl.MarketWeightServiceImpl$$FastClassBySpringCGLIB$$3b5113e6.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:684) at com.tifang.market.service.impl.MarketWeightServiceImpl$$EnhancerBySpringCGLIB$$48c8daec.startDistribution(<generated>) at com.tifang.core.quartz.MarketWeightTask.startDistribution(MarketWeightTask.java:62) at com.tifang.core.quartz.MarketWeightTask$$FastClassBySpringCGLIB$$d4f16575.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)再结合日志文件进行排查,但只有开始日志,没有结束日志,日志抓取输出如下:$ grep -A 20 -B 10 -i "分配潜客名单】" info.log该项目是10:00~22:00执行,每15分钟执行一次,初步估计10:45是该节点第一次获取到redis任务锁,第一次执行,在进入到方法后进行了死循环,导致一直没有打印结束日志到此,我们继续回到项目代码中,找到MarketWeightServiceImpl的154行一般造成CPU过高的原因大多数是死循环,通过这个思路,其实我们可以看出,假定业务逻辑没有问题的情况下,这里的单break并不能跳出双层循环问题定位到了,改造代码提交至master,通过jenkins再次发布生产环境通过观察生产环境的CPU,未再次发现问题,业务正常运转总结以上只是使用arthas最基础的功能进行线上问题排查,arthas还有很多功能强大的指令供我们使用。在没有arthas之前,我们只能使用jvm提供的指令进行排查,过程复杂很容易错过生产事故的第一现场,arthas的出现极大的提升问题排查的效率,但arthas也有不足的地方,对于tomcat的web服务监听似乎就比较局限了。
GitHub是我们经常使用的网站,小白是这样查看源码的,一层层点开,响应慢不说,还没有代码结构,头发就是这么没有的Sourcegraph使用Sourcegraph插件后,领导再也不用担心我的CV大法,安装插件后的效果图在线安装(有chrome商店的同学)进入Chrome插件中心,浏览器输入 chrome://extensions/打开开发者模式开关离线安装(无chrome商店)下载我提供的插件安装包,【自学编程站】回复【github插件】获取,解压放到某个盘下,一定要解压,比如我的路径:打开浏览器插件中心,打开 开发者模式,选择 加载已解压的扩展程序,即可完成安装。安装成功后的界面打开一个仓库看看效果工具解析Sourcegraph主页开启大小写敏感、开启正则表达式开启长行模式下载当前查看的文件查看历史提交记录永久链接(具有完整的git提交SHA)切换回github网页Octotree使用Octotree插件后,工作(copy)效率翻倍提升,安装插件后的效果图在线安装(有chrome商店的同学)进入Chrome插件中心,浏览器输入 chrome://extensions/打开开发者模式开关离线安装(无chrome商店)下载我提供的插件安装包,【自学编程站】回复【github插件】获取,解压放到某个盘下,一定要解压,比如我的路径:打开浏览器插件中心,打开 开发者模式,选择 加载已解压的扩展程序,即可完成安装。安装成功后的界面,左侧目录树github1s绝对相见恨晚,无插件、任何电脑,你只需要在github的浏览器地址栏,在github后面加1s即可如你访问的github地址是:https://github.com/ovity/octotree ,你只需要把地址换成:https://github1s.com/ovity/octotree
项目场景:记一次Dockerfile构建的Docker镜像,启动容器时sh: not found的问题Dockerfile构建的Docker镜像,启动容器时找不到start.sh,执行docker run 命令时报错:/bin/sh: 1: /data/server/start.sh: not found问题描述Dockerfile如下(脚本正确无内容错误)FROM openjdk:8 MAINTAINER it235.com # 环境sit/pro,由gradle传入 ARG env ENV env ${env} WORKDIR /data/server #应用包 COPY order.jar start.sh arthas-boot.jar /data/server/ RUN sh -c 'mkdir order' &&\ sh -c 'mkdir logs' &&\ sh -c 'mkdir order/gc' &&\ sh -c 'chmod -R 755 order/' &&\ sh -c 'chmod -R 755 logs/' &&\ sh -c 'chmod 755 start.sh' &&\ ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone #启动容器 ENTRYPOINT ["/bin/sh","-c","/data/server/start.sh ${env}"] EXPOSE 80造成原因造成这个问题的原因主要有以下几点:docker中的文件夹没有获得权限start.sh文件不在指定的目录下start.sh没有权限build image时没有把该脚本传入start.sh文件中内容错误start.sh中windows和unix的CRLF和LF的换行符问题start.sh本身换行符没问题,但是提交Git后由于git的设置导致文件换行符发生变化最终发现我这里是第6点问题,坑爹,这里针对以上6点分别给出解决方案。解决方案原因1:Dockerfile中给权限即可,我这里已经授权原因2:在启动容器报错后使用 docker cp 容器名称:/data/server/start.sh ./命名拷贝到本地宿主机目录,如果能够拷贝出来,说明文件位置正确原因3:Dockerfile中给权限,给到最高777原因4:排查方式同原因3,注意build image时时一定要在Dockerfile当前上下文中,maven和gradle项目可以用插件来做,可以看我的文档进行,文档地址看上面Dockerfile第2行原因5:构建镜像之前,先自己在linux上运行下,保证内容正确原因6:这点和原因7一样坑爹,一般情况下你根本不会考虑到这个问题,这个问题也非常好改,但是排查就要废点心了。以IDEA编辑器为例问题6我们是解决了,但是怎么排查出来是这个问题的呢,我们可以使用上述原因2的命令docker cp把start.sh从容器中拷贝出来(停止的容器也可以),然后使用命令cat -A start.sh,如果你命令后面跟的是$美元符号,那么恭喜你,文件是LF换行的,如果跟的是^M\$符号,那么不好意思,是CRLF换行符,以下2张图给你做个对比。原因7跟原因6一样吧好了,就到这里,做个小小的总结,给个爱心让更多小伙子看到吧
快速搭建演示环境说明git: 能访问github/gitee即可node: v10.15.3npm: 6.14.8# npm设置淘宝源 npm config set registry https://registry.npm.taobao.org npm config set disturl https://npm.taobao.org/dist # 打开cmd 全局安装yarn $ npm i -g yarn # 设置yarn淘宝源,使用npm的同学,也是可以设置淘宝源 $ yarn config set registry https://registry.npm.taobao.org $ yarn config set disturl https://npm.taobao.org/dist $ yarn config set sass-binary-site https://npm.taobao.org/mirrors/node-sass克隆$ git clone https://github.com/it235/it235-vuepress.git删除it235-vuepress目录下的.git目录,并将it235-vuepress改名为你自己的目录构建# 打开CMD命令行窗口 $ cd 你自己改名后的文档目录 # 安装vuepress 安装过程稍慢,如果卡住,按空格键继续 $ yarn add -D vuepress 或 npm install -g vuepress $ yarn docs:dev 或 npm run docs:dev服务启动成功后,会输出访问地址,如http://localhost:7777,如果你能正常访问到,那么就可以开始发布了打包成html$ yarn docs:build 或 npm run docs:build执行完成后,会在当前目录下生成dist目录,这里大家要注意了,直接访问dist目录下的html会提示静态资源找不到,不要担心。发布到Gitee、Github注意:gitee pages需要实名认证申请发布到github.io创建对外展示仓库,该仓库与内容开发的仓库不一样,大家都按照用户名.github.io的格式创建吧,我这里命名是it235.github.io创建好后,不用clone到本地,只需要往这个项目中发布dist目录即可接下来我们将npm run docs:build生成dist拷贝到指定目录下,使用如下命令进行发布# 注意不要放在开发目录下,因为开发目录下已经有.git版本控制了,需要拷贝到外面 # 进入到dist目录 $ cd dist # git初始化 $ git init $ git add -A $ git commit -m "first commit" $ git branch -M main # 注意此处的格式是:git push -f git@github.com:USERNAME/USERNAME.github.io.git main $ git push -f git@github.com:it235/it235.github.io.git main注意:仓库文件推送成功后,Pages中的站点自动开通,如果你的仓库名不是用户名.github.io,则需要你手动选择分支后进行Save,如下:以上,是我们手动push操作完成,接下来我们可以把发布动作做成自动的脚本,并配合npm命令完成在开发根目录下创建deploy.sh,用于发布dist网页到github.io,脚本内容如下#!/usr/bin/env sh # 确保脚本抛出遇到的错误 set -e # 生成静态文件 , yarn docs:build npm run docs:build rm -rf ../blog/dist/* # 将build生成的dist目录拷贝至上一层目录中 cp -rf dist ../blog/ # 进入生成的文件夹 cd ../blog/dist # git初始化,每次初始化不影响推送 git init git add -A git commit -m 'deploy' git branch -M main # 如果你想要部署到 https://USERNAME.github.io git push -f git@github.com:it235/it235.github.io.git main修改package.json文件"scripts": { // 君哥给的源码中已经添加 "deploy": "bash deploy.sh" }打开gitbash,类unix命令窗口,执行npm run deploy或yarn deploy静态资源坑(可跳过)注意:遇到的同学可以参考,未遇到的同学直接跳过本节发布到github.io后,你会发现,文章内容中的图片无法加载,这就比较难受了。这里的静态资源有2类:第一类是属于.vuepress下的第二类是属于我们文章内容中的图片这里第二类是我们关心的,且处理难度比较大。我们针对第二类进行讲解vuerpesss建议将静态资源存放到.vuepress\public下,但是我们在写博客时不可能将当前文章的图片拷贝到其他目录下,我们建议属于什么模块的图片存放到什么模块下。这里有3种解决方案采用图床,不建议使用gitee等一些源码或博客平台图床,有防盗链处理,推荐使用其他云对象存储或CDN采用君哥推荐的做法,植入JS脚本,动态修改img.src找到君哥给大家准备好的custom.js文件,在loadSidebar函数下增加函数,并在vueSidebarShow函数中调用该函数//函数内容如下 function gitHubIoImg() { var imgs = document.querySelectorAll('p img'); let origin = location.origin; for (var i=0; i< imgs.length; i++) { let img = imgs[i]; console.log(img.src); var b = img.src.substr(img.src.lastIndexOf('/assets'), img.src.length); let newsrc = origin + b; console.log(newsrc); } }开发源码上传至Gitee友情提醒:当前仓库名称非常重要,建议与您的个人空间地址名一致,如果不一致,gitpages访问的url就会变化成https://xx.gitee.io/项目名称,与前面我讲的github一样,逼格不高也不太好看,同时还需要开启base目录在码云创建仓库将从github克隆下来的代码,删除.git重命名后关联并推送到gitee仓库git init git add -A git commit -m "first commit" //注意,这里我使用的是ssh的方式 git remote add origin git@gitee.com:it235/it235-vuepress-gitee.git git push -u origin "master"参照博客搭建环节安装vuepress依赖包,生成node_modules库使用build命令,创建dist目录,然后一起push上去,后续更新文章继续使用build命令进行打包发布到gitee.io执行deploy_gitee.sh脚本即可推送到gitee,前提是已经创建和关联过gitee的远程仓库#!/usr/bin/env sh # 确保脚本抛出遇到的错误 set -e # 生成静态文件 , yarn docs:build npm run docs:build # git初始化,每次初始化不影响推送 git add -A git commit -m 'deploy' # 如果你想要部署到 https://USERNAME.github.io git push -u origin "master"发布到云服务器Nginx本地实验静态资源部署将dist目录发布到本地Nginx中(需要先下载nginx)带后缀的nginx配置location /dist { alias html/dist/; index index.html; autoindex on; }开启.vuepress/config.js中的basemodule.exports = { //此处需要填写你部署在nginx下的文件夹名称,如果是根目录,那么可以注释掉此行,注释掉后本地打开index.html无法访问 base: "/dist/", title: "君哥聊编程", description: '点赞、转发、收藏', dest: './dist', ... }把dist文件夹,放在nginx/html目录下**提示:dist可以换成你想要的名称,比如blog**不带后缀的nginx配置简单做法:关闭config.js中的base设置,把dist目录下的文件,拷贝至html目录下,不要保留dist目录名称复杂做法:location / { alias html/dist/; index index.html; }反向代理到服务端口location / { proxy_pass http://127.0.0.1:7777; }发布到云服务ECS服务器购买,戳我域名购买icp备案公网安备域名解析Nginx安装安装git,拉取博客代码运行博客使用Nginx反向代理映射# 进入/usr/local/nginx/conf/目录 $ vim nginx.conf # 在最后一行大括号结束之前,加入以下配置,并将it235换成你的域名 # http 请求处理 server { listen 80; server_name it235.com www.it235.com; large_client_header_buffers 4 16k; client_max_body_size 30m; client_body_buffer_size 128k; # 域名默认映射到 http://127.0.0.1:7777 location / { proxy_pass http://127.0.0.1:7777; proxy_redirect off; } }
这里我以阿里云为例,可能会收取少量费用,大家用完记得删除。注册登录阿里云,还没有账号的可以点此的链接注册进入找到控制台,搜索容器镜像服务,并创建实例,我这里选个人实例创建命名空间,这里可以用你自己的拼音,公司的建议走企业账号点击镜像仓库,创建镜像仓库,选择你刚刚创建的命名空间阿里云镜像仓库操作指南公网操作# 登录阿里云Docker Registry $ docker login --username=xxxx registry.cn-hangzhou.aliyuncs.com 用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码。您可以在访问凭证页面修改凭证密码。 # 从Registry中拉取镜像 $ docker pull registry.cn-hangzhou.aliyuncs.com/it235/it235:[镜像版本号] # 将镜像推送到Registry 请根据实际镜像信息替换示例中的[ImageId]和[镜像版本号]参数。 $ docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/it235/it235:[镜像版本号] $ docker push registry.cn-hangzhou.aliyuncs.com/it235/it235:[镜像版本号]阿里云内网操作# 选择合适的镜像仓库地址 从ECS推送镜像时,可以选择使用镜像仓库内网地址。推送速度将得到提升并且将不会损耗您的公网流量。 如果您使用的机器位于VPC网络,请使用 registry-vpc.cn-hangzhou.aliyuncs.com 作为Registry的域名登录。 #示例 使用"docker tag"命令重命名镜像,并将它通过专有网络地址推送至Registry。 $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE registry.aliyuncs.com/acs/agent 0.7-dfb6816 37bb9c63c8b2 7 days ago 37.89 MB $ docker tag 37bb9c63c8b2 registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816 # 使用 "docker push" 命令将该镜像推送至远程。 $ docker push registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816VPC是什么,为什么要重命名?修改密码
目前主流的Docker4种私有仓库docker-registry:docker hub 提供的一直私有仓库解决方案。没有图形化界面Harbor:带有图形化界面的工具,用户管理,及查看更加方便Nexus:一般我们Maven和Gradle用得比较多,管理jar包,也能存储docker镜像,由于一个版本一个版本的保存和叠加,所以清理起来比较麻烦。云平台容器服务(如阿里云,常用于生产环境)这里我主要介绍2种相对较友好的镜像私服,harbor和阿里云容器服务。系统环境说明: Centos 7 Docker Version: 20.10.14Harbor简介Harbor Registry(又称 Harbor 云原生制品仓库或 Harbor 镜像仓库)由 VMware 公司中国研发中心云原生实验室原创,并于 2016 年 3 月开源。Harbor 在 Docker Distribution的基础上增加了企业用户必需的权限控制、镜像签名、安全漏洞扫描和远程复制等重要功能,还提供了图形管理界面及面向国内用户的中文支持,开源后迅速在中国开发者和用户社区流行,成为中国云原生用户的主流容器镜像仓库。特性:基于角色的访问控制 :用户与Docker镜像仓库通过“项目”进行组织管理,用户可以对多个镜像仓库在同一命名空间(project)里有不同的权限。镜像复制 : 镜像可以在多个Registry实例中复制(同步)。尤其适合于负载均衡,高可用,混合云和多云的场景。图形化用户界面 : 用户可以通过浏览器来浏览,检索当前Docker镜像仓库,管理项目和命名空间。AD/LDAP 支持 : Harbor可以集成企业内部已有的AD/LDAP,用于鉴权认证管理。审计管理 : 所有针对镜像仓库的操作都可以被记录追溯,用于审计管理。国际化 : 已拥有英文、中文、德文、日文和俄文的本地化版本。更多的语言将会添加进来。RESTful API : RESTful API 提供给管理员对于Harbor更多的操控, 使得与其它管理软件集成变得更容易。部署方便 : 提供在线和离线两种安装工具, 也可以安装到vSphere平台(OVA方式)虚拟设备。下载安装包Harbor 提供了多种途径来帮助用户快速搭建 Harbor 镜像仓库服务,包括:离线安装包:通过docker-compose编排运行。安装包除了包含相关的安装脚本外,还包含了所有安装所需要的Harbor组件镜像,可以在离线环境下安装使用。在线安装包:与离线安装包类似,唯一的区别就是不包含harbor组件镜像,安装时镜像需要从网络上的仓库服务拉取。手动下载:官方源码戳我,找到最新Tag拉到底部进行下载,建议采用迅雷采用wget下载(较慢):wget https://github.com/goharbor/harbor/releases/download/v2.5.0/harbor-offline-installer-v2.5.0.tgz这里我们选择离线版。安装docker-composeHarbor由多个组件组成,其每个组件都是以Docker容器的形式构建的,官方采用Docker Compose来对它进行部署。用于部署Harbor的Docker Compose模板位于 harbor/docker-compose.yml中,这个模板文件中有多个镜像定义,常见如:harbor-log,harbor-db,registry,harbor-core,nginx,harbor-portal,harbor-jobservice等,版本不一样所依赖的容器熟练不一样。所以我们需要提前安装好docker compose。# 下载 http://github.com/docker/compose/releases 网络原因可能会失败 $ curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose # 也可以手动下载 https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64 # 重命名 $ mv docker-compose-linux-x86_64 docker-compose # 授权 $ chmod +x /usr/local/bin/docker-compose # 可定义全局变量或者软链接,方便执行: $ ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose # 验证 $ docker-compose --version Docker Compose version v2.5.0 # 注意如果运行docker-compose出现Segmentation fault错误,可以做如下操作 1. 重启docker服务 2. 删除docker-compose文件重新下载生产HTTPS证书由于我们是在本地或虚拟机,可以不用安装,大家也可以采用其他免费证书# 官方下载地址 https://goharbor.io/docs/1.10/install-config/configure-https/安装Harbor解压配置$ tar xvf harbor-offline-installer-v2.5.0.tgz -C /usr/local/ $ cd harbor/ # 编辑harbor.yml(老版本是harbor.cfg),修改hostname、https证书路径、admin密码(可选) # 先复制一份harbor.yml $ cp -f harbor.yml.tmpl harbor.yml $ vim harbor.yml -- 示例如下 # 用于访问用户界面和 register 服务。它应该是目标机器的 IP 地址或完全限 定的域名。例如 192.168.2.195 或 hub.it235.com。不要使用 localhost 或 127.0.0.1 为主机名。 hostname: 192.168.2.195 http: # port避免和nginx冲突,改成非80即可,本文改为9080 port: 8098 # 注意,如果不开启https,则需要把https节点的内容注释掉,否则安装会报错,在老版本中由enable属性控制 https: port: 443 # ssl证书地址 仅当协议设置为 https 时才应用。 certificate: /your/certificate/path private_key: /your/private/key/path # harbor默认用户名密码 harbor_admin_password: Harbor12345 # harborDB 用于db_auth 的MySQL数据库root 用户的密码。 database: password: root123 max_idle_conns: 100 max_open_conns: 900 # The default data volume data_volume: /data # 运行 $ sh /usr/local/harbor/install.sh 或进入到harbor目录执行 ./install.sh # 安装成功的结果 ✔ ----Harbor has been installed and started successfully.---- # 注意,如果失败,修改相关配置后再次执行./install.sh即可(一般失败原因大多是端口冲突)重启docker-compose# 关闭 $ docker-compose down -v # 启动 $ docker-compose up -d # 如果出现以下错误,请启动docker,或重启docker $ docker-compose ps no configuration file provided: not found $ systemctl start docker $ systemctl restart docker 查看harbor安装情况# 查看已经拉取的镜像 $ docker images # 查看所有容器 $ docker ps -a # 查看compose容器相关运行情况 $ docker-compose ps清空所有容器和镜像docker rm -f $(docker ps -aq) docker rmi -f $(docker images -aq)访问harbor如果使用了https出现检查证书的情况,说明证书失效或配置存在问题,可点击继续访问默认账号密码: admin/Harbor12345,可在harbor.yml中修改作为开发人员,与harbor打交道最多的就是项目模块,其他模块可以暂时忽略镜像推送在harbor创建项目找另外一台Linux机器安装docker环境配置docker的镜像仓库为harbor的机器# 注意,这里不再是其他的国内镜像仓库,而是要使用我们的私服harbor $ cat /etc/docker/daemon.json { "registry-mirrors" : ["http://hub-mirror.c.163.com"], # 国内镜像代理 "insecure-registries" : ["192.168.2.195:8098"], # harbor私服 "live-restore": true # 重载docker守护进程而不重启容器 }在我们另外另外一台机器上把harbor的密码存下来,写到一个文件中使用命令行的方式登录到harbor# 在我们另外另外一台机器上把harbor的密码存下来,写到一个文件中 $ echo Harbor12345 > /etc/docker_passwd # 登录,因为harbor对密码有一定的要求,所以要采用这种方式 $ cat /etc/docker_passwd | docker login -u admin --password-stdin http://192.168.2.195:8098 Login Succeeded # 注意:如果出现了https问题,那么请检查daemon.json配置是否与我上面描述的一致随意从官方仓库下载一个镜像$ docker pull nginx给镜像打标签# docker tag SOURCE_IMAGE[:TAG] 192.168.2.195:8098/it235/REPOSITORY[:TAG] $ docker tag redis:latest 192.168.2.195:8098/it235/nginx:v1推送到harbor仓库# docker push 192.168.2.195:8098/it235/REPOSITORY[:TAG] $ docker push 192.168.2.195:8098/it235/nginx:v1进入ui页面查看删除已存在的镜像,并从harbor拉取镜像$ docker rmi nginx $ docker pull 192.168.2.195:8098/it235/nginx:v1启动容器并验证$ docker run -d -p 8888:80 192.168.2.195:8098/it235/nginx:v1 浏览器访问:http://192.168.2.194:8888 # 注意,如果是在云服务器上,需要在安全组中配置8888端口或使用nginx代理才可以在外网访问 # 本地如果有防火墙未直接暴露8888端口,则需要将8888端口加入到防火墙中IDEA容器插件自动推送使用Maven或Gradle集成自动推送使用jenkins打包构建推送总结Harbor是目前企业中应用较为广泛的镜像仓库,虽然各云厂商推出了私有镜像仓库,但是云下的应用场景Harbor有着不可撼动的地位
官网:https://www.sonarqube.org/安装Sonar安装Docker环境# 安装docker,docker 要求操作系统必须为64位,且centos内核版本为3.1及以上 $ uname -r 3.10.0-1062.el7.x86_6 # 保证yum包是最新,使用root执行,更新到最新 $ yum update # 列出可安装的docker包 $ yum list docker-ce --showduplicates | sort -r # 1.指定版本安装 $ yum list docker-ce.x86_64 --showduplicates | sort -r # 2.安装最新版 $ yum install docker-ce -y # 查看当前版本 $ docker version # 不能连接到`Docker daemon`异常 装完后使用docker命令后会提示异常 Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? # 需要重启下docker $ service docker restart # 配置开机启动 $ systemctl enable docker配置docker源,不配置下载较慢找到/etc/docker目录下的daemon.json文件进行编辑,输入如下内容{ "registry-mirrors": ["https://9cpn8tt6.mirror.aliyuncs.com"] }如果没有该文件,可自行创建,也可以直接使用如下命令tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://9cpn8tt6.mirror.aliyuncs.com"] } EOF重启docker安装Postgres(SonarQube7.9以上版本已不再支持mysql)$ docker pull postgres # 根据镜像创建容器,并命名mypostgres $ docker run --name mypostgres -d -p 5432:5432 -e POSTGRES_PASSWORD=123456 postgres # 进入容器 $ docker exec -it mypostgres psql -U postgres -d postgres # 创建数据库,注意只需要执行语句create database sonar; postgres=# create database sonar; # 设置sonar用户名和密码 sonar postgres postgres=# create user sonar; postgres=# alter user sonar with password 'postgres'; # 给sonar授权 postgres=# alter role sonar createdb; postgres=# alter role sonar superuser; postgres=# alter role sonar createrole; # 更改sonar数据库拥有者(这一步是必须的,否则会sonarqube会连接失败) postgres=# alter database sonar owner to sonar; # 查看数据库 postgres=# \l # 查看用户 postgres=# \du exit # 重启数据库 $ systemctl start postgresql-11安装SonarQube点击下载sonar压缩包,下载Community版本# 请求root权限 $ su - # 创建目录并下载sonar压缩包 $ mkdir -p /opt/sonar $ cd /opt/sonar $ wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-8.9.0.43852.zip解压sonar,并设置权限# 安装zip命令 $ yum install unzip # 解压 $ unzip sonarqube-8.9.0.43852.zip # 创建sonar用户,必须sonar用于启动,否则报错 $ mv sonarqube-8.9.0.43852 sonarqube $ useradd sonar # 更改sonar目录及文件权限 $ chown -R sonar. /opt/sonar更改sonar配置文件$ su sonar $ vim /opt/sonar/sonarqube/conf/sonar.properties 内容如下: sonar.jdbc.username=sonar sonar.jdbc.password=postgres sonar.jdbc.url=jdbc:postgresql://47.92.230.43:5432/sonar注意:sonar默认监听9000端口,如果9000端口被占用,需要更改。下载JDK11链接: https://pan.baidu.com/s/1LbqYzeC0xO1My9hS8rTHtw 提取码: 7hbw $ tar -zxvf jdk-11_linux-x64_bin.tar.gz $ cd jdk-11/bin $ pwd /opt/sonar/jdk-11/bin # 拿到上述jdk11的路径,配置到sonar中 # 修改sonarqube/conf/wrapper.conf中的java配置 vim /opt/sonar/sonarqube/conf/wrapper.conf 将 wrapper.java.command=java 改为 wrapper.java.command=/opt/sonar/jdk-11/bin/java # 修改环境变量,添加ES_HOME vim /etc/profile export ES_JAVA_HOME=/opt/sonar/jdk-11 source /etc/profile启动sonar$ cd /opt/sonar/sonarqube $ su sonar ./bin/linux-x86-64/sonar.sh start 启动 $ su sonar ./bin/linux-x86-64/sonar.sh status 查看状态 $ su sonar ./bin/linux-x86-64/sonar.sh stop 停止 $ tail -f logs/sonar.logs 查看日志 # 启动后报错 warning: no-jdk distributions that do not bundle a JDK are deprecated and will be removed in a future release Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release. ERROR: [2] bootstrap checks failed. You must address the points described in the following [2] lines before starting Elasticsearch. bootstrap check failure [1] of [2]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535] bootstrap check failure [2] of [2]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] ERROR: Elasticsearch did not exit normally - check the logs at /opt/sonar/sonarqube/logs/sonarqube.log 2021.05.17 16:33:07 WARN app[][o.s.a.p.AbstractManagedProcess] Process exited with exit value [es]: 78 2021.05.17 16:33:07 INFO app[][o.s.a.SchedulerImpl] Process[es] is stopped 2021.05.17 16:33:07 INFO app[][o.s.a.SchedulerImpl] SonarQube is stopped 1. 编辑如下文件,加入如下内容 $ vim /etc/security/limits.conf * soft nofile 65536 * hard nofile 65536 2. 编辑如下文件,加入如下内容 vim /etc/sysctl.conf vm.max_map_count=655360 3. 执行如下命令,并退出当前用户重新登录 sysctl -p # 启动后继续报错 [es]: 143 2021.05.17 16:47:16 INFO app[][o.s.a.SchedulerImpl] Process[web] is stopped 2021.05.17 16:47:16 WARN app[][o.s.a.p.AbstractManagedProcess] Process exited with exit value [es]: 143 2021.05.17 16:47:16 INFO app[][o.s.a.SchedulerImpl] Process[es] is stopped 2021.05.17 16:47:16 INFO app[][o.s.a.SchedulerImpl] SonarQube is stopped # 查看下web.log日志,发现9000端口被占用 $ tail -222f /opt/sonar/sonarqube/logs/web.log # 查看端口占用程序 $ netstat -anp |grep 9000 tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN 9343/php-fpm: maste # kill掉这该死的进程后再次启动 $ kill -9 9343浏览器访问访问器防火墙开放9000的端口,阿里云服务器则在安全组配置即可$ firewall-cmd --zone=public --add-port=9000/tcp --permanent $ firewall-cmd --reload启动过程有点漫长,机器差的5分钟后访问该地址:http://192.168.2.195:9000/创建sonar账号默认账户:admin/admin这里我改成admin123,token要记下来后面要使用汉化搜索插件Chinese Pack,安装后会提示重启服务!获取token(后续会使用到)f3a3e01c0eb286b110152a32f6766ddea4a66d72jenkins集成sonar安装SonarQube Scanner插件添加SonarQube凭证Jenkins进行SonarQube配置系统管理->系统配置->SonarQube servers系统管理->全局工具配置->SonarQube ScannerSonaQube关闭审查结果上传到SCM功能实现代码审查在项目添加SonaQube代码审查配置文件在Java代码中src/main/resources目录下新增sonar-project.properties文件sonar-project.properties内容如下# must be unique in a given SonarQube instance sonar.projectKey=it235-sonar sonar.projectName=it235-sonar sonar.projectVersion=1.0 # modules #sonar.modules=order,goods sonar.sources=src/main/java sonar.tests=src/test/java sonar.version=1.0 sonar.language=java sonar.java.binaries=target/classes sonar.exclusions=**/test/**,**/target/** sonar.java.source=1.8 sonar.java.target=1.8 sonar.sourceEncoding=UTF-8在jenkins的job中配置sonar执行器配置好后,推送到远程仓库,构建打包,查看控制台日志是否有如下输出到SonarQube的UI界面查看审查结果目前没有发现异常信息和Bug改造代码,让其充满Bug后再次构建查看sonar报告再次构建后查看sonar报告结果总结到这里,我们把sonarqube给集成到了jenkins,非常强的检测工具,还有很多功能待大家去完善
构建发布jenkins发布项目的过程可以总结为4个步骤拉取代码编译打包(mvn或npm)部署至应用服务器(scp命令)启动应用SpringBoot项目发布准备SpringBoot项目这里我继续以上面Maven集成模块的代码为例,添加应用访问的接口和端口,并将代码提交至Gitee浏览器访问本地的服务是否启动成功提交代码并推送至Gitee(此处你也可以使用Git命令的方式)弹出登录提示,输入用户名密码在Gitee上检查到代码推送成功添加Maven风格的任务填写Gitee仓库地址填写Maven构建操作项配置Post Steps,选中执行shell思考,这里我们需要执行的shell内容是什么?是不是把当前编译好的jar包拷贝到应用服务器去启动应用服务器规划由于jenkins构建消耗内存极大,一般jenkins是一台单独的工具机器,Java项目一般在其他的机器上,这里我重新安装一台虚拟机,有条件的同学可以直接开通阿里云的ECS应用服务器信息IP:192.168.1.2JDK:1.8(安装过程省略)user:admin部署路径:/data/app/it235-boot端口:9010配置ssh免密登录免密登录主要是方便jenkins服务器的jenkins用户—》应用服务器的admin用户上的jar包拷贝,部署本就是jar包拷贝的过程在jenkins机器上使用jenkins用户生成秘钥$ su jenkins $ ssh-keygen -t rsa # 3次回车 运行后会在当前用户的根目录生成一个.ssh文件夹.ssh文件夹中的文件描述id_rsa : 生成的私钥文件id_rsa.pub : 生成的公钥文件know_hosts : 已知的主机公钥清单接下来需要将公钥导入到认证文件中$ su jenkins $ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys如果希望ssh公钥生效需满足至少下面两个条件:.ssh目录的权限必须是700.ssh/authorized_keys文件权限必须是600给对应文件授权$ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/authorized_keys将authorized_keys文件拷贝到应用服务器的admin用户.ssh目录下# 在应用服务器(192.168.1.2)上用admin用户创建/home/admin/.ssh文件夹 # 在jenkins服务器(192.168.1.195)上将pub公钥文件拷贝到应用服务器的.ssh目录下,并命名为authorized_keys $ scp -p ~/.ssh/id_rsa.pub admin@192.168.1.2:/home/admin/.ssh/authorized_keys在jenkins服务器上进行免密连接测试# 在jenkins服务器的/home/admin目录下创建filetest文件,并拷贝到应用服务器 $ cd ~/ $ touch filetest $ scp -p filetest admin@192.168.1.2:/home/admin/filetest # 进入到应用服务器(192.168.1.2),检查/home/admin目录下是否出现filetest # 在jenkins服务器上使用ssh进行免密连接测试,成功后会出现Last Login的提示 $ ssh admin@192.168.1.2 Last login: Sun Sep 20 21:53:03 2020 $ exit到此免密登录和拷贝实现成功,为接下来jar包部署提供了快捷的帮助编写jenkins发布脚本#!/bin/bash echo "部署的目录和项目名称" DIR="/data/app" projectName="it235-boot" echo "待部署的应用服务器,可多台" server_ips="192.168.1.2" for server_ip in ${server_ips[@]} do echo "ssh连接进行备份操作" ssh -Tq -oStrictHostKeyChecking=no admin@${server_ip} <<EOF mkdir -p $DIR/backup/${projectName} if [ -f "$DIR/${projectName}/${projectName}.jar" ];then mv $DIR/${projectName}/${projectName}.jar $DIR/backup/${projectName}/${projectName}-`date "+%Y%m%d_%H%M%S"`.jar fi EOF echo "拷贝jar包到目标服务器的tmp目录" scp -q -oStrictHostKeyChecking=no ${WORKSPACE}/target/*.jar admin@${server_ip}:/tmp/${projectName}.jar echo "ssh远程连接进行发布操作" ssh -q -oStrictHostKeyChecking=no admin@${server_ip} <<EOF mv /tmp/${projectName}.jar $DIR/${projectName}/${projectName}.jar EOF done echo "success"编写应用启动脚本在/data/app/it235-boot目录下创建启动脚本$ touch start.sh $ vim start.sh # 将下面代码粘贴到start.sh中#!/bin/bash set -e #任何命令出错就退出 set -o pipefail APP_ID=it235-boot APP_DIR="/data/app" nohup java -Dspring.profiles.active=dev -jar ${APP_DIR}/${APP_ID}/${APP_ID}.jar > release_out.log & start_ok=false if [[ $? = 0 ]];then sleep 3 tail -n 10 release_out.log sleep 5 tail -n 50 release_out.log fi aaa=`grep "Started" release_out.log | awk '{print $1}'` if [[ -n "${aaa}" ]];then echo "Application started ok" exit 0 else echo "Application started error" exit 1 fi在/data/app/it235-boot目录下创建停止脚本$ touch stop.sh $ vim stop.sh # 将下面代码粘贴到stop.sh中#!/bin/bash APP_ID=it235-boot ps aux | grep ${APP_ID} | grep -v "grep" | awk '{print "kill -9 "$2}' | sh并进行启动和停止测试,查看日志输出是否正常将下述启动代码配置jenkins中sh $DIR/${projectName}/stop.sh sh $DIR/${projectName}/start.sh访问并测试代码是否生效如果是虚拟机则需要给防火墙添加9010端口$ su root # 开启防火墙9010端口 $ firewall-cmd --zone=public --add-port=9010/tcp --permanent # 使配置生效 $ firewall-cmd --reload如果是ECS则需要在安全组中开放9010端口(此处省略)此外也可以采用Nginx映射(此处省略)修改代码返回值,提交至Gitee,并再次进行构建发布,访问http://192.168.1.2:9010查看结果是否更新到此一个简单的SpringBoot项目发布完成,但企业中的发布往往比这要复杂很多,在参数化构建章节再为您详细讲解,比如:自动构建按分支发布Tag发布节点发布流水线发布等等,
安装下载下载本地下载http://nginx.org/en/download.html下载后上传至CentOS下的/usr/local目录下wget直接下载$ cd /usr/local $ wget http://nginx.org/download/nginx-1.17.1.tar.gz安装解压安装包$ tar -zxvf nginx-1.17.1.tar.gz注意:nginx被解压到了/usr/local/nginx-1.17.1 目录下(不要把压缩包解压到/usr/local/nginx目录下,或者将解压后的目录重命名为nginx,因为nginx会默认安装到/usr/local/nginx目录下)安装前准备安装前先安装nginx所需的依赖库,如果缺少依赖库,可能会安装失败$ yum install gcc-c++ $ yum install pcre $ yum install pcre-devel $ yum install zlib $ yum install zlib-devel $ yum install openssl $ yum install openssl-devel进入nginx-1.17.1目录,并执行以下配置命令$ cd nginx-1.17.1 $ ./configureconfigure操作会检测当前系统环境,以确保能成功安装nginx,如果出错,请检查上述安装前依赖包是否已经安装如果出现如下信息表示你需要安装依赖库gcc库未安装提示checking for OS + Linux 3.10.0-123.el7.x86_64 x86_64 checking for C compiler ... not found ./configure: error: C compiler cc is not foundPCRE库未安装提示 ./configure: error: the HTTP rewrite module requires the PCRE library. You can either disable the module by using --without-http_rewrite_module option, or install the PCRE library into the system, or build the PCRE library statically from the source with nginx by using --with-pcre=<path> option.zlib库未安装提示 ./configure: error: the HTTP gzip module requires the zlib library. You can either disable the module by using --without-http_gzip_module option, or install the zlib library into the system, or build the zlib library statically from the source with nginx by using --with-zlib=<path> option.如果没有其他错误,则可进行下一步执行make安装注意:下面2步会将nginx安装到/usr/local/nginx目录下,所以请勿占用nginx目录命名 # make $ make install如果上述2步操作未报错,你可以切换到上一级目录,会发现nginx目录已经存在了$ cd .. $ cd nginx $ ls配置nginx开机启动切换到/lib/systemd/system/目录,创建nginx.service文件vim nginx.service$ cd /lib/systemd/system/ $ vim nginx.service添加如下内容[Unit] Description=nginx After=network.target [Service] Type=forking ExecStart=/usr/local/nginx/sbin/nginx ExecReload=/usr/local/nginx/sbin/nginx reload ExecStop=/usr/local/nginx/sbin/nginx quit PrivateTmp=true [Install] WantedBy=multi-user.target退出并保存文件,执行如下名使nginx开机启动$ systemctl enable nginx.service# 启动nginx $ systemctl start nginx.service # 结束nginx $ systemctl stop nginx.service # 重启nginx $ systemctl restart nginx.service验证是否安装成功# 你要将80端口添加到防火墙中 $ firewall-cmd --zone=public --add-port=80/tcp --permanent #重新加载 $ firewall-cmd --reload 在浏览器访问如下地址 http://192.168.2.204/云上环境ECS让外网通过ip+端口的方式访问以下内容是基于我提供了一个7777端口的服务,需要开启一个7777端口的web服务后进行,或者将下方的7777端口改为nginx默认主页的80端口也可以进入服务器安全组添加7777端口为0.0.0.0/0外网通过ip + 端口的方式访问,查看效果Nginx域名http版配置nginx配置ssl# 进入/usr/local/nginx/conf/目录 $ vim nginx.conf # 在最后一行大括号结束之前,加入以下配置,并将it235换成你的域名 # http域名配置 server { listen 80; server_name www.it235.com it235.com; # 域名默认映射到 http://127.0.0.1:7777 location / { proxy_pass http://127.0.0.1:7777; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; root html; index index.html index.html; } }重新加载nginx配置文件# 验证nginx配置文件是否正确 $ /usr/local/nginx/sbin/nginx -t nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful # 重新加载nginx配置文件,重新加载才会生效 $ /usr/local/nginx/sbin/nginx -s reload阿里云安全组配置80端口为0.0.0.0/0还需要去做域名解析A记录主要关注A记录,设置到一个具体的服务器IP上(一定是公网IP,192.168.1.3)CNAME记录SSL证书(https)配置提供SSL证书服务 在配置了SSL证书后,重新启动报如下错误,所以需要提前安装SSL模块[root@iz2zehvxttbua2f45dp7ihz sbin]# ./nginx -s reload nginx: [emerg] the "ssl" parameter requires ngx_http_ssl_module in /usr/local/nginx/conf/object.nginx.conf:14表示当前Nginx缺少SSL模块,我们可以查看nginx版本[root@iz2zehvxttbua2f45dp7ihz sbin]# ./nginx -V nginx version: nginx/1.17.1 built by gcc 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) configure arguments:如果带有SSL模块,在上面输出的信息最后一行应该有如下参数,如下图--prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module那应该怎么添加中呢?请看如下操作添加SSL模块切换到源码安装包,就是带版本的那个nginx文件夹# cd /usr/local/nginx-1.17.1执行如下命令# ./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module配置完成后执行如下命令# make # 切忌,只执行make,不要执行make install,否则会覆盖停止服务,备份原来的nginx.sh文件# systemctl stop nginx.service # cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak将源码包中的nginx文件覆盖到正在使用的nginx执行文件# cp -f /usr/local/nginx-1.17.1/objs/nginx /usr/local/nginx/sbin/nginx重启并查看版本# systemctl start nginx.service # /usr/local/nginx/sbin/nginx -V阿里云免费证书申请后可以下载,然后上传到nginx目录下做映射Let's Encrypt免费证书,一次申请只能使用3个月,到期后需要手动续期Nginx配置证书# 进入/usr/local/nginx/conf/目录 $ vim nginx.conf # 在最后一行大括号结束之前,加入以下配置,并将it235换成你的域名 # 所有http的请求,统一发到https请求上 server { listen 80; server_name www.it235.com *.it235.com; rewrite ^(.*)$ https://$host$1 permanent; } # 未带www的请求,统一分发到https://www上 server { listen 80; listen 443 ssl; server_name it235.com; return 301 https://www.it235.com$request_uri; } # https 请求处理 server { listen 443 default_server ssl; server_name www.it235.com; ssl_certificate /etc/letsencrypt/live/it235.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/it235.com/privkey.pem; large_client_header_buffers 4 16k; client_max_body_size 30m; client_body_buffer_size 128k; # 域名默认映射到 http://127.0.0.1:7777 location / { proxy_pass http://127.0.0.1:7777; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; root html; index index.html index.html; } }# 验证nginx配置文件是否正确 $ /usr/local/nginx/sbin/nginx -t # 重新加载nginx配置文件,重新加载才会生效 $ /usr/local/nginx/sbin/nginx -s reload最终效果,访问https://www.it235.com要能成功,并且加上锁
MySQL安装下载安装这里是CentOS7_64操作系统,所以我采用rpm包进行安装下载:https://dev.mysql.com/downloads/mysql/这里最好是右键复制链接之后采用迅雷进行下载,速度很快移除mariadb安装之前先检测有没有mariadb,如果有则移除,执行如下命令# 查看,我这里显示有 [root@iz2zehvxttbua2f45dp7ihz java]# rpm -qa | grep mariadb mariadb-libs-5.5.56-2.el7.x86_64 # 接着使用下面命令进行移除 [root@iz2zehvxttbua2f45dp7ihz java]# rpm -e mariadb-libs-5.5.56-2.el7.x86_64 --nodeps # 再次查看有没有,没有则表示移除干净了 [root@iz2zehvxttbua2f45dp7ihz java]# rpm -qa | grep mariadb上传安装文件我们使用cd名跳转到/usr/local目录下,并执行mkdir mysql命令,创建mysql目录并进入[root@iz2zehvxttbua2f45dp7ihz java]# cd .. [root@iz2zehvxttbua2f45dp7ihz local]# ls [root@iz2zehvxttbua2f45dp7ihz local]# mkdir mysql [root@iz2zehvxttbua2f45dp7ihz local]# cd mysql这里建议使用ftp上传,安装xftp或者其他ftp工具,使用ftp连接后进行上传,上传到/usr/local/mysql目录查看文件目录位置及文件信息解压安装执行解压命令进行解压[root@iz2z7ihz mysql]# tar -xvf mysql-8.0.16-2.el7.x86_64.rpm-bundle.tar执行安装命令进行安装安装common# 命令:rpm -ivh mysql-community-common-8.0.16-2.el7.x86_64.rpm --nodeps --force [root@iz2zehvxttbua2f45dp7ihz mysql]# rpm -ivh mysql-community-common-8.0.16-2.el7.x86_64.rpm --nodeps --force安装libs# 命令:rpm -ivh mysql-community-libs-8.0.16-2.el7.x86_64.rpm --nodeps --force [root@iz2zehvxttbua2f45dp7ihz mysql]# rpm -ivh mysql-community-libs-8.0.16-2.el7.x86_64.rpm --nodeps --force安装client# 命令:rpm -ivh mysql-community-client-8.0.16-2.el7.x86_64.rpm --nodeps --force [root@iz2zehvxttbua2f45dp7ihz mysql]# rpm -ivh mysql-community-client-8.0.16-2.el7.x86_64.rpm --nodeps --force安装server# 命令:rpm -ivh mysql-community-server-8.0.16-2.el7.x86_64.rpm --nodeps --force [root@iz2zehvxttbua2f45dp7ihz mysql]# rpm -ivh mysql-community-server-8.0.16-2.el7.x86_64.rpm --nodeps --force查看已安装的包[root@iz2zehvxttbua2f45dp7ihz mysql]# rpm -qa | grep mysql mysql-community-libs-8.0.16-2.el7.x86_64 mysql-community-server-8.0.16-2.el7.x86_64 mysql-community-common-8.0.16-2.el7.x86_64 mysql-community-client-8.0.16-2.el7.x86_64初始化通过以下命令完成mysql初始化操作mysqld --initialize如果报如下错误信息:mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory则安装下libaio.so.1包# 方案1 yum install -y libaio # 方案2,如果方案1安装后,继续初始化mysql没成功,则执行该方案 yum install -y libaio.so.1授权服务防火墙配置#给mysql目录授权给mysql组合mysql用户,该步骤一般不用操作,我们使用的root用户拥有所有权限, $ chown mysql:mysql /var/lib/mysql -R; # 启动mysql服务 systemctl start mysqld.service; # 配置开机启动 $ systemctl enable mysqld;查看数据库默认密码# 该密码为随机生成kQ*q9a1eQpZq [root@sdfsdf mysql]# cat /var/log/mysqld.log | grep password拿到查询出的密码进行数据库的登录mysql -u root -p # 然后将密码复制粘贴过来,密码不显示,直接回车登录即可修改密码ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';然后退出以修改后的密码重新登录授权远程访问create user 'root'@'%' identified with mysql_native_password by '123456'; grant all privileges on *.* to 'root'@'%' with grant option; flush privileges;修改加密规则,目前可视化工具如Navicat普遍只支持MySQL5.X,所以我们需要调整8.0的加密规则-- 修改命令 ALTER USER 'root'@'localhost' IDENTIFIED BY 'root' PASSWORD EXPIRE NEVER; -- 刷新权限 flush privileges;执行exit退出mysql控制台此时你采用Navicat等可视化客户端是无法连接该服务的,因为端口未对外开放防火墙配置本地虚拟机需要开启防火墙,CentOS6采用的iptables,而CentOS7采用的是systemctl,所以下面我们采用CentOS7的配置#查看防火墙状态 $ systemctl status firewalld #启动防火墙,防火墙启动后,除了22端口对外能够访问,其他端口均不能使用,所以需要添加 $ systemctl start firewalld #添加端口 firewall-cmd --zone=public --add-port=80/tcp --permanent firewall-cmd --zone=public --add-port=3306/tcp --permanent firewall-cmd --zone=public --add-port=6379/tcp --permanent #重新加载 $ firewall-cmd --reload查看mysql配置文件# 默认的配置文件为:/etc/my.cnf $ cat /etc/my.cnf注意Linux下的MySQL数据库大小写敏感,所以SQL语句中的表名区分大小写忽略大小写配置$ vim /etc/my.conf [mysqld] lower_case_table_names=1查看进程语句$ ps -ef | grep mysql
Linux环境搭建虚拟机下载VMWareVMware Workstation是一款功能强大的桌面虚拟软件,可模仿物理机器承载多个虚拟操作系统下载资源下载地址链接:https://pan.baidu.com/s/1OFVRzCySbrzLs-P37Iw9gg 提取码:wiih安装安装博客:https://blog.csdn.net/babyxue/article/details/80970526Linux镜像下载CentOS7这里我们采用CentOS 7操作系统,该系统是目前企业中使用率非常高的系统下载地址:http://mirrors.aliyun.com/centos/7.6.1810/isos/x86_64/CentOS-7-x86_64-DVD-1810.iso:标准安装版(推荐)CentOS-7-x86_64-Everything-1810.iso:完整版,集成所有软件CentOS-7-x86_64-LiveGNOME-1810.iso:GNONE桌面版CentOS-7-x86_64-LiveKDE-1810.iso:KDE桌面版CentOS-7-x86_64-Minimal-1810.iso:精简版,自带软件少CentOS-7-x86_64-NetInstall-1810.iso:网络安装版迅雷打开torrent该文件问BT种子文件,使用迅雷下载速度会更快CentOS镜像安装镜像安装安装配置网络配置yum国内镜像源我们在使用 yum install 命令来在线安装 linux系统的软件时,会一次安装所有依赖的软件包。而从国外下载软件速度非常慢,很多情况下都无法下载,所以这里我们要更换镜像源为国内地址。yum 的配置文件在 /etc/yum.repos.d 目录下, 其中有多个配置文件,每一个配置文件中都可以配置一个或多个repository, 但是最终会被合并为一个交给系统,所以多个文件只是为了方便管理。阿里巴巴镜像源,点击 centos ,进行配置说明页面根据官网的说明,分别有 CentOS 6、CentOS 7、CentOS 8等配置操作步骤。备份,将 CentOS-Base.repo 为CentOS-Base.repo.backupcp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup下载新的http://mirrors.aliyun.com/repo/Centos-7.repo,并命名为CentOS-Base.repowget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo` 或者 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo清除缓存yum clean all # 清除系统所有的yum缓存 yum makecache # 生成yum缓存总结本篇就带大家安装了CentOS 7,主要分为VMware的安装、CentOS的安装,下篇我们进行JDK相关软件的安装的学习
本文来自于生产级别应用的实践,同时需要大家掌握前置技能Docker环境准备安装Docker环境Linux配置(必须)Docker安装(必须)Docker镜像测试配置Docker仓库安装使用harbor使用阿里云仓库(必须)开启Docker服务远程端口如果要借助Jenkins、Gradle、Maven等工具将构建好的Docker image推到到某个服务器的Docker上,就必须开启该服务中Docker的远程连接端口,注意该端口尽量在内网操作,外网开放可以加入证书认证功能。例:我现在想通过Gradle直接将镜像推送到192.168.2.195的docker服务里面,就需要配置195上的Docker,注意这里不是指私服,而是某个机器上的Docker服务。修改Docker服务的配置文件$ vim /usr/lib/systemd/system/docker.service在[Service]部分的ExecStart处加入如下内容ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375加入后的Service完整配置如下重载配置,重启服务$ systemctl daemon-reload && systemctl restart docker如果开启了防火墙,则需要通过该端口$ firewall-cmd --zone=public --add-port=2375/tcp --permanent $ firewall-cmd --reload如果是ECS等云服务器,则需要在安全组中放开2375端口能访问到http://192.168.2.195:2375/表示配置成功,注意这里换成你服务的ip编写build.gradle引入docker插件(必须)buildscript{ repositories { maven { url 'https://maven.aliyun.com/repository/central' } } } plugins { ... id 'com.bmuschko.docker-remote-api' version '6.7.0' id 'com.bmuschko.docker-spring-boot-application' version '6.7.0' }插件文档地址编写构建docker镜像的代码docker { springBootApplication { //待上传到的docker服务 url = "tcp://192.168.2.195:2375" // 基础镜像 baseImage = 'openjdk:8-alpine' //容器暴露的端口,我们的application.yml中配置的服务端口server.port=7000 ports = [7000] //镜像名称 images = ["it235-order:${version}"] //容器启动的参数 jvmArgs = ['-Dspring.profiles.active=prod', '-Xmx256m'] } }执行./gradlew dockerBuildImage任务,我们可以查看到控制台的输出查看Docker上的镜像是否创建成功$ docker images | grep it235通过镜像启动服务$ docker run -d -p 7001:7000 --name=it2351 it235-order:1.0.0-SNAPSHOT # 查看是否启动成功 $ docker ps # 如果启动失败,需要使用日志查看命令进行查看 $ docker logs -f -t --tail=200 容器名称或ID通过观察build/docker目录发现,插件帮我们生成了DockerfileFROM openjdk:8-alpine LABEL maintainer=Ron WORKDIR /app COPY libs libs/ COPY resources resources/ COPY classes classes/ ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-Xmx256m", "-cp", "/app/resources:/app/classes:/app/libs/*", "com.it235.it235order.It235OrderApplication"] EXPOSE 7000Dockerfile整合编写Dockerfile脚本在项目根目录下新增Docker文件夹,并在其中创建Dockerfile文件,内容如下:FROM openjdk:8-alpine LABEL maintainer=junge WORKDIR /app COPY it235-order.jar ./ ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-Xmx256m" , "-jar" , "/app/it235-order.jar"] EXPOSE 7000编写gradle脚本,一定要让jar和Dockerfile在一个目录下// 每次构建都清理一次build/docker目录 task cleanDocker(type: Delete) { delete fileTree("${buildDir}/docker") { include '**/*' } } //将bootJar拷贝到build/docker目录下,并重命名 task copyDockerJar(type: Copy, dependsOn: bootJar) { group = "docker" //拷贝前清理docker目录下所有文件 dependsOn cleanDocker //将jar拷贝到build/docker目录下 from "${project.rootDir}/Dockerfile/Dockerfile" , bootJar.archiveFile into "${project.buildDir}/docker" //改名为it235-order.jar,Dockerfile中需要使用 rename { String fileName -> fileName.replace("-${version}", "") } } //指定构建镜像时的位置和服务URL import com.bmuschko.gradle.docker.tasks.image.* task buildImage(type: DockerBuildImage , dependsOn:copyDockerJar) { group "docker" url = "tcp://192.168.2.194:2375" //指定docker目录和Dockerfile文件 inputDir = file("${project.buildDir}/docker") //指定Dockerfile dockerFile = project.file("${project.buildDir}/docker/Dockerfile") //镜像名称 images.add("it235-order2:${version}") }执行任务buildImage,观察Docker镜像列表是否已成功上传启动容器docker run -d -p 7001:7000 --name=it2351 it235-order:1.0.0-SNAPSHOT浏览器访问接口该方式有哪些不足之处呢?是不是多个环境切换时没有办法做到很好的兼容,需要编写多个环境的Dockfile,同时如果我想使用start.sh脚本也不能做到Dockerfile升级版使用start.sh启动在docker目录下添加sh脚本#!/bin/bash PROFILE_ENV=$1 java -Dspring.profiles.active=$PROFILE_ENV -Xmx256m -Djava.security.egd=file:/dev/./urandom -jar /app/it235-order.jar注意:切不可采用nohup或&号的形式设置为后台启动,程序一定要在前台,否则容器会直接退出改造Dockerfile# 注意此时需要使用/bin/bash,所以需要更换基础镜像,采用对sh支持更好的镜像 FROM openjdk:8 # sh脚本权限 COPY it235-order.jar ./ RUN sh -c 'chmod 755 start.sh' # 更改启动方式 ENTRYPOINT ["/bin/sh","-c","/app/start.sh ${env}"]解决多环境切换的问题# 增加全局扩展属性 project.ext{ //gradle buildImage -Penv=prod if (!project.hasProperty("env")) { env = "dev" } println "run env: ${env}" } # 在buildImage中使用buildArgs属性 buildArgs = ["env" : "${env}"] # copyJar中,需要把start.sh脚本添加进去改造Dockerfile# 在workdir下面增加 # 接收gradle传递的参数 ARG env # 将参数设置到env中去,启动时容器内部能读到镜像中的env数据 ENV env ${env}推送到harbordocker { url = 'tcp://192.168.2.194:2375' certPath = null registryCredentials { username = 'admin' password = 'Harbor12345' } } task copyDockerJar(type: Copy){ group "docker" from "${project.rootDir}/docker/Dockerfile" ,"${project.rootDir}/docker/start.sh" , bootJar.archiveFile into "${project.buildDir}/docker" rename { fileName -> fileName.replace("-${version}" , "") } } import com.bmuschko.gradle.docker.tasks.image.* task buildImage(type: DockerBuildImage , dependsOn: copyDockerJar){ group "docker" url = "tcp://192.168.2.194:2375" images.add("192.168.2.195:8098/it235-${env}/it235-order:${version}") buildArgs = ["env" : "${env}"] } task tagImage(type: DockerTagImage,dependsOn: buildImage){ group 'docker' tag = "${version}" repository = "192.168.2.195:8098/it235-${env}/${project.name}" targetImageId buildImage.getImageId() } // 移除镜像 task removeImage(type: DockerRemoveImage) { group 'docker' force = true targetImageId buildImage.imageId } //推送镜像 task pushImage(type: DockerPushImage , dependsOn: tagImage){ group 'docker' finalizedBy removeImage images = buildImage.images }注意:如果出现HTTPS问题,请配置daemon.json文件,参照Docker中的视频配置,我这边192.168.2.194的配置如下:$ cat /etc/docker/daemon.json { "registry-mirrors" : ["http://hub-mirror.c.163.com"], "insecure-registries" : ["192.168.2.195:8098"], "live-restore": true }推送到云容器镜像服务错误解决# 问题1 connection time out 1. 镜像体积先缩小,可以FROM openjdk:8-alpine,这个打出来只有150M 2. 分步上传,先push到Docker服务,然后在Docker服务上执行push命令,2步打通之后再合并一起操作 docker push registry.cn-hangzhou.aliyuncs.com/it235-dev/it235-order:1.0.0-SNAPSHOT # 问题2 Could not push image: denied: requested access to the resource is denied 1. registryCredentials中的用户名密码需要填写阿里云镜像服务的用户名密码 2. images的名称要能够与阿里云提供的对上 配置:images.add("registry.cn-hangzhou.aliyuncs.com/it235-${env}/it235-order:${version}") 阿里云:registry.cn-hangzhou.aliyuncs.com/it235-dev/it235-order:[镜像版本号] 3. docker服务机器上需要登录阿里云镜像仓库 docker login --username=用户名 registry.cn-hangzhou.aliyuncs.com最终配置project.ext{ //gradle buildImage -Penv=prod if (!project.hasProperty("env")) { env = "dev" } println "run env: ${env}" } docker { url = "tcp://192.168.2.194:2375" certPath = null registryCredentials { username = 'xxxx' password = '阿里云镜像服务密码' } } task copyDockerJar(type: Copy){ group "docker" from "${project.rootDir}/docker/Dockerfile" ,"${project.rootDir}/docker/start.sh" , bootJar.archiveFile into "${project.buildDir}/docker" rename { fileName -> fileName.replace("-${version}" , "") } } import com.bmuschko.gradle.docker.tasks.image.* task buildImage(type: DockerBuildImage , dependsOn: copyDockerJar){ group "docker" url = "tcp://192.168.2.194:2375" images.add("registry.cn-hangzhou.aliyuncs.com/it235-${env}/it235-order:${version}") buildArgs = ["env" : "${env}"] } task tagImage(type: DockerTagImage) { dependsOn buildImage repository = "registry.cn-hangzhou.aliyuncs.com/it235-${env}/it235-order" tag = "${version}" targetImageId buildImage.getImageId() } task removeImage(type: DockerRemoveImage) { targetImageId buildImage.imageId force = true } task pushImage(type: DockerPushImage) { group "docker" //构建 dependsOn tagImage //推完之后,进行删除 finalizedBy removeImage images = buildImage.images }总结学习本篇内容需要先掌握Docker使用,在build.gradle调试的过程中你可能会遇到莫名其妙的坑,需要你有清晰的思路去排查,在后续文章中,我将给大家带来Docker系列的内容。我是君哥,关注我,这里有你意向不到的惊喜
本地仓库:指存在于我们本机的仓库,在我们加入依赖时候,首先会跑到我们的本地仓库去找,如果找不到则会跑到远程仓库中去找。远程仓库:指其他服务器上的仓库,包括全球中央仓库,公司内部的私服,又或者其他公司或组织提供的公共库。中央仓库:Maven中央仓库,服务于全球私服:私服是架设在局域网的一种特殊的远程仓库,目的是代理远程仓库及部署第三方构件。有了私服之后,当 Gradle、Maven 需要下载构件时,直接请求私服,私服上存在则下载到本地仓库;否则,私服请求外部的远程仓库,将构件下载到私服,再提供给本地仓库下载。私服的优点:解决中央仓库网络、重复下载、本公司非公开组件多项目依赖等问题。在团队协作开发中,为了提高开发效率,每个公司会有自己的私有仓库,私服是一种特殊的远程仓库,部署在局域网内,开发人员需要构建提交自己的项目组件至服务器,方便其他同事可以下载下来进行协同开发,如下就是围绕私服进行的团队开发协作流程。其他公共库:阿里云镜像的远程仓库构件的坐标坐标使用示例<!-- maven --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.20</version> </dependency> // gradle dependencies { implementation 'org.springframework:spring-core:5.3.20' }坐标解析groupId:项目所属的组,由java中的package命衍生而来,通常与域名反向一 一对应artifactId:模块名或项目名version:定义项目版本坐标查询https://search.maven.org/https://mvnrepository.com/https://developer.aliyun.com/mvn/search本地仓库如果不指定,默认为: ${user.home}/.gradleLinux:~/.gradleWIn:~\.gradle若要改变默认的本地仓库,可以在环境变量中通过GRADLE_USER_HOME进行指定,如下:细心的同学也会发现,在之前的课程中,gradle-wrapper.properties文件中就使用到了GRADLE_USER_HOME这个变量。Gradle仓库目录说明.gradle - .tmp - caches:缓存目录,除了缓存jar还会缓存一些运行时的缓存,使用构建缓存的目的是为了让构建更快,像单元测试任务、Jacoco任务都是可以被缓存 - daemon:守护进程相关存储目录,gradle的守护进程是一个长期存在的进程,这可以节约jvm启动成本,在内存中缓存项目结构、文件、任务等信息,从而加快gradle的构建,在3.0时就已经默认启用,可以通过no-deamon参数不启用守护进程或配置gradle.properties停用守护进程,守护进程默认驻守3个小时候会自动关闭 - jdks:环境标志,如go语言就是go目录 - native:用于存放平台相关(Win/Linux/Mac)的库。 - notifications - workers - wrapper:gradle-wrapper下载目录 Gradle交互进程剖析$ ./gradlew build 1. 会启动一个jvm进程,启动的jvm进程为deamon进程 2. 当前运行的窗口作为client,client会将守护进程剖析# 查看daemon进程是否有启动 $ ./gradlew --status No Gradle daemons are running. # 执行build命令,如果daemon没启动,那么会先启动 $ ./gradlew build Starting Gradle Daemon... Gradle Daemon started in 3 s 473 ms # 查看已经存在一个空闲的守护进程了 $ ./gradlew --status PID STATUS INFO 9140 IDLE 7.4.1 # 停止守护进程 $ ./gradle --stop Stopping Daemon(s) 1 Daemon stopped为什么会启动多个守护进程Gradle 将创建一个新的守护进程而不是使用一个已经在运行的守护进程有几个原因。基本规则是,如果没有现有的空闲或兼容的守护程序,Gradle 将启动一个新的守护程序。Gradle 将杀死任何闲置 3 小时或更长时间的守护进程,因此不必手动清理它们。远程仓库是指非本地的远程网络上的仓库。私服、其他公共仓库、中央仓库均被称为远程仓库,远程仓库可以看做为一个庞大的项目组件仓库中心,私服是一种特殊远程仓库,它是部署局域网内的仓库。中央仓库Maven 中央仓库是由 Maven 社区提供的仓库,其中包含了大量常用的库。中央仓库包含了绝大多数流行的开源Java构件,以及源码、作者信息、SCM、信息、许可证信息等。一般来说,简单的Java项目依赖的构件都可以在这里下载到。中央仓库的关键概念:这个仓库由 Maven 社区管理。不需要配置。需要通过网络才能访问。镜像仓库阿里云Maven中央仓库为 阿里云云效 提供的公共代理仓库,帮助研发人员提高研发生产效率,使用阿里云Maven中央仓库作为下载源,速度更快更稳定。私服私服的优点:解决中央仓库网络、重复下载、本公司非公开组件多项目依赖等问题。在团队协作开发中,为了提高开发效率,每个公司会有自己的私有仓库,私服是一种特殊的远程仓库,部署在局域网内,开发人员需要构建提交自己的项目组件至服务器,方便其他同事可以下载下来进行协同开发,如下就是围绕私服进行的团队开发协作流程。私服是架设在局域网的一种特殊的远程仓库,目的是代理远程仓库及部署第三方构件。有了私服之后,当 Maven 需要下载构件时,直接请求私服,私服上存在则下载到本地仓库;否则,私服请求外部的远程仓库,将构件下载到私服,再提供给本地仓库下载。私服的优点:解决中央仓库网络、重复下载、本公司非公开组件多项目依赖等问题。在团队协作开发中,为了提高开发效率,每个公司会有自己的私有仓库,私服是一种特殊的远程仓库,部署在局域网内,开发人员需要构建提交自己的项目组件至服务器,方便其他同事可以下载下来进行协同开发,如下就是围绕私服进行的团队开发协作流程。Windows安装网上下载安装包nexus-3.31.1-01:服务及可执行文件sonatype-work:文件存储的工作目录找到nexus-3.31.1-01-win64\nexus-3.31.1-01\bin\nexus.exe,通过CMD命令行的方式进入,然后在bin目录下运行nexus.exe/run如果出现端口占用无法启动的情况,可以修改sonatype-work\nexus3\etc\nexus.properties文件Docker安装环境:CentOS 7、 JDK8 、Sonatype Nexus、Docker,服务器IP:192.168.2.195拉取镜像$ docker pull sonatype/nexus3运行nexus容器# 创建一个目录用于存放容器挂载的数据 mkdir –p /usr/local/nexus3/nexus-data/ chmod 777 /usr/local/nexus3/ docker run -d \ --privileged=true \ --name=nexus3 \ -p 5000:5000 \ -p 8081:8081 \ -v /usr/local/nexus3/nexus-data:/var/nexus-data \ -e INSTALL4J_ADD_VM_PARAMS="-Xms1024M -Xmx1024M" \ b7c023b6a9b9 # 注意此处是镜像ID # 查看启动日志,是否成功启动 $ docker logs nexus3 # 启动成功的日志如下 2022-05-14 09:04:38,819+0000 INFO [jetty-main-1] *SYSTEM org.eclipse.jetty.server.Server - Started @55942ms 2022-05-14 09:04:38,820+0000 INFO [jetty-main-1] *SYSTEM org.sonatype.nexus.bootstrap.jetty.JettyServer - ------------------------------------------------- Started Sonatype Nexus OSS 3.38.1-01 ------------------------------------------------- # 如果http://ip:8081访问不到,则需要配置一下防火墙加入端口 $ firewall-cmd --zone=public --add-port=8081/tcp --permanent $ firewall-cmd --zone=public --add-port=5000/tcp --permanent $ firewall-cmd --reload # 获取登录密码 $ docker exec -it nexus3 /bin/bash $ cat /nexus-data/admin.password 访问nexus3登录仓库页面:http://ip:8081默认账号admin,初始始密码则存在容器中的/nexus-data/admin.password文件中。进入Nexus配置界面:私服仓库仓库类型宿主类型:此类型所描述的仓库,主要用于存放内部发布的的项目构件,和外部没有连接关系,是由公司内部用户发布上来的项目(其典型代表为:release仓库、snapshots仓库)代理类型:此类型所描述的仓库,主要是提供下载缓存构件和插件、比如你请求一个jar,它实际从远程中央仓库中寻找数据的仓库,如果只是从远程仓库下载构件和插件、那么代理仓库完全足够(其典型代表为:central中央仓库、阿里云仓库、apache仓库)。仓库组类型:用于聚合多个仓库,为外部提供统一的地址,此类型描述的仓库,把其他的仓库使用同一个地址暴露出去,组仓库用来方便我们开发人员进行设置的仓库,不具有实际的功能,只是一个概念,简单来说就是访问这个group设置的一个地址,其他仓库的jar都能获取到<!--我们配置阿里云镜像仓库时就可以使用它的public--> <mirror> <id>nexus-aliyun</id> <mirrorOf>central</mirrorOf> <name>Nexus aliyun</name> <url>https://maven.aliyun.com/repository/public/</url> </mirror>Formatmaven2:maven类型仓库,区别于老版本中的maven1nuget:.net使用,NuGet 是一个Visual Studio的扩展。在使用Visual Studio开发基于.NET Framework的应用时,NuGet能够令你在项目中添加、移除和更新引用的工作变得更加快捷方便。Namemaven-central:用来代理Maven中央仓库,其策略为Release,只会下载和缓存中央仓库中的发布版本的构件maven-public:该仓库组将上述所有存储策略为Release的仓库聚合并通过统一的地址提供服务。maven-releases:策略为Release的宿主仓库,用来部署公司或组织内部的发布版本构件。maven-snapshots:策略为Snapshot的宿主仓库,用来部署公司或组织内部的快照版本构件。使用私服下载构建将中央仓库的代理地址改代理为阿里云镜像仓库build.gradle中,使用私服的public地址buildscript { repositories { maven { allowInsecureProtocol = true url 'http://192.168.2.195:8081/repository/maven-public/' } } } repositories { maven { //忽略https allowInsecureProtocol = true url 'http://192.168.2.195:8081/repository/maven-public/' } } 添加依赖,并检查私服和本地仓库是否有该jar包implementation 'org.apache.commons:commons-compress:1.20'如果出现jar包无法拉下来,如何解决,这里我以org.springframework.boot:spring-boot-starter:2.6.4为例先确认maven-central中的代理地址是否是https://maven.aliyun.com/repository/public/,其他地址有可能因为网络原因导致从中央仓库拉取jar包失败再确认你的maven-central仓库中是否存在该jar包如果私服存在,则删除你本地caches目录下的modules-2目录,重启IDEA即可如果maven-central不存在,则需要去中央仓库查看是否存在该jar包如果maven-central中的文件不完整,比如只有pom没有其他文件,且以上操作均无效,则可能需要重建私服索引publishing插件发布plugins{ id 'maven-publish' } publishing { publications { ... } repositories.maven { ... } } // 通过上述配置我们可以知道publishing有2个属性publications和repositories,而publishing作为maven-publish提供的插件类,实际上是一个PublishingExtensionPublishingExtensionpublic class DefaultPublishingExtension implements PublishingExtension { private final RepositoryHandler repositories; private final PublicationContainer publications; public DefaultPublishingExtension(RepositoryHandler repositories, PublicationContainer publications) { this.repositories = repositories; this.publications = publications; } public RepositoryHandler getRepositories() { return this.repositories; } public void repositories(Action<? super RepositoryHandler> configure) { configure.execute(this.repositories); } public PublicationContainer getPublications() { return this.publications; } public void publications(Action<? super PublicationContainer> configure) { configure.execute(this.publications); } }PublicationContainerpublications就是PublicationContainer的实例,PublicationContainer 负责创建和管理 Publication 实例。具体的Publication实例,需要看你使用的事什么插件,当前有ivy-publish和maven-publish,对应的实例是(IvyPublication以及MavenPublication)ivyplugins { id 'ivy-publish' } publishing.publications.create('publication-name', IvyPublication) { // Configure the ivy publication here } 或 publishing { publications { myOrderJar(IvyPublication) { // Configure the ivy publication here } } }mavenplugins { id 'maven-publish' } publishing.publications.create('publication-name', MavenPublication) { // Configure the ivy publication here } 或 publishing { publications { myOrderJar(MavenPublication) { // Configure the maven publication here } } }RepositoryHandlerrepositories闭包可以设置具体的RepositoryHandler实例,提供了众多方法,用于指定仓库或本地目录FlatDirectoryArtifactRepository:指定目录,如:libs里面的jar包IvyArtifactRepository:ivy仓库MavenArtifactRepository:Maven仓库repositories { maven { ... } }编写完整的build.gradleplugins { ... id 'maven-publish' ... } publishing { publications { //打springboot-jar包 orderJar(MavenPublication) { artifact bootJar from components.java } //可以配置多个,如下: /* dbchangelog(MavenPublication) { //指定包名 artifactId 'order-db-changelog' //可以依赖一个task artifact dbChangelogZip } */ } repositories { maven { //忽略https allowInsecureProtocol true name = 'it235-order' //nexus3的url def releasesRepoUrl = "http://192.168.2.195:8081/repository/maven-releases/" def snapshotsRepoUrl = "http://192.168.2.195:8081/repository/maven-snapshots/" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl credentials { username 'admin' password 'abc123' } } } }执行publish任务上述闭包配置作用在于创建两个实现了MavenPublication接口的task。publishOrderJarPublicationToMavenLocal发布到本地仓库publishOrderJarPublicationToMavenRepository发布到远程仓库。也可以直接双击publish任务执行查看nexus服务是否已经存在it235-order手工发布总结在企业中一般会使用自己搭建的nexus私服,其次是直接使用阿里云镜像仓库,通过这篇文章的学习应该让你对repository的了解更加深入,接下来我们继续进入下一篇的学习。
Junit4和Junit5区别非常大,高版本的springboot(如:2.6.5)只有junit5没有引入junit4,但是低版本springboot(如:2.1.8.RELEASE)的默认引入的是junit4。初始化项目,访问spring init,创建完成后导入IDEA中修改repositories为阿里云的镜像repositories { maven { url 'https://maven.aliyun.com/repository/central' } mavenCentral() }Junit版本差异Junit4和Junit5区别非常大,高版本的springboot(如:2.6.5)只有junit5没有引入junit4,但是低版本springboot(如:2.1.8.RELEASE)的默认引入的是junit4。Junit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。Junit Jupiter: Junit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。Junit Vintage: 由于JUnit已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。junit4代码示例import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @RunWith(SpringRunner.class) public class Junit4Test { @Autowired private TestRestTemplate testRestTemplate; @Before public void testBefore(){ System.out.println("before.. 每个Test都会执行一次"); } @Test public void test1(){ String t = testRestTemplate.getForObject("/index", String.class); System.out.println("toIndex:" + t); } @After public void TestAfter(){ System.out.println("after... 每个Test都会执行一次"); } @BeforeClass public static void initClass(){ System.out.println("******测试开始初始化,必须是static方法,仅执行一次"); } @AfterClass public static void endClass(){ System.out.println("******测试结束初始化,必须是static方法,仅执行一次"); } } //执行顺序是:@BeforeClass→@Before→@Test→@After→@AfterClassjunit5代码示例//包名有变化 import org.junit.jupiter.api.*; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class Junit5Test { @BeforeAll public static void testBeforeAll() { System.out.println("表示在所有单元测试之前执行......"); } @BeforeEach public void testBeforeEach() { System.out.println("类似于JUnit4的@Before 表示在每个单元测试之前执行......"); } @AfterEach public void testAfterEach() { System.out.println("类似于JUnit4的@After 表示在每个单元测试之后执行......"); } @DisplayName("测试类或测试方法声明一个自定义的显示名称") @Test public void testTwo() throws Exception { System.out.println("testTwo......"); } /** * 规定方法超时时间。超出时间测试出异常 * @throws InterruptedException */ @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) @Test void testTimeout() throws InterruptedException { Thread.sleep(600); } @AfterAll public static void testAfterAll() { System.out.println("表示在所有单元测试之后执行......"); } }更多的变化请自行查阅相关文档build.gradle配置//方式1 tasks.named('test') { useJUnitPlatform() } //方式2 test { useJUnitPlatform() }单元测试代码主程序代码//application package com.it235.it235order @SpringBootApplication public class It235OrderApplication { public static void main(String[] args) { SpringApplication.run(It235OrderApplication.class, args); } } //controller package com.it235.it235order.rest; @RestController public class OrderController { @GetMapping("/index") public String index(){ return "ok"; } } //service package com.it235.it235order.service; public interface OrderService { String get(Long orderId); } @Service public class OrderServiceImpl implements OrderService { @Override public String get(Long orderId) { return "订单ID:" + orderId; } }测试程序代码package com.it235.it235order; @SpringBootTest public class It235OrderApplicationTests { } package com.it235.it235order.order; import com.it235.it235order.It235OrderApplicationTests; import com.it235.it235order.service.OrderService; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; //集成Tests类 public class OrderTests extends It235OrderApplicationTests { @Autowired private OrderService orderService; @DisplayName("测试方法1") @Test void getOrderTest() { Long orderId = 1001L; String s = orderService.get(orderId); Assertions.assertEquals("订单ID:" + orderId , s ); } @DisplayName("测试方法2") @Test void getOrderTest2() { Long orderId = 1002L; String s = orderService.get(orderId); Assertions.assertEquals("订单ID2:" + orderId , s ); } } 单元测试执行命令使用gradlew test命令执行.\gradlew.bat testbuild时跳过test.\gradlew build -x testtest闭包扩展参数执行单元测试前jvm相关参数test { // Discover and execute JUnit4-based tests useJUnit() // Discover and execute TestNG-based tests useTestNG() // set a system property for the test JVM(s) systemProperty 'some.prop', 'value' // set heap size for the test JVM(s) minHeapSize = "128m" maxHeapSize = "512m" //实际执行:java -Xmx20m -Dspring.profiles.active=unit -XX:+UseG1GC -jar app.jar jvmArgs "-Xmx20m" ,"-Dspring.profiles.active=unit" , "-XX:+UseG1GC" //Junit5 useJUnitPlatform() }执行单元测试前拷贝资源文件//定义一个拷贝任务,将src/main/db目录文件拷贝至build中 task copyDbResources(type: Copy) { from 'src/main/db' into 'build/resources/main/db' } test { useJUnitPlatform() } //将测试资源复制到测试资源目录 processTestResources { dependsOn copyDbResources } 排除不被单元测试扫描的包test { // explicitly include or exclude tests include 'org/foo/**' exclude 'com/it235/it235order/order' useJUnitPlatform() }总结SpringBoot将Junit无缝整合后让单元测试变得更加简单,Gradle也只需要简单的配置即可完成Junit的集成。
前置准备使用start.spring快速搭建一个Gradle的Web项目(https://start.spring.io/)项目结构解析├─build.gradle ① ├─gradlew ② ├─gradlew.bat ③ ├─settings.gradle ④ ├─gradle ⑤ │ └─wrapper │ ├─ gradle-wrapper.jar │ ├─ gradle-wrapper.properties └─src ⑥ ├─main └─test项目自动编译的时候要读取的配置文件。比如指定项目的依赖包等。build.grade有两个,一个是全局的,一个是在模块里面。全局的build.grade主要设置的是声明仓库源,gradle的版本号说明等。linux下的gradle环境脚本,可以执行gradle指令,如:./gradlew buildwindows下的gradle环境,可以执行gradle指令包含必要的一些设置,例如,任务或项目之间的依懒关系等,无论有多少个子模块,该文件只会有一个,且一定在根项目中;包含wrapper文件夹及其2个子文件,作用是:可以自动安装gradle环境程序源码build.gradle基础结构//插件声明 plugins { id 'org.springframework.boot' version '2.7.4' id 'io.spring.dependency-management' version '1.0.14.RELEASE' id 'java' } //该项目的坐标信息 group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' //仓库地址 repositories { //这里推荐使用阿里云的镜像仓库,待会下载jar包的时候速度会很快 maven { url 'https://maven.aliyun.com/repository/public/' } mavenCentral() } //第三方依赖jar dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' } //单元测试声明 tasks.named('test') { useJUnitPlatform() } 打开cmd命令行,我们使用gradlew build来编译这个项目(默认会下载一个gradle-x.x-bin.zip),并使用java -jar build/libs/demo-0.0.1-SNAPSHOT.jar运行起来。将项目导入到IDEA中,然后执行右侧build再启动服务到这里,我们前置准备工作已经就绪,接下来我们使用这个build.gradle来学习gradle的apiGradle的Project在gradle中,每一个build.gradle文件对应一个Project实例,我们在build.gradle中编写的内容,就相当于Project实例的属性或方法。构建初始化期间,Gradle实例化的步骤如下:给整个构建创建一个Settings实例,一个Settings实例就是一个settings.gradle文件针对Settings实例的配置,按配置层次扫描并解析配置在settings.gradle中的project。(其中settings中最为重要的属性是include)针对每个project对应的build.gradle进行初始,并创建Project实例(这里加载project的顺序是按照前面的配置层次进行,即广度扫描加载,这样可以保证父级模块加载完后,子模块才会被加载)一个完整的project由以下几个对象组成(实际上只由属性和方法组成)我们可以在build.gradle文件中任意使用gradle提供的属性或方法,如下://输出当前项目名称 println(project.name) //一般在当前build.gradle中使用时,默认会省略project println(name) //输出project中名字为name的属性 println(project.property("name")) //指定默认执行的task,即./gradlew不指定task时会执行该task defaultTasks('yourTask')属性内置属性可以直接赋值,无需声明group = 'com.it235' version = '1.0.0'自定义属性可以使用groovy语法,也可以与java语法结合//groovy定义属性 def pname = "projectName:" + project.name //java类型接收 String pname = "projectName:" + project.name使用ext名命空间来扩展属性,定义后可以在project、task、subproject中读取和更新ext.prop1 = "it235" ext.prop2 = "君哥聊编程"使用案例//REPOSITORY_HOME 和 REPOSITORY_URL apply plugin: 'maven' ext { REPOSITORY_HOME = 'http://maven.aliyun.com' REPOSITORY_URL = REPOSITORY_HOME + "/nexus/content/groups/public" } repositories { // 使用 mavenCentral()时,将远程的仓库替换为自己搭建的仓库 maven { url REPOSITORY_URL } } uploadArchives { repositories { mavenDeployer { snapshotRepository(url: REPOSITORY_HOME + "/nexus/content/repositories/snapshots/") { authentication(userName: 'xxx', password: 'xxx') } repository(url: REPOSITORY_HOME + "/nexus/content/repositories/releases/") { authentication(userName: 'xxx', password: 'xxx') } } } } 属性作用域读写属性时,Project 会按照下面范围的顺序进行查找的,在某个范围找到属性后就会返回该属性。如果没有找到,会抛出异常。Project 对象自身。这个范围里的属性包含 Project 实现类中定义有 getters 和 setters 方法的所有属性。比如:project.getName() 方法就对应了 name 属性。至于这些属性的可读写性取决于它们是否定义 getters 或者 setters 方法。Project 的ext属性 ( extra ) 。每个 Project 都会维护一个额外属性的映射,它可以包含任意的名称 -> 值对。定义后,此作用域的属性是可读写的。比如:project.ext.prop1 = 'it235' 。通过插件被添加到 Project 中的扩展属性 ( extensions ) 。每个扩展名都可以作为只读属性使用,其名称与扩展名相同。比如:project.android.compileSdkVersion 。通过插件添加到 Project 中的约定属性 ( convention ) 。插件可以通过 Project 的 Convention 对象向 Project 中添加属性和方法。此范围的属性的可读可写性取决于约束对象。Project 中 Tasks 。可以使用 Task 的名称作为属性名称来访问task。此范围的属性是只读的。ext的属性和约定属性从项目的父级继承,递归到根项目。此范围的属性是只读的。常用的project属性allprojects:包含此项目及其子项目的集合。buildDir:当前项目的编译目录(自动生成)默认值 porjectDir/builddefaultTasks:当前项目的默认任务的名字集,当前构建没有提供任务名时会执行这些默认任务group:当前项目的组名logger:当前项目的日志器,可以用来在 build 文件中写日志name:此项目的名称parent:此项目的父项目path:这个项目的绝对路径project:当前project对象实例rootDir:本项目的根目录。根目录是根项目的项目目录rootProject:当前项目层次结构中的根projectsubprojects:当前项目的子项目的集合tasks:本项目的task集合。version:此项目的版本方法方法作用域Project 对象自身build.gradle 脚本文件通过插件添加到 Project 中的扩展 ( extensions ) 。每个扩展都可以当做参数是闭包或 Action 的方法。插件添加到项目中的约定方法 ( convention ) 。插件可以通过项目的 Convention 对象向项目中添加属性和方法。项目中的 Tasks 。每个 Task 都会添加一个方法,方法名是任务名,参数是单个闭包或者 Action 。该方法使用提供的闭包为相关任务调用 Task.configure( groovy.lang.Closure ) 方法。常用的Project方法方法描述afterEvaluate可以添加一个闭包,它会在项目完成评估后立即执行。当执行属于该项目的构建文件时,会通知此类监听器。allprojects配置当前项目以及它的每个子项目apply应用零个或多个插件或脚本。beforeEvaluate添加一个闭包,它会在项目开始评估前立即执行configure通过闭包配置对象集合。copy复制指定的文件defaultTasks设置此项目的默认任务的名称。当开始构建时没有提供任务名称时使用这些。delete删除文件和目录exec执行外部命令file解析相对于该项目的项目目录的文件路径findProject按路径定位项目。如果路径是相对的,则相对于该项目进行解释。findProperty找特定属性,如果未找到,则返回给定属性的值或 nullgetAllTasks返回此项目中包含的任务的地图hasProperty确定此项目是否具有给定的属性javaexec执行 Java 主类javaexec执行外部 Java 进程。mkdir创建一个目录并返回一个指向它的文件。property返回给定属性的值。此方法定位属性如下:setProperty设置此项目的属性。此方法在以下位置搜索具有给定名称的属性,并将该属性设置在它找到该属性的第一个位置。subprojects配置本项目的子项目task创建Task具有给定名称的 a 并将其添加到此项目中uri将文件路径解析为 URI,相对于该项目的项目目录常用方法示例buildscript{}:配置当前gradle脚本自身需要使用的构建信息或依赖假设要执行一项指令./gradlew buildImage,构建docker镜像,而Gradle官方自身没有,则需要依赖到maven库下载或需要调用第三方插件,虽然这里是调用的task,但是task背后所依赖的插件是需要提前定义在buildscript中的,我们需要在buildscript{}中指定docker的依赖即可。apply plugin: 'idea' apply plugin: 'java' apply plugin: "maven" apply plugin: "war" apply plugin: "com.bmuschko.docker-remote-api" apply plugin: "org.springframework.boot" buildscript { repositories { mavenLocal() maven { url "https://maven.aliyun.com/repository/public" } mavenCentral() } dependencies { classpath "com.bmuschko:gradle-docker-plugin:3.3.4" classpath "org.springframework.boot:spring-boot-gradle-plugin:2.6.5" } }configurations{}:配置使用声明的依赖项用于特定目的(听君一席话,如听一席话)我们看个案例,下面的implementation和testRuntime就是Gradle帮我们提供的configuration,configurations{} 记录着项目中各个分组(implementation ,runtime)的依赖信息。dependencies { implementation "org.springframework.boot:spring-boot-starter-web" testRuntime "junit:junit:4.13" }简单一句话概括configurations{}的作用:将本项目需要的相关依赖资源进行分组,A组你可以提供在运行时使用,B组你提供在测试运行时使用,C组编译时使用,D组xxxx?因为configuration支持继承进行扩展,子配置会集成所有父配置的依赖,testImplementation extends implementation ,如下图 那implementation、testImplementation是谁帮我们提供的呢?当然是java的插件啦,插件声明如下(后面插件环节会单独讲解):apply plugin: 'java' //或者如下定义 plugins{ id 'java-library' }下图是java插件提供的测试相关的configuration关于扩展功能,还允许我们开发者自定义configurations { //声明一个具备加载测试依赖的configuration smokeTest.extendsFrom testImplementation //声明一个具备类编译和运行的configuration compileClasspath.extendsFrom(someConfiguration) runtimeClasspath.extendsFrom(someConfiguration) //全局排除log4j依赖 implementation.exclude group:'org.apache.logging.log4j' implementation.exclude module:'spring-boot-starter-log4j2' //声明一个具备类运行的configuration developmentOnly runtimeClasspath { extendsFrom developmentOnly } } dependencies { testImplementation 'junit:junit:4.13' //读取根目录下的lib目录 smokeTest fileTree('lib') smokeTest 'org.apache.httpcomponents:httpclient:4.5.5' //使用someConfiguration将lib模块添加到本项目依赖 someConfiguration project(":lib") developmentOnly("org.springframework.boot:spring-boot-devtools") } //将文件拷贝到另外一个目录 afterEvaluate{ //configurations属性可以在任意一个task中读取 println configurations.smokeTest.files.first() println configurations.smokeTest.asPath def libPath = projectDir.absolutePath + "/src/main/lib2" copy { from configurations.smokeTest.files.first() into libPath } }repositories{}:仓库配置通过 repositories{} 可以配置maven,ivy,local仓库。这样子,在dependencies{}声明的依赖就可以通过repositories{}中指定的仓库查询到具体的JAR资源。repositories { mavenLocal() mavenCentral() maven { // Name is optional. If not set url property is used name = 'Main Maven repository' url = 'https://maven.aliyun.com/repository/central' } //有权限控制的仓库 maven() { credentials { username = 'username' password = 'password' } url = 'https://maven.aliyun.com/repository/central' } //本地仓库 repositories { flatDir(dir: '../lib', name: 'libs directory') flatDir { dirs '../project-files', '/volumes/shared-libs' name = 'All dependency directories' } } }dependencies{}在gradle中dependencies{}是一等公民,它描述了configurations{}中分组依赖的第三方资源。我们可以把依赖简单的分成两大类:gradle依赖:主要是gradle运行的时候,需要加载一些插件,如android等,此时需要配置它。项目编译/运行依赖:编译一个项目,通常需要依赖其他项目或者JAR。依赖的常规写法(与maven一致)//配置依赖 dependencies { //compile已过时,推荐使用implementation //按照maven名称加载jar implementation 'com.google.guava:guava:11.0.2' //排除部分依赖 implementation('org.slf4j:slf4j-simple:1.6.4') { exclude 'org.slf4j:slf4j-api' } //测试模块 testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2' //依赖其他子项目 implementation project(':projectA') //加载目录下所有的jar implementation fileTree(dir: 'libs', include: ['*.jar']) //多个文件 implementation ('com.google.guava:guava:11.0.2'){ //在版本冲突的情况下优先使用该版本 isForce = true //禁用依赖传递 transitive = false } } //使用dependencis任务可以查看当前依赖树,*表示被忽略的allprojects{}:配置此项目及其每个子项目所需要的依赖。一般在多模块项目场景下我们会把公共的部分配置在根项目的allprojects中。apply plugin: 'java' apply plugin: "maven" apply plugin: "war" apply plugin: "com.bmuschko.docker-remote-api" apply plugin: "org.springframework.boot" buildscript { repositories { mavenLocal() maven { url "https://maven.aliyun.com/repository/public" } mavenCentral() } dependencies { classpath "com.bmuschko:gradle-docker-plugin:3.3.4" classpath "org.springframework.boot:spring-boot-gradle-plugin:2.6.5" } } allprojects { apply plugin: "idea" repositories { mavenLocal() maven {url "https://maven.aliyun.com/repository/public"} mavenCentral() } // 依赖管理器 dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR12' mavenBom 'com.alibaba.cloud:spring-cloud-alibaba-dependencies:2.2.6.RELEASE' } } // ...各种配置... //依赖 dependencies { implementation("org.springframework.boot:spring-boot-starter") { exclude module: "tomcat-embed-el" } implementation("org.springframework.boot:spring-boot-starter-web") api 'com.squareup.retrofit2:retrofit:2.4.0' //仅编译 compileOnly 'org.projectlombok:lombok:1.18.16' annotationProcessor 'org.projectlombok:lombok:1.18.16' //仅测试编译 testCompileOnly 'org.projectlombok:lombok:1.18.16' testAnnotationProcessor 'org.projectlombok:lombok:1.18.16' // ...依赖超级多的 jar 包,包括公司的包和三方包.... implementation "xxx.xxx.xxx" ... } }subprojects{}:子模块配置sourceSets{}:配置源码信息maven和gradle默认约定的项目目录一样,如下:src/main/ src/main/java/ src/main/resources/ src/test/ src/test/java/ src/test/jresources/但是Gradle提供了修改的默认目录的功能,官方叫Changing the project layout,对于Java来说基本上不会调整项目结构,但也会存在特殊情况,如:过滤掉某个main目录下的文件夹,将一个外部文件夹纳入源码资源管理中。这个时候sourceSets登场了,sourceSets的功能攘括了源文件及位置定义、相关依赖管理、定义编译输出的位置这3块。(其中main和test默认被官方定义在的sourceSets,无需手动指定)sourceSets有一些属性,比如名字、源文件位置、资源位置、classpath等等,这些属性有些是只读的,有些是可写的,并且可以被当作变量来引用。有可写的属性那么就提供了自定义的功能了,比如,你可以改变一个SourceSet源代码的位置,像这样下面这样,你就改变了main这个SourceSets的源代码目录和资源目录。sourceSets { main { java { srcDirs = ['src/java'] } resources { srcDirs = ['src/resources'] } } }这样,gradle就只在src/java下搜源代码,而不是在src/main/java下。如果你只是想添加额外的目录,而不想覆盖原来的目录,则像下面这样:sourceSets { main { java { srcDir 'thirdParty/src/main/java' } } }此时,gradle就会同时在src/main/java和thirdParty/src/main/java两个目录下都进行源代码搜索。注意:你可要注意srcDir和srcDirs的区别同时我们还可以排除classpath下面的某个目录,你可以这样做,执行build之后,你在jar或war包的classes中不会见到排除掉的目录sourceSets { main{ java { exclude 'com/it235/first/ct/**' } resources { exclude 'cert/**' } } } 我的目录结构 first-gradle src main java com.xxx resources cert test.cert application.properties 指定源码输出目录sourceSets { main { //指定main的源码输出位置 output.resourcesDir = output.classesDir = 'WebContent/WEB-INF/classes/' //源码目录 java.srcDir('src') //资源文件目录 resources.srcDir('src') } }什么情况下我们会添加一个新的SourceSets 原始的src/test目录我们一般存放单元测试,但是对于集成测试部分不应存放在src/test目录下,因为这些测试有它们自己的dependencies, classpaths, 以及其他的资源,所以这些测试应该是单独分开的,而不应该和单元测试混为一谈。这样会将2个测试工作进行混淆,也是架构师们不希望看到的局面,所以我们需要重新定义一个用于集成测试的代码环境。但是,集成测试和验收测试也是这个项目的一部分,因此我们也不能为集成测试或验收测试单独建立一个项目。它们也不是子项目,所以用subproject也不合适。此时,我们就可以新建一个集成测试或者验收测试的SourceSets,把相关的测试资源管理起来。在src目录下新建一个intTest作为集成测试的环境,与src/main、src/test保持一致,格式如下first-gradle src intTest -- 存放集成测试的源码 java resources main -- 存放应用源码 java resources test -- 存放单元测试源码 java resources为他们创建一个新的sourceSets将所需的依赖项添加到该sourcesSets配置中为该源集配置编译和运行时类路径configurations { //intTestImplementation 扩展至implementation,说明它具备生产源码的各项依赖 intTestImplementation.extendsFrom implementation intTestRuntimeOnly.extendsFrom runtimeOnly //如果集成测试需要单元测试的依赖可以采用如下做法 //intTestImplementation.extendsFrom testImplementation } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' intTestImplementation 'org.springframework.boot:spring-boot-starter-test' } sourceSets { //这里因为我们的intTest目录与闭包命名一致,所以不使用srcDir添加也能自动识别 intTest { //将正式项目的源码编译和运行时路径给到intTest,记住是+=,而不是覆盖 compileClasspath += sourceSets.main.output runtimeClasspath += sourceSets.main.output } }创建一个任务来运行集成测试//任务类型是Test task integrationTest(type: Test) { description = 'Runs integration tests.' //这里指定后,该任务将会在IDE右侧的verification分组里面 group = 'verification' //配置已编译的测试类的位置 testClassesDirs = sourceSets.intTest.output.classesDirs //运行我们的集成测试时使用的类路径。 classpath = sourceSets.intTest.runtimeClasspath //声明在test单元测试完成后自动执行集成测试(非必须) shouldRunAfter test //mustRunAfter表示必须在test任务执行完成之后执行 } //定义一个检查任务,让集成测试在检查任务之前运行,如果集成任务构建失败,检查任务也会失败(非必须) check.dependsOn integrationTest最后注意定义test单元测试引擎tasks.withType(Test) { useJUnitPlatform() } //以下写法不生效,暂未找到原因 test { useJUnitPlatform() }完整的配置如下buildscript { repositories { mavenLocal() maven { url "https://maven.aliyun.com/repository/public" } mavenCentral() } dependencies { classpath "org.springframework.boot:spring-boot-gradle-plugin:2.6.5" } } plugins { id 'org.springframework.boot' version '2.6.4' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'com.it235' version = '1.0.0' repositories { maven { url 'https://maven.aliyun.com/repository/central' } } configurations { intTestImplementation.extendsFrom implementation intTestRuntimeOnly.extendsFrom runtimeOnly } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' intTestImplementation 'org.springframework.boot:spring-boot-starter-test' } sourceSets { intTest { compileClasspath += sourceSets.test.runtimeClasspath runtimeClasspath += sourceSets.test.runtimeClasspath } } tasks.withType(Test) { useJUnitPlatform() } task integrationTest(type: Test) { //tasks.register('integrationTest', Test) { description = 'Runs integration tests.' group = 'verification' testClassesDirs = sourceSets.intTest.output.classesDirs classpath = sourceSets.intTest.runtimeClasspath shouldRunAfter test } check.dependsOn integrationTestartifacts{}:配置需要交付的产品组件信息artifacts的意思是产品、产物,可以理解为一个可以交付的jar或者war。artifacts主要的用途:定义好产物构建规则,然后结合archives或publishing发布到私服或中央仓库传统的构建artifacts一般是结合IDE工具进行的,这里我新建一个普通的Gradle项目:second-gradle,新建也给Java类package com.it235.second; public class SecondApplication { public static void main(String[] args) { System.out.println("start SecondApplication"); } }然后采用IDEA的传统方式进行打包,流程如下:File->Project Structrue->Project Settings->Artifacts,选中From modules with dependencies 15年前我们可能经常这么干,但是以上的操作不够灵活,比如多模块的情况下我们有特殊处理,版本也没有办法管理,随着Maven和Gradle的强大,我们采用了项目管理工具来构建,也就是接下来我要说的。生成artifact产品主要有以下3种途径使用已经定义好的Tasktask myJar(type: Jar) artifacts { archives myJar } 或 configurations { xxx } task myJar2(type: Jar) artifacts { xxx myJar2 } //仔细一看,好像干了什么,又好像什么都没干,使用示例如下 //实例1,定义jar包构成 task myJar2(type: Jar){ from compileJava.outputs , "src/main/resources" manifest { attributes( "Manifest-Version": 1.0, 'Main-Class': 'com.it235.second.SecondApplication', ) } } //实例2,生成sql.zip task dbChangelogZip(type: Zip) { from 'src/main/resource4db' baseName 'order-db-changelog' //输出的目标目录 destinationDir(file('build/libs')) } //实例3,生成source源码 task mySourceJar(type: Jar){ archiveClassifier = 'sources' from sourceSets.main.allSource } artifacts { archives myJar2 archives mySourceJar archives dbChangelogZip }使用一个file文件(暂时没有想到使用场景)def someFile = file('build/somefile.txt') artifacts { archives someFile } artifacts { archives file: file('build/libs/second-gradle-1.0-SNAPSHOT.jar'), builtBy: war }publishing{}:deploy当前项目到仓库plugins { ... id 'maven-publish' ... } publishing { publications { //打springboot-jar包 uesJar(MavenPublication) { artifact bootJar from components.java } //可单独执行publishDbchangelogPublicationToUesRepository 上传dbchangelog压缩包 dbchangelog(MavenPublication) { artifactId 'ues-db-changelog' artifact dbChangelogZip } } repositories { maven { //忽略https allowInsecureProtocol true name = 'ues' def releasesRepoUrl = "http://192.168.35.xx:8081/nexus/content/repositories/releases/" def snapshotsRepoUrl = "http://192.168.35.xx:8081/nexus/content/repositories/snapshots/" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl credentials { username 'admin' password 'admin123' } } } }总结本篇到这里就结束了,通过对Project的学习和了解,我们知道了build.gradle的构成,已经每个内置api的基本使用,后续文章会跟着实战来详细介绍这些api的使用,我们下一篇见。
关注我,这里有你意想不到的惊喜Gradle从0入门到实战系列【一】Hello World GradleGradle从0入门到实战系列【二】Groovy And GradleGradle从0入门到实战系列【三】build.gradle之ProjectGradle从0入门到实战系列【四】build.gradle之TaskGradle从0入门到实战系列【五】工程化之插件Gradle从0入门到实战系列【六】生命周期剖析Gradle从0入门到实战系列【七】一个简单完整的build.gradleGradle从0入门到实战系列【八】SpringBoot集成Junit单元测试Gradle从0入门到实战系列【九】仓库与私服Gradle从0入门到实战系列【十】集成Dockerfile构建Docker镜像认识GradleGradle是一种开源自动化构建工具,支持多语言环境,受Ant、Maven思想的影响,集二者之大成,相比Ant的不规范,Maven的配置复杂、生命周期限制较多,Gradle既规范也更灵活,可以使用DSL(领域特定语言,如Groovy)编写构建,脚本更加精悍。本系列基于Gradle7讲解。Ant:2000年发布,纯java编写,几乎很少使用纯Ant进行项目的管理和构建。Maven:2004年发布,采用pom.xml管理项目,目前最火热的Java项目管理工具。Gradle:2012年发布,google背书、Android项目开发主流构建工具,Spring项目源码采用该工具构建。我们为什么要学Gradle?他有哪些优势优势:灵活性:相对于 Maven、Ant 等构建工具,Gradle 提供了一系列的 API 让我们有能力去修改或定制项目的构建过程。粒度性:源码的编译,资源的编译,都是一个一个Task的,我们可以修改task来达到精细控制上。扩展性:Gradle 支持插件机制,所以我们可以复用这些插件,就如同复用库一样简单方便。兼容性:Gradle 不仅自身功能强大,而且它还能兼容所有的Maven、Ant功能,也就是说,Gradle 吸取了所有构建工具的长处。众多大厂开源代码开始由Maven转向了Gradle管理,这已经是趋势劣势:每一个版本都较上一次有非常大的改动,没有做较好向上兼容学习成本高,需要学习groovy脚本语言经常遇到一些莫名其妙的问题,会有一些踩坑成本项目管理专业术语构建(build):Gradle和Maven一样,提供了构建相关的Task,而构建是一种抽象概念,我们能执行的如install、build、clean、package等都称为构建,现阶段你可以通俗理解构建就是打包。仓库(repository):存放jar包的仓库,我们常使用到的第三方jar包,如spring-aop-5.3.jar、hibernate-3.x.jar、mybatis-3.5.jar都存放在中央仓库中,仓库的详细知识在后面的文章中会给大家详细讲解。坐标(GAV):我们曾学过x坐标、y坐标以及生活中的经纬度,他们都是用来定位的,所以在Gradle和Maven中也需要有坐标的概念,用来拉取中央仓库中的某一个具体的jar包。groupId:组织或机构的域名倒写,如com.taobao、org.springframeworkartifactId:第三方jar包名称version:版本号示例Maven<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.8</version> </dependency> 示例Gradle dependencies { implementation 'org.springframework:spring-aop:5.3.23' }Gradle组成GroovyGroovy 是一种基于 JVM 的动态语言,他的语法和 Java 相似,最终也是要编译 .class 在JVM上运行。Groovy 完全兼容 Java 并且在此基础上添加了很多动态类型和灵活的特性,比如支持闭包,支持DSL(领域特定语言),是一门非常灵活的动态脚本语言。要执行Groovy的脚本必须要安装Groovy环境,或者使用Java的ClassLoader来调用,大家初次学习Groovy不需要安装环境,可以寻找在线的云编译环境运行。第二篇文章会进行详细的语法讲解。build script block构建脚本快由Groovy语法组成,是由Gradle内置+插件提供的一些闭包,后面内容会详细给大家剖析buildscript{}configurations{}repositories{}dependencies{}allprojects{}subprojects{}sourceSets{}artifacts{}publishing{}gradle api内置属性:如project、version、group、parent内置方法:property、copy、apply、afterEvaluate等安装Gradle因为gradle不同版本兼容性较差,在实际工作中,我们一般不会直接下载安装,而是采用idea+wrapper的形式来进行编译和使用,这里学习使用安装一次。环境:windows、java8,戳我Gradle官网进行下载,下载后解压到本地binary-only版本(-bin后缀):只有可执行文件complete版本(-all后缀):除了可执行文件还包含Gradle的源码和源码文档说明下载后解压,配置环境变量新建GRADLE_HOME环境变量,将gradle根目录配置在path中加入项%GRADLE_HOME%\bin,类似于JDK或Maven的配置打开CMD,执行gradle -v,成功输出版本则表示安装配置完成Mac平台下推荐使用Homebrew包管理器自动安装,输入brew install gradle默认会下载binary-only版本 C:\Users\Ron>gradle -v Welcome to Gradle 7.4.1! ------------------------------------------------------------ Gradle 7.4.1 ------------------------------------------------------------ Build time: 2022-03-09 15:04:47 UTC Revision: 36dc52588e09b4b72f2010bc07599e0ee0434e2e Kotlin: 1.5.31 Groovy: 3.0.9 Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021 JVM: 1.8.0_131 (Oracle Corporation 25.131-b11) OS: Windows 10 10.0 amd64用Gradle说Hello World新建build.gradle文件(名字不能为其他否则无法执行)在其内部输入task say{ println "hello world" }在cmd环境下执行gradle -q say回车查看结果参数解析-q :输出QUIET级别及其之上的日志信息,只显示错误日志 -i :输出INFO级别及其之上的日志信息 -d :输出DEBUG级别及其之上的日志信息每一个task都是org.gradle.api.DefaultTask类型,在构建脚本中我们可以直接使用属性名使用该类中的相关属性,在底层Groovy会为你调用相关的getter和setter方法去访问这些属性,这里我们简单使用doFirst或doLast方法试试,后续会对task做详细的讲解。task say{ println "hello world" doFirst{ println "ready" } doLast{ println "thanks" } }总结本篇就到这里了,带大家认识和学习了gradle的基础,用好gradle的前提是我们需要掌握Groovy基础语法,下一篇我们来学习Groovy,有疑问可以在评论区留言讨论
2022年10月