基于maven的profile实现动态选择配置文件

需求

根据选择不同的部署环境自动替换相关配置变量,如连接的数据库等。

最终效果概览

  • 部署环境分为dev和release
  • 工程目录结构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    myproject
    |-profile
    | |-dev
    | | |-dbconfig.properties
    | |-release
    | |-dbconfig.properties
    |-src
    | |-main
    | |-java
    | |-webapp
    | |-resources
    | |-dbconfig.properties
    |-pom.xml
  • 部署时执行命令
    • dev
      mvn clean package -Pdev
    • release
      mvn clean package -Prelease

实现步骤

1. 编辑各环境的变量

按如下结构创建目录和文件(各目录、文件的名字和路径均可更改,与下一步的配置对应即可)

1
2
3
4
5
6
myproject
|-profile
| |-dev
| | |-dbconfig.properties
| |-release
| |-dbconfig.properties

dev/dbconfig.properties内容如下

1
2
3
4
jdbc.url=jdbc\:oracle\:thin\:@111.00.00.111\:1521\:orcl
jdbc.username=myproject
jdbc.password=myproject_test
jdbc.dbType=oracle

release/dbconfig.properties内容如下

1
2
3
4
jdbc.url=jdbc\:oracle\:thin\:@222.00.00.222\:1521\:orcl
jdbc.username=myproject
jdbc.password=myproject_release
jdbc.dbType=oracle

项目实际访问的数据库配置文件位于myproject/src/main/resources/dbconfig.properties,内容如下

1
2
3
4
jdbc.url=${jdbc.url}
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}
jdbc.dbType=${jdbc.dbType}

在部署过程中${xxx}将被替换为对应环境的值,变量名称与dev/dbconfig.properties、release/dbconfig.properties的字段名对应。

2. pom.xml中配置profile

在pom.xml的<project></project>中添加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<profiles>
<profile>
<!-- 开发环境 -->
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<filters>
<filter>${basedir}/profile/dev/dbconfig.properties</filter>
</filters>
</build>
</profile>

<profile>
<!-- 生产环境 -->
<id>release</id>
<build>
<filters>
<filter>${basedir}/profile/release/dbconfig.properties</filter>
</filters>
</build>
</profile>
</profiles>

此处添加两个profile,分别为开发环境的dev和正式环境的release,其中dev环境配置了<activeByDefault>true</activeByDefault>,指定dev为默认配置

3. 激活过滤资源

pom.xml配置资源时,针对待替换的资源设置<filtering>true</filtering>。本例中配置为src/main/resources下的所有properties文件都需要检测替换。

1
2
3
4
5
6
7
8
9
10
11
12
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>

4. 部署

mvn命令中-P可指定profile
根据配置中profile的id可选择对应的部署环境,如部署到正式环境时执行如下命令即可完成打包
mvn clean package -Prelease

其他

IDEA中的使用

在idea的Maven Project标签中可以看到项目配置的profile,如果通过<activeByDefault>true</activeByDefault>指定了默认环境,在项目构建过程将会自动勾选默认的环境,如此可以在其他开发成员对profile概念不熟悉时仍然能正常运行项目。


【杂谈】我又迁服务器了

之前

迁移前我是用着香港VPS的,为什么选择境外?因为可以用来搭建VPN,而且价格相对便宜。

为什么要迁

  1. 国内很多服务都需要备案的域名才能使用,如微信公众号开发、七牛CDN等,无奈~
  2. 阿里云有活动,服务器便宜啊!

为什么不想迁

备案麻烦这一点不是我所顾虑的,毕竟我就是想备案。不想迁的主要原因是境内服务器无法搭建科学上网服务,现在我只能另外再买个最低配的VPS专门搭建VPN,而且因为价格问题买不到香港的VPS,只能选择美国西岸的机子,速度比较勉强,心痛。

迁移前的顾虑

