Nexus部署

程小虎2025-12-28 09:15:26

📦 Nexus部署

📌 文档信息

  • 作者: 程小虎
  • 创建时间: 2025-12-30
  • 最近修订: 2026-01-07
  • 版本号: v1.0

📄 简述

​ 你正在开发一个项目,正好可以使用你自定义的starter,于是你将其引入到了项目中。如果你一直在你自己的电脑上开发的话,那没有什么问题。但是如果你换了电脑或者别人要协同你开发,重新从git上拉下代码,是下载不到你自定义的starter依赖的,因为starter一直都在你的本地maven仓库!

所以,无论对于个人还是公司,有一个私有maven仓库是很便利的。你不仅仅能将你的starter上传上去,还能将普通的maven项目上传上去,更重要的是你的代码永远在自己手中而不必暴露出去。

私有maven仓库的实现:NEXUS。你可能已经见过它了,阿里的镜像仓库也是用它做的。在maven的setting.xml中的阿里镜像中就有 "nexus" 一词

✨ 介绍

  • 本教程基于Docker部署Nexus
  • 本教程使用脚本安装,可实现一键安装,整个安装过程只需要一条命令即可
  • 默认安装的Nexus版本为 3.39.0

🛠️ 安装过程

安装过程中需要使用root用户进行安装,若系统当前用户为非root用户(必须是配置了sudo免密),则先执行下面命令切换为root用户

sudo su - root

执行下面命令,创建文件上传目录

mkdir /soft

将Docker安装包对应的文件夹 [ ],上传至/soft 路径下,若一开始登录的时非root用户,可以先上传到 /tmp 路径下,然后移动到 /soft

  • sonatype-nexus3-3.39.0.tar:Gitlab离线镜像包
  • install_nexus.sh:一键安装脚本
[root@huge ~]# cd /soft/Nexus/
[root@huge Nexus]# ll
总用量 709976
-rw-r--r--. 1 root root      6173 1230 20:56 install_nexus.sh
-rw-r--r--. 1 root root 727006208 1230 20:56 sonatype-nexus3-3.39.0.tar

直接执行脚本即可安装

[root@huge Nexus]# bash install_nexus.sh 

导入离线镜像 [ sonatype-nexus3-3.39.0.tar ] 
                                                                                 
运行容器                                                                                          

恭喜!Nexus部署成功。请登录以下地址访问:                                                                                  
====================================
    登录地址:192.168.80.12:8081
    用户名:admin
    密码:d06e545b-41a3-471f-ab05-87cbdd3b0568
==================================== 

📄 Maven仓库配置

📄 登陆

使用 账号 admin 和临时密码登陆 Nexus

第一次登陆,需要修改默认密码(页面上面的版本过低不安全提示可忽略)

image-20251230210026964

image-20251230210108331

image-20251230210131620

image-20251230210209680

image-20251230210426488

📄 仓库介绍

nexus仓库分三种类型

  • proxy 代理仓库,比如代理到maven中央仓库。
  • hosted 宿主仓库,即自己的私人仓库。
  • group 仓库组,由多个仓库组成,当要下载依赖时会遍历每个仓库去找。

其中,hosted 宿主仓库又分为:releases 和shapshots,分别表示依赖的版本的发行版、快照版。快照版依赖不能上传到发行版仓库,反之亦然。nexus做了限制。

仓库格式有两种

  • maven2
  • nuget

我们搞java的只需要关心maven2的就好了。

其实能猜到我们使用最多的就是仓库组,可以把代理仓库、releases 和shapshots一起放进去,这样就完美实现:能从私服下载依赖,找不到还能去代理仓库那找。我们只需要在本地maven中配置下载镜像从私服上的仓库组下载就行了。

📄 仓库组

其默认的仓库组也是这样干的,把代理仓库、releases 和shapshots这三个仓库都放进去了。

点击就能进去看仓库详情了:

📄 代理仓库

默认创建好的代理仓库是代理到maven中心仓库的:

点击进入可以看到:

📄 修改默认仓库

默认的 maven-release 仓库是不允许重复上传的,我们需要改为允许重复上传

📄 添加仓库

📄 添加阿里代理仓库

众所周知,国内在maven中心仓库下载依赖很慢,所以我们可以重新创建一个代理仓库,然后代理到阿里的镜像仓库:

仓库类型很多,但我们只需要关心maven格式的即可:

仓库名字、代理地址必填,其余可以默认值(存储类型根据需要,默认只保存release依赖,maximum那些是设置缓存时间的)

仓库名:ali-proxy

阿里镜像仓库地址:https://maven.aliyun.com/nexus/content/groups/public

📄 添加私人仓库

image-20260107091747241

📄 关联 maven-public

上一步我们新建的 阿里代码仓库和私人仓库,需要关联到 maven-public 仓库组下

📄 新建用户

📄 上传依赖

首先找到本地maven的setting.xml,打开后找到servers标签。加入以下内容。

