
目录位置
将本地的nexus2依赖仓库jar包、aar包上传到nexus3仓库
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NexusUploader {
public static void main(String[] args) {
c1 = 0;
c2 = 0;
uploadDirectory(Paths.get(sourceDir));
}
private static int c1, c2 = 0;
private static final String sourceDir = "C:\Users\admin123\sonatype-work\nexus\storage\releases";
private static final String baseDir = "C:\Users\admin123\sonatype-work\nexus\storage\releases";
private static final String REPO_ID = "maven-releases";
private static final String NEXUS_USERNAME = "admin";
private static final String NEXUS_PASSWORD = "123456"; // 实际使用时建议从配置文件读取
private static final String REST_API_URL = "http://192.168.1.221:8081/service/rest/v1/components?repository=" + REPO_ID;
private static void uploadDirectory(Path directory) {
try {
Files.walk(directory).filter(path -> path.toString().endsWith(".jar") ||
path.toString().endsWith(".pom") ||
path.toString().endsWith(".aar") ||
path.toString().endsWith(".war") ||
path.toString().endsWith(".zip"))
.forEach(NexusUploader::uploadFile);
} catch (IOException e) {
e.printStackTrace();
}
System.out.printf("成功:%s,失败:%s%n", c1, c2);
}
private static void uploadFile(Path filePath) {
try {
// 从文件路径提取 Maven 坐标
MavenCoordinates coords = extractCoordinates(filePath);
Thread.sleep(100);
// 提取文件扩展名
String fileExtension = filePath.toString().substring(filePath.toString().lastIndexOf("."));
// 执行上传
uploadToNexus(filePath, coords, fileExtension);
System.out.printf("上传成功%s: %s:%s:%s (%s)%n", c1, coords.groupId, coords.artifactId, coords.version, filePath.getFileName());
c1++;
// if (c1 >= 5) {
// System.exit(0);
// }
} catch (Exception e) {
System.err.println("上传失败" + filePath + ": " + e.getMessage());
e.printStackTrace();
c2++;
}
}
private static MavenCoordinates extractCoordinates(Path filePath) {
String pathStr = filePath.toString();
String fileName = filePath.getFileName().toString();
// 正则表达式匹配文件名中的 artifactId 和 version
// 支持格式: artifactId-version.extension 或 artifactId-version-classifier.extension
Pattern pattern = Pattern.compile("(.+?)-([0-9]+\\.[0-9]+(\\.[0-9]+)?(\\-[a-zA-Z0-9]+)?)(\\..+)?");
Matcher matcher = pattern.matcher(fileName);
String artifactId;
String version;
if (matcher.matches()) {
artifactId = matcher.group(1);
version = matcher.group(2);
} else {
// 尝试简单匹配(artifactId-version.extension)
int lastDashIndex = fileName.lastIndexOf('-');
int extensionIndex = fileName.lastIndexOf('.');
if (lastDashIndex > 0 && extensionIndex > lastDashIndex) {
artifactId = fileName.substring(0, lastDashIndex);
version = fileName.substring(lastDashIndex + 1, extensionIndex);
} else {
throw new IllegalArgumentException("无法从文件名提取坐标: " + fileName);
}
}
// 提取 groupId
String relativePath = pathStr.substring(baseDir.length());
// 移除文件名部分
String directoryPath = relativePath.substring(0, relativePath.lastIndexOf(File.separator) + 1);
// 移除 version 目录
String groupIdPath = directoryPath.substring(0, directoryPath.length() - version.length() - 1);
// 移除 artifactId 目录
int artifactIdIndex = groupIdPath.lastIndexOf(artifactId + File.separator);
if (artifactIdIndex > 0) {
groupIdPath = groupIdPath.substring(0, artifactIdIndex);
}
// 转换为 groupId(用点分隔)
String groupId = groupIdPath.replace(File.separator, ".").replaceAll("\\.$", "");
if (groupId.startsWith(".")) {
groupId = groupId.substring(1);
}
return new MavenCoordinates(groupId, artifactId, version);
}
private static void uploadToNexus(Path filePath, MavenCoordinates coords, String fileExtension) throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder().build();
// 构建 Multipart 请求体
String boundary = "---------------------------" + System.currentTimeMillis();
byte[] requestBody = buildMultipartBody(filePath, coords, boundary, fileExtension);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(REST_API_URL))
.header("Authorization", "Basic " + encodeCredentials(NEXUS_USERNAME, NEXUS_PASSWORD))
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(requestBody))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 204) {
throw new IOException("上传失败,状态码: " + response.statusCode() + ", 响应: " + response.body());
}
}
private static byte[] buildMultipartBody(Path filePath, MavenCoordinates coords, String boundary, String fileExtension) throws IOException {
List<byte[]> parts = new ArrayList<>();
String lineFeed = "\r\n";
// 添加 metadata 部分
String metadata = "--" + boundary + lineFeed +
"Content-Disposition: form-data; name=\"maven2.groupId\"" + lineFeed +
lineFeed +
coords.groupId + lineFeed +
"--" + boundary + lineFeed +
"Content-Disposition: form-data; name=\"maven2.artifactId\"" + lineFeed +
lineFeed +
coords.artifactId + lineFeed +
"--" + boundary + lineFeed +
"Content-Disposition: form-data; name=\"maven2.version\"" + lineFeed +
lineFeed +
coords.version + lineFeed;
// 确定 packaging 类型
String packaging = fileExtension.replace(".", "");
if ("pom".equalsIgnoreCase(packaging)) {
metadata += lineFeed +
"--" + boundary + lineFeed +
"Content-Disposition: form-data; name=\"maven2.asset1.classifier\"" + lineFeed +
lineFeed +
"pom" + lineFeed;
} else if ("jar".equalsIgnoreCase(packaging) || "war".equalsIgnoreCase(packaging) || "aar".equalsIgnoreCase(packaging)) {
// 主 artifact
} else {
// 其他类型,添加 classifier
metadata += lineFeed +
"--" + boundary + lineFeed +
"Content-Disposition: form-data; name=\"maven2.asset1.classifier\"" + lineFeed +
lineFeed +
packaging + lineFeed;
}
metadata += lineFeed +
"--" + boundary + lineFeed +
"Content-Disposition: form-data; name=\"maven2.asset1.extension\"" + lineFeed +
lineFeed +
fileExtension.substring(1) + lineFeed; // 移除开头的点
parts.add(metadata.getBytes());
// 添加文件内容部分
String filePartHeader = "--" + boundary + lineFeed +
"Content-Disposition: form-data; name=\"maven2.asset1\"; filename=\"" +
coords.artifactId + "-" + coords.version + fileExtension + "\"" + lineFeed +
"Content-Type: application/octet-stream" + lineFeed +
lineFeed;
parts.add(filePartHeader.getBytes());
parts.add(Files.readAllBytes(filePath));
parts.add((lineFeed + "--" + boundary + "--" + lineFeed).getBytes());
// 合并所有部分
int totalLength = parts.stream().mapToInt(p -> p.length).sum();
byte[] result = new byte[totalLength];
int currentPosition = 0;
for (byte[] part : parts) {
System.arraycopy(part, 0, result, currentPosition, part.length);
currentPosition += part.length;
}
return result;
}
private static String encodeCredentials(String username, String password) {
return java.util.Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
}
static class MavenCoordinates {
String groupId;
String artifactId;
String version;
public MavenCoordinates(String groupId, String artifactId, String version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
}
}
}
这是Nexus3.8的Gradle上传到私服仓库的方法
plugins {
id 'com.android.library'
id 'maven-publish'
}
version = '0.0.01'
group = 'com.test.model'
// 定义版本、组、名称等属性
ext {
version = version
group = group
artifactName = 'adapter'
releaseFileName = "release.aar"
remoteUrl = 'http://192.168.1.111/content/repositories/releases/'
remoteName = 'admin'
remotePassword = 'mypwd'
}
// 生成源码jar任务
tasks.register('sourcesJar', Jar) {
from android.sourceSets.main.java.srcDirs
archiveClassifier.set("sources")
}
// 发布配置(Gradle 9.0 兼容写法)
afterEvaluate {
publishing {
repositories {
maven {
name = "maven-releases"
url = uri(project.remoteUrl)
credentials {
username = project.remoteName
password = project.remotePassword
}
allowInsecureProtocol = true
}
}
publications {
release(MavenPublication) {
groupId = project.group
artifactId = project.artifactName
version = project.version
// // 使用组件1,(现代做法)无效
// from components.findByName("release")
// 使用组件2,添加aar
artifact(file("${buildDir}/outputs/aar/${releaseFileName}"))
// 添加源码JAR
artifact(tasks["sourcesJar"])
// 配置POM文件
pom {
name = project.artifactName
packaging = 'aar'
// 自动生成依赖信息(更现代的方式)
withXml {
def dependenciesNode = asNode().appendNode('dependencies')
// 使用变体API获取依赖(Gradle 9.0推荐)
configurations.releaseRuntimeClasspath.resolvedConfiguration.firstLevelModuleDependencies.each { dep ->
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', dep.moduleGroup)
dependencyNode.appendNode('artifactId', dep.moduleName)
dependencyNode.appendNode('version', dep.moduleVersion)
// 设置依赖范围
def scope = 'runtime'
if (configurations.api.getDependencies().any { it.group == dep.moduleGroup && it.name == dep.moduleName }) {
scope = 'compile'
}
dependencyNode.appendNode('scope', scope)
}
}
}
}
}
}
tasks.withType(PublishToMavenRepository).tap {
configureEach {
dependsOn bundleReleaseAar
}
}
}
android {
compileSdk = 36
defaultConfig {
//14(4.0),16(4.1),17(4.2),18(4.3),19(4.4),21(5.0),23(6.0),24(7.0),26(8.0),28(9.0)
//29(10),30(11),31,32(12),33(13),34(14),35(15)
minSdk = 26
//noinspection OldTargetApi
targetSdk = 34
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
// 设置要打包的名称,可以是jar或aar
android.libraryVariants.configureEach { variant ->
variant.outputs.all {
outputFileName = releaseFileName
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
//buildConfigField("String", "version", "\"v${version}\"")
//buildConfigField("String", "group", "\"${group}\"")
//buildConfigField("String", "artifact", "\"${artifactName}\"")
//buildConfigField("String", "text", "\"${group}:${artifactName}:${version}\"")
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
buildFeatures {
viewBinding = true
}
namespace = 'com.test.model'
}
dependencies {
api group: 'androidx.recyclerview', name: 'recyclerview', version: '1.4.0'
}