应该也有其他人有跟我一样的顾虑,我大致说说

  • 1M带宽够吗
    境外的VPS一般都是100M带宽限流量的模式,阿里云参与活动的机子都是固定带宽的,1M看起来好像什么都做不了。首先我担心的是上传速度问题,按我们的理解1M对应的下载速度大概只有100k/s,要是上传速度只有100k/s,那要是做javaweb开发时上传个war包都能等哭。其实阿里云ECS的上传速度是不限制的(用户上传到服务器的操作对服务器来说是下载,所以准确点说是阿里云服务器的下载速度是不限制的),只跟本地上传速度相关,所以上传文件这一点不需要担心。另外一点就是网站访问速度,服务器的带宽需求一般是根据同时在线人数计算的,就是理解为一秒内请求的文件大小在1M以内带宽都是通畅的,只要带宽通畅那速度是跟10M都是一样的,所以对于1M够不够用这个问题只要考虑网站的并发量即可,不要像个人带宽一样以为10M带宽就一定比1M更快,作为个人博客1M带宽一般都足够的。
  • 1核1G内存够吗
    带宽只需考虑并发量,而CPU核数是真正影响到计算能力的,理论上肯定是CPU越强响应速度越快,不过对于个人博客,在并发压力不高的情况下1核的处理能力也是可以满足的,在其他方面优化可能比购买更高核数配置的机器效果更明显,如CDN等。(为了减少机子计算压力和方便以后服务迁移,我是另外购置了数据库服务的,当然1核1G的机子再装个mysql我觉得也是问题不大的,这一点我没亲测~)

Ansible实战[附录一]——Ansible命令参数列表
-m MODULE\_NAME, --module-name=MODULE\_NAME     要执行的模块,默认为 command  
-a MODULE_ARGS, --args=MODULE_ARGS      模块的参数  
-u REMOTE_USER, --user=REMOTE_USER ssh      连接的用户名,默认用 root,ansible.cfg 中可以配置
-k, --ask-pass      提示输入 ssh 登录密码,当使用密码验证登录的时候用     
-s, --sudo      sudo 运行
-U SUDO_USER, --sudo-user=SUDO_USER     sudo 到哪个用户,默认为 root
-K, --ask-sudo-pass     提示输入 sudo 密码,当不是 NOPASSWD 模式时使用
-B SECONDS, --background=SECONDS            run asynchronously, failing after X seconds(default=N/A)
-P POLL_INTERVAL, --poll=POLL_INTERVAL      set the poll interval if using
-B (default=15)
-C, --check     只是测试一下会改变什么内容,不会真正去执行
-c CONNECTION   连接类型(default=smart)
-f FORKS, --forks=FORKS     fork 多少个进程并发处理,默认 5
-i INVENTORY, --inventory-file=INVENTORY        指定 hosts 文件路径,默认 default    =/etc/ansible/hosts
-l SUBSET, --limit=SUBSET       指定一个 pattern,对<host_pattern>已经匹配的主机中再过滤一次
--list-hosts        只打印有哪些主机会执行这个 playbook 文件:不是实际执行该 playbook
-M MODULE_PATH, --module-path=MODULE_PATH       要执行的模块的路径,默认为/usr/share/ansible/
-o, --one-line      压缩输出,摘要输出
--private-key=PRIVATE_KEY_FILE      私钥路径
-T TIMEOUT, --timeout=TIMEOUT   ssh 连接超时时间,默认 10 秒
-t TREE, --tree=TREE            日志输出到该目录,日志文件名会以主机名命名
-v, --verbose   verbose mode (-vvv for more, -vvvv to enable connection debugging)

Ansible实战[三] ——自动化部署实战例子

前言

本文中的脚本例子没实际执行过,是基于成功运行过的脚本提炼修改而成,语法细节上可能会有错漏,主要用于理解过程。
另外,例子中基本只使用到shell模块,有些操作用ansible自带模块效果更佳,在执行过程中ansible也会有相应优化提示,读者可自行优化。

场景

  • 控制终端 10.10.10.10
  • 受控节点
    • group1
      • 11.11.11.11
    • group2
      • 12.12.12.12
      • 13.13.13.13
  • 实现目标
    达到不停服务完成自动化发布的目的。
  • 执行过程
    1. 上传war包到控制终端;
    2. 从控制终端复制war包到group1节点;
    3. 复制group1节点上tomcat原来部署的war包到控制终端备份,并追加时间戳重命名;
    4. 重启tomcat进行部署;
    5. 检测group1节点上的tomcat是否成功启动,如果成功启动则继续往下执行;
    6. 从控制终端复制war包到group2所有节点,然后重启tomcat进行部署。