账号密码使用我们前面创建的账号,不建议使用admin账号。

  <servers>
    <!-- 这是配置访问私有仓库的用户名密码 -->
    <server>
      <!-- id标签可以随便填,只需要在servers中唯一即可,后面很多地方会使用该id -->
      <id>chengxiaohu</id>
      <username>chengxiaohu</username>
      <password>123456</password>
    </server>
      
  </servers>

maven配置好了,接下来去maven项目中设置,在你想上传的模块的pom文件中写入:

<distributionManagement>
		
        <repository>
            <id>chengxiaohu</id>
            <url>http://192.168.80.6:8081/repository/maven-releases/</url>
        </repository>

        <snapshotRepository>
            <id>chengxiaohu</id>
            <url>http://192.168.80.6:8081/repository/maven-snapshots/</url>
        </snapshotRepository>
    
</distributionManagement>
  • repository 标签是代表上传 release 版本
  • snapshot 标签代表上传 snapsho 版本

会根据该模块的版本进行自动选择,如果你的版本号带有SNAPSHOT如:<version>0.0.1-SNAPSHOT</version>,那么会上传到SNAPSHOT仓库,release也是这个道理,如果版本号不存在这两个单词,如1.0.0,那么会选择release仓库上传。

标签中的 id,就是上面我们配置的server id,maven会通过这个id去server中拿用户名密码去访问私服仓库。

标签中的url,就是对应仓库的地址:仓库的url复制即可,注意对应仓库,弄反了是传不上去的

ok,针对上传,我们已经配置好了,那么如何上传呢?

在idea右侧,maven中,选择要上传的模块,点击deploy(发布)即可

会先下载一些东西,然后上传。

发布后,在对应的仓库也能查看,我这里是snapshot仓库:

📄 下载依赖

接下来,配置本地maven。

还是打开setting.xml,找到mirrors标签。

然后添加一个mirror标签:

<mirror>
    <id>chengxiaohu</id>
    <mirrorOf>*</mirrorOf>
    <url>http://192.168.80.6:8081/repository/maven-public/</url>
</mirror>

之前阿里的镜像可以注释了,

添加的镜像:

  • id 之前server的id,访问仓库组也要拿用户名密码
  • mirrorOf *代表所有的依赖都从私服找
  • url 就是仓库组的,还是去仓库组右边copy即可

到这里就能使用私服下载依赖了。

📄 本地Maven上传(按需)

有时候本地依赖非常多,使用deploy不能把所有依赖都上传,可以采用离线上传的方式,将本地 maven仓库里面所有内容都上传

📄 上传到服务器

将本地 maven 压缩之后上传到服务器上面

📄 解压

unzip repo.zip

📄 编写上传脚本

cd repo

cat >> ./mavenimport.sh << "EOF"
#!/bin/bash
 
# copy and run this script to the root of the repository directory containing files
# this script attempts to exclude uploading itself explicitly so the script name is important
# Get command line params
while getopts ":r:u:p:" opt; do
	case $opt in
		r) REPO_URL="$OPTARG"
		;;
		u) USERNAME="$OPTARG"
		;;
		p) PASSWORD="$OPTARG"
		;;
	esac
done
 
find . -type f -not -path './mavenimport\.sh*' -not -path '*/\.*' -not -path '*/\^archetype\-catalog\.xml*' -not -path '*/\^maven\-metadata\-local*\.xml' -not -path '*/\^maven\-metadata\-deployment*\.xml' | sed "s|^\./||" | xargs -I '{}' curl -u "$USERNAME:$PASSWORD" -X PUT -v -T {} ${REPO_URL}/{} ;
EOF

chmod +x mavenimport.sh

📄 执行脚本

这里上传必须使用 admin账号

./mavenimport.sh -u admin -p 123456 -r http://192.168.80.6:8081/repository/cxh-maven

📄 查看上传结果

📄 注意事项

关于nexus代理仓库状态

online-remote available 表示该被代理仓库可用

online-ready to connect并不是代理仓库用不了。而是你根本还没有一个依赖去访问这个被代理仓库,nexus也不知道这个被代理仓库能不能用,所以它只能展示为待连接状态:

该配置:表示代理仓库会根据每次请求被代理仓库的响应来决定是否暂停请求这个被代理仓库,请求无响应就会block。

还有一个Blocked选项,直接阻止,相当于禁用该代理仓库。一般选择auto就行了。

所以 online-ready to connect并不是仓库出现了问题。

新建的代理仓库都是 online-ready to connect状态。

最好在maven配置中还是加上阿里镜像仓库

<mirror>
    <id>nexus-aliyun</id>
    <mirrorOf>central</mirrorOf>
    <name>Nexus aliyun</name>
    <url>https://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

📄 Npm仓库配置

📄 添加仓库

📄 添加淘宝代理仓库

仓库名:npm-proxy

阿里镜像仓库地址:https://registry.npm.taobao.org

📄 添加私人仓库

📄 添加仓库组

📄 上传单个组件

📄 上传项目所有组件

📄 简述

​ 项目上初始化的时候需要把本地所依赖的所有组件都上传到私服npm仓库上面,如果这个时候一个一个上传就非常麻烦,此时我们就需要写一些批量上传的脚本进行批量上传,下面我们就来演示如果批量上传本地所有组件到 npm私服仓库。