操作流程

一、配置主机

hosts清单如下

[group1]
11.11.11.11
[group2]
12.12.12.12
13.13.13.13
[myproject:children]
group1
group2

二、配置变量

vim /etc/ansible/group_vars/myproject

ansible_ssh_user: deploy
new_war_location: /var/deploydata/deploy/ROOT.war
backup_location: /var/deploydata/deploy/backupwar
tomcat_home: /home/deploy/tomcat/apache-tomcat-7.0.73_9898_d
control_machine_ip: 10.10.10.10

三、编写脚本

注:此处默认主机间可以相互免密登录,具体配置可参考《linux配置ssh免密登录》
脚本代码如下

main.yml

- name: deploy group1
  hosts: group1_d
  tasks:
    # 备份旧war包
    - name: backup war
      shell: scp  {{ tomcat_home }}/webapps/ROOT.war root@{{ control_machine_ip }}:{{ backup_location }}/ROOT$(date +"%Y%m%d%H%M%S").war
      # 忽略tomcat从未部署过war包会提示ROOT.war不存在的错误
      ignore_errors: yes
    - name: del old war
      shell: rm -f {{ tomcat_home }}/ROOT.war
      ignore_errors: yes
    - include: tomcat_deploy.yml
    # 暂停40s等待tomcat的启动过程
    - name: sleep 40s
      shell: sleep 40s
- name: deploy group2
  hosts: group2_d
  tasks:
    # 检测group1主机的tomcat状态
    # (curl命令会持续监听待检测服务直到其作出响应或超时,所以虽然上边定义只暂停40s,
    # 但是实际情况tomcat的启动过程稍微大于40s是允许的)
    - name: tomcat status
      shell: curl http://10.46.66.94:9898 &>/dev/null && echo YES || echo NO
      # 此处忽略错误是因为无论成功与否都要把输出结果注入到register的变量中
      ignore_errors: True
      # 把上一条命令的执行结果赋值给is_tomcat_success
      register: is_tomcat_success
    - include: tomcat_deploy.yml
      # 此处可理解为当is_tomcat_success.stdout 为yes时,则执行include tomcat_deploy.yml
      # (命令执行结果是一个map形式,其中包含stdout代表标准输出,所以此处是is_tomcat_success.stdout获取变量值)
      # (如果需要查看变量的具体结果,可以使用debug模块查看指定变量
         - name: Show debug info 
           debug: var=is_tomcat_success verbosity=0)
      when: is_tomcat_success.stdout == "YES"

tomcat_deploy.yml

- name: stop tomcat
  action: shell {{ tomcat_home }}/bin/shutdown.sh
  # 忽略原本并没有tomcat运行的错误提示
  ignore_errors: yes
- name: del ROOT dir
  shell: rm -rf {{ tomcat_home }}/webapps/ROOT
  ignore_errors: yes
- name: del old war
  shell: rm -f {{ tomcat_home }}/webapps/ROOT.war
  ignore_errors: yes
- name: copy war
  shell: scp  root@{{ control_machine_ip }}:{{ new_war_location }}  {{ tomcat_home }}/webapps/ROOT.war
- name: start tomcat
  shell: chdir={{ tomcat_home }}/bin nohup ./startup.sh &

四、执行脚本

ansible-playbook main.yml

[目录]
[上一篇] Ansible实战[二] ——playbook初探



Ansible实战[二] ——playbook初探

简述

Ansible的脚本被称为playbook,playbook是基于YAML语法进行编写的,一个playbook会包含多个play,每个play中会包含一系列的任务,所有的play组合起来共同完成一系列的运维操作。

YAML语法简介

1、列表

每一个YAML文件都是从一个列表开始,列表中的所有成员都开始于相同的缩进级别, 并且使用一个”- “作为开头(一个横杠和一个空格),如

# Address Book
- Amy
- Jerry
- Tom

2、哈希/字典

在YAML中使用键值对表示成员属性,它们被称为“哈希” 或 “字典”,如

name: amy
age: 20

也可以用以下语法表示字典集(在Ansible中不常见)

{name: amy, age: 20}

更常见的情况是每一个列表都是包含一组字典集,如

# Address Book
- name: Amy
  age: 20
- name: Jerry
  age: 21
  job: teacher
- name: Tom
  age: 25

playbook结构示例

#playbookdemo.yml
#play1
- hosts: group1
  vars: 
    tomcat_home: ~/tomcat
  tasks:
    - name: shutdown tomcat
      shell: {{ tomcat_home }}/bin/shutdown.sh
    - name: startup tomcat
      shell: {{ tomcat_home }}/bin/startup.sh
#play2
- hosts: group2
  tasks:
    - name: restart nginx
      shell: service nginx restart

示例详解:

  • 实例playbook中包含了两个play,分别针对group1主机组和group2主机组进行了一些操作
  • play1的tasks中包含了两个操作,而两个操作都是通过调用shell模块执行linux命令
  • 除了shell模块,Ansible还内置了许多非常常用的模块,如远程复制的copy模块
  • 脚本中通过的形式使用变量,变量的定义可以在play中的vars定义,也可以在之前文章提到的在hosts文件、组变量文件中定义
  • tasks中的每一个任务都指定name值可以提高脚本执行时的易读性

执行ansible脚本

通过ansible-playbook命令可以执行yml脚本,如

ansible-playbook demo.yml

执行后输出结果如图所示
QQ截图20170317084823.png

如果希望能够看见执行过程中的调试信息可增加-verbose参数

ansible-playbook -verbose demo.yml

[目录]
[上一篇] Ansible实战[一]——Ansible基础
[下一篇] Ansible实战[三] ——自动化部署实战例子



linux配置ssh免密登录

简述

ssh 无密码登录需要使用公钥与私钥,在linux下可以使用ssh-keygen生成公钥/私钥对

场景

  • 将要登录的主机ip为10.10.10.10
  • 以deploy用户登录

步骤

  1. 生成key

    ssh-keygen

    QQ截图20170310175140.png

  2. 复制key
    过程中需要输入ssh登录密码
    ssh-copy-id -i ~/.ssh/id_rsa.pub deploy@10.10.10.10

  3. 测试登录
    若配置成功则无需输入密码即可成功ssh登录
    ssh root@192.168.0.4

  4. 退出
    exit


Ansible实战[一]——Ansible基础

一、Ansible简介

Ansible是新出现的自动化运维工具,基于Python开发,集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。Ansible本身只提供一个框架,真正工作的是它内置的各种模块,通过各模块的组装配合完成一系列操作。Ansible通常以执行脚本的方式去实现自动化运维,Ansible的脚本被称为playbook,基于yaml语法编写,一个playbook可以包含多个play,每个play中可以通过调用多个模块实现部署操作。

二、安装Ansible

在CentOS下直接yum即可安装

yum install ansible

输入ansible –version输出如下信息即代表安装成功
QQ截图20170310143837.png

Ansible的配置文件目录结构

-- /etc/ansible
  |-- ansible.cfg    # ansible的相关配置项
  |-- hosts          # 待连接的主机列表(可以配置独立主机或主机组)

三、基于命令行初探Ansible

3.1 写在前边

ansible主要还是以编写playbook脚本,然后执行playbook脚本来操作节点,比较少直接在控制端逐条地输入执行命令去操作受控节点,所以本小节只介绍ansible命令的简单用法,用于初步感受ansible的执行方式,后边章节将会讲解更为常用的playbook部分。

3.2 场景介绍

  • 控制终端 10.10.10.10

  • 受控节点

    • 11.11.11.11

    • 12.12.12.12

      3.3 修改hosts文件配置主机列表

      hosts文件里的初始备注已经列举了配置例子,看两眼基本都能懂,此处就不详细介绍语法了。本例中我们把受控节点的两个服务器ip归回test组,hosts文件配置如下

      [test]
      11.11.11.11
      12.12.12.12

3.4 测试连接受控节点

输入如下命令

ansible test -m ping -u deploy -k

若执行结果返回的是pong则代表访问成功

11.11.11.11 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}