📄 批量下载组件

​ 因为本地 node_modules 里面的依赖都是解压之后,上传之后无法直接使用。所以我们需要先从当前的package.json文件中读取配置先下载tgz格式组件包到本地,然后再上传到npm私服仓库。 使用如下nodejs脚本进行批量自动下载

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const fs = require('fs');
const path = require('path');
const request = require('request');
// 指定根据package-lock.json中记录的信息下载依赖
const packageLock = require('./package-lock.json');
// 指定将依赖下载到当前目录下的npm-dependencies-tgz目录
const downUrl = './npm-dependencies-tgz';

if (!fs.existsSync(downUrl)) {
  fs.mkdirSync(downUrl);
}

// 收集依赖的下载路径
const tgz = [];
// 当前下载索引
let currentDownIndex = 0;
// 下载失败时,重试次数
const retryTimes = 3;
// 当前重试计数
let currentTryTime = 0;

// 重试次数内仍旧下载失败的链接
const downloadFailTgz = [];

for (const pkg in packageLock.packages) {
  if (!packageLock.packages[pkg].resolved) continue;
  const tgzUrl = packageLock.packages[pkg].resolved.split('?')[0];
  tgz.push(tgzUrl);
}

// 下载依赖
function doDownload(url) {
  const outUrl = url.split('/').pop();
  let outUrl2 = [outUrl];
  if (outUrl.indexOf('?') !== -1) {
    outUrl2 = outUrl.split('?');
  }
  const outputDir = path.join(downUrl, outUrl2[0]);
  let receivedBytes = 0;
  let totalBytes = 0;
  const req = request({
    method: 'GET',
    uri: url,
    timeout: 60000
  });
  req.on('response', function(data) {
    totalBytes = parseInt(data.headers['content-length']);
  });
  req.on('data', function(chunk) {
    receivedBytes += chunk.length;
    showProgress(receivedBytes, totalBytes, outUrl2[0]);
    // 当前文件下载完成
    if (receivedBytes >= totalBytes) {
      currentDownIndex++;
      currentTryTime = 0;
      if (currentDownIndex < tgz.length) {
        doDownload(tgz[currentDownIndex]);
      } else {
        if (downloadFailTgz.length === 0) {
          console.log('【完成】所有依赖均下载成功!');
        } else {
          console.warn('【完成】初步处理完成,但部分依赖多次重试后仍旧下载失败,请手动下载:', downloadFailTgz);
        }
      }
    }
  });
  req.on('error', e => {
    console.log(`${currentDownIndex + 1}/${tgz.length}个依赖${outUrl2[0]}下载失败:`, JSON.stringify(e));
    if (currentTryTime < retryTimes) {
      currentTryTime++;
      console.log(`【第${currentTryTime}次】尝试重新下载第${currentDownIndex + 1}/${tgz.length}个依赖${outUrl2[0]}`);
      doDownload(tgz[currentDownIndex]);
    } else {
      // 存入下载失败数组中
      downloadFailTgz.push(tgz[currentDownIndex]);
      currentDownIndex++;
      currentTryTime = 0;
      if (currentDownIndex < tgz.length) {
        doDownload(tgz[currentDownIndex]);
      }
    }
  });
  req.pipe(fs.createWriteStream(outputDir));
}

// 依赖下载进度显示
function showProgress(received, total, filePath) {
  const percentage = ((received * 100) / total).toFixed(2);
  process.stdout.write(`${filePath} 下载进度:${percentage}% (${received}/${total} 字节)\r`);
  if (received === total) {
    console.log(`\n第${currentDownIndex + 1}/${tgz.length}个依赖${filePath} 下载完成!`);
  }
}

// 串行下载
doDownload(tgz[currentDownIndex]);

新增 uploadNpm.js 到 package.json的同级目录下,内容为上面的js内容

到控制台执行这个 nodejs 脚本

node uploadNpm.js

📄 批量上传组件

📄 解压
unzip npm-dependencies-tgz.zip
📄 编写上传脚本
cd npm-dependencies-tgz

cat >> ./UploadnpmPackage.sh << "EOF"
#!/bin/bash
 
# get param
while getopts ":r:u:p:" opt; do
    case $opt in
        r) REPO_URL="$OPTARG"
        ;;  
        u) USERNAME="$OPTARG"
        ;;  
        p) PASSWORD="$OPTARG"
        ;;  
    esac
done
 
# find and upload
find . -type f -name '*.tgz'  | sed "s|^\./||" | xargs -I '{}' \
curl -u "$USERNAME:$PASSWORD" -X 'POST' -v \
  ${REPO_URL} \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'npm.asset=@{};type=application/x-compressed' ;
EOF

chmod +x UploadnpmPackage.sh
📄 执行脚本

这里上传必须使用 admin账号,仓库需要是私人仓库 npm-hosted

./UploadnpmPackage.sh -u admin -p 123456 -r http://192.168.80.6:8081/service/rest/v1/components?repository=npm-hosted
📄 查看上传结果

最近更新 3/19/2026, 9:06:05 AM