例子详解

  • test 指定需要控制的主机组为test,若指定为all则hosts文件中所有的主机都将被控制
  • -m 指定将要执行的模块
  • ping 执行ansible的ping模块
  • -u 指定以哪个用户登录受控节点,例子中指定登录用户为deploy,若不指定默认为root
  • -k 提示输入 ssh 登录密码,当使用密码验证登录的时候用

3.5 配置免密登录

每次执行ansible命令都指定密码是非常不方便的,而把密码在配置文件中填写只能用明文保存,这样安全性很低。为解决这个问题,通常的做法是配置主机间免密登录,具体配置方式请参考《linux配置ssh免密登录》

3.6 命令参数

参见《Ansible实战[附录一]——Ansible命令参数列表》

四、主机管理进阶

4.1 组嵌套

利用组嵌套可实现组与组之间的层级关系,提高可管理性
以下配置指定了group1、group2隶属于test组下

[group1]
10.10.10.10
[group2]
11.11.11.11
[test:children]
group1
group2

4.2 变量

通过指定变量可以在ansible执行时自动获取相应变量,例如指定变量ansible_ssh_user后在连接到主机时将会自动以变量中指定的用户登录,无需每次敲写指定用户。除了ansible_ssh_user外还有许多其他的系统参数可以更改,如ansible_ssh_pass等;当然除了系统参数外,还可以填写自定义参数(在playbook中可以用到)。

4.2.1在hosts文件中指定变量
  • 方法一 在主机后直接带上变量

    [test]
    10.10.10.10 ansible_ssh_user=deploy1
    11.11.11.11 ansible_ssh_user=deploy2

  • 方法二 对一个指定变量

    [test]
    10.10.10.10
    11.11.11.11

    [test:vars]
    ansible_ssh_user=deploy

4.2.2 在外部添加变量

  • 所有主机共享变量
    在/etc/ansible目录下创建host_vars文件,在该文件中可以指定特定变量,该变量将会对所有主机生效。
    注意变量与值之间是用冒号而不是等号

    ansible_ssh_user: deploy
    ansible_ssh_pass: 123456

  • 组内共享变量
    在/etc/ansible目录下创建group_vars目录,在该目录下以hosts文件中配置的组为文件名创建文件,如test,然后在新建的文件中指定变量则可实现组内共享变量。
    注意变量与值之间是用冒号而不是等号

    ansible_ssh_user: deploy
    ansible_ssh_pass: 123456


[目录]
[下一篇] Ansible实战[二]——playbook初探



Ansible实战[目录]
Nexus搭建Maven私服[下篇]——Nexus使用

Nexus搭建Maven私服[上篇]——Nexus安装


登录Nexus

点击右上角的login,输入账号密码即可登录,初始账号密码为admin/admin123
21.png

管理仓库

点击左边的Repositories即可打开仓库管理页面
22.png

仓库类型

Nexus中有四种仓库类型group,hosted,proxy,virtual,这里我们不关心virtual,只介绍下另外三种类型:

  • hosted 本地仓库,通常我们会部署自己的构件到这一类型的仓库。
  • proxy 代理仓库,它们被用来代理远程的公共仓库,如maven中央仓库。
  • group 仓库组,用来合并多个hosted/proxy仓库,通常我们配置maven依赖仓库组。

23.png

Nexus预定义的仓库

Nexus预定义了Public Repositories、3rd Party、Central、Releases、Snapshots这几个主要的分支(不同版本的Nexus对应的名称会有一点差异),主要作用如下:

  • Public Repositories 这是一个group类型的仓库,一般情况下里边包含了其他所有仓库,这样做的好处是我们在maven的pom.xml中只需配置一个仓库链接即可,无需针对所有仓库单独配置。
    在PublicRepositories的Configuration页中可以看到包含的仓库组,通过调整顺序可以更改优先匹配的仓库
    24.png
  • Central 这是proxy类型的,代理的是https://repo1.maven.org/maven2/,也就是Maven中央仓库
  • 3rd Party 用于部署第三方构件,有些构件如Oracle的JDBC驱动,我们不能从公共仓库下载到,我们就需要将其部署到自己的仓库中
  • Snapshots 用于部署我们自己的snapshot构件
  • Releases 用于部署我们自己的release构件

Release和Snapshots的区别

在配置hosted和proxy类型的仓库时,需要配置Repository Policy指定仓库类型是Release或Snapshots类型。Maven默认情况下是根据构件的版本号去更新依赖构件的,但是在开发过程中我们如果引入的构件还没进入稳定状态,构件也是在不断的修改和发布,如果使用Release类型则必须修改版本号才能更新依赖,针对这种情况我们可选择Snapshots类型。对于Snapshots类型,Maven会从镜像中获取构件,即使不更改版本号也能确保构件是最新的。但是也因为Snapshots类型代表着构件仍处于开发状态,是不稳定的构件,所以要根据实际需要选择Release或Snapshots类型。
25.png

Download Remote Indexes的配置

[这点很重要!]打开Central仓库的Configuration页,在Download Remote Indexes一项中默认是false状态,这是因为Nexus初始状态下防止在用户不知情的情况下自动下载构件,所以要求用户手动修改成true状态,只有修改为true才能下载Maven中央仓库的索引文件。注意所有proxy类型的仓库都要配置Download Remote Indexes为true才可用。
26.png

Download Remote Indexes为true后,选中对应仓库,右键选择Repair Index,然后Nexus就会开始下载远程仓库的索引文件,如果索引文件非常大,可能会花费比较久的时间。
27.png

要测试是否下载成功可以在Browser Index页中查看是否能够打开索引目录。
28.png

proxy仓库配置成功后可在Artifact Search中查找对应的构件,如搜索maven对应的搜索结果如下
29.png

上传私有构件

有些构件如Oracle的JDBC驱动,我们无法从公共仓库下载到,所以我们只能先上传到Nexus,创建私有构件。通常这些无法从公共仓库下载的构件我们会放在3rd party中。

  • 在3rd party仓库的Artifact Upload页中我们可以上传已下载的第三方jar包,步骤如图
    210.png
  • 上传后在Maven项目的pom.xml中添加对应的dependency即可引入对应构件
    211.png

Maven中使用Nexus

在Maven项目的pom.xml中配置repository指定到我们的public仓库地址即可
(注意:因为我们的public中包含了policy为napshots的仓库,所以在配置repository时需要设定snapshots enable为true)

<repositories>
    <repository>
        <id>public</id>
        <name>Public Repositories</name>
        <url>http://xx.com/nexus/content/groups/public/</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

Nexus搭建Maven私服[上篇]——Nexus安装

Nexus介绍

现今大部分的项目都已经是使用Maven进行构建,如果你使用Maven,你可以从Maven中央仓库下载所需要的构件(artifact),但是如果直接从Maven中央仓库下载可能会遇到下载速度慢的问题。Nexus可以用来搭建Maven私服,通过Nexus可以缓存Maven中央仓库的构件,节省带宽和提升下载速度。另外,利用Nexus还可以实现在内网中使用Maven、创建公共仓库不存在的构件(如私有jar包)等功能。

Nexus安装

  • 基础环境
    linux(CentOS)
    jdk7

  • 下载Nexus
    下载地址 http://www.sonatype.org/nexus/
    下载NEXUS REPOSITORY MANAGER OSS
    (FREE TRIAL是收费版)
    1.png

  • 解压Nexus
    解压Nexus到/usr/local/nexus目录下,解压后将会有两个目录
    nexus-2.14.2-01
    sonatype-work
    第一个目录是服务程序,第二个目录用于存放数据

  • 修改端口
    进入/usr/local/nexus/nexus-2.14.2-01/conf目录下,编辑nexus.properties文件,修改application-port即可
    2.png

  • 启动Nexus
    进入/usr/local/nexus/nexus-2.14.2-01/bin目录, 执行命令./nexus 即可运行

  • 修改运行用户
    启动过程中可能会报如下错误
    修改/usr/local/nexus/nexus-2.14.2-01/bin/nexus, 改为RUN_AS_USER=root,保存后重新执行./nexus即可。
    851491-20160729120732966-1744702207.png

QQ截图20170215172053.png


Nexus搭建Maven私服[下篇]——Nexus使用