前言
實際開發中,因爲開發人員往往在版本庫中獨享一個分支,或者幾個負責同一模塊的組員共享一個分支,一個模塊開發完成後,代碼以增量的方式部署測試服務器或者生產服務器,而非全量部署,因此,開發人員在完成代碼編寫後,需要整理自己更改過的文件都有哪些,然後在自己開發機器上找到文件對應的編譯後的 .class 文件,最後纔是部署到目標服務器,這一過程手動整理的話往往容易出錯,落下一個文件都是致命的,於是乎我自己寫了一個工具類,所有工作一鍵解決,再也不擔心出錯了,現在特分享出來,希望對大家有幫助。
功能
* 1.查詢指定 svn 賬戶指定時間段的所有提交記錄;
* 2.根據提交記錄整理出指定用戶開發機上所有變更文件的絕對路徑;
* 3.清洗重複的數據、文件夾、刪除文件、複製改名文件等數據,得到有效路徑;
* 4.將有效路徑數據寫入文檔,提供開發人員覈對,包括兩個文檔,一個是可以直接正常部署的路徑文檔,一個是刪除或者修改 了某文件名提醒文檔,提醒是否對應刪除或修改目標服務器對應文件;
* 5.調用cmd窗口執行windows命令,生成開發機上所有類型的變更文件壓縮jar文件,所有文件歸入各自絕對項目路徑;
* 6.調用cmd窗口執行windows命令,解壓jar文件,獲得可參照包名直接部署到目標服務器的項目文件;
PS:可以配多個svn賬號,解決多員工協同操作同一個文件問題,只要保證所有參與人和svn同步,無需考慮操作先後
代碼
導包lib:
工具類代碼如下:
package com.svnkit;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNLogEntryPath;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNWCUtil;
/**
* SvnLogConversionUtil svn組件工具
* 1.查詢指定 svn 賬戶指定時間段的所有提交記錄;
* 2.根據提交記錄整理出指定用戶開發機上所有變更文件的絕對路徑;
* 3.清洗重複的數據、文件夾、刪除文件、複製改名文件等數據,得到有效路徑;
* 4.將有效路徑數據寫入文檔,提供開發人員覈對;
* 5.生成開發機上所有類型的變更文件壓縮jar文件,所有文件歸入各自絕對項目路徑;
* 6.解壓jar文件,獲得可參照包名直接部署到目標服務器的項目文件;
*
* PS:可以配多個svn賬號,解決多員工協同操作同一個文件問題,只要保證所有參與人和svn同步,無需考慮操作先後
*
* @author weny.yang create date: 2020-05-20 09:00:00
*/
public class SvnLogConversionUtil {
private static Log log = LogFactory.getLog(SvnLogConversionUtil.class);
private String userName;
private String password;
private String svnUrl;
private String staDate;
private String endDate;
// File export path
private String writeUrl;
// native project path
private String nativeProjectUrl;
// content directory name: WebContent or WebRoot
private String contentDirectory;
private List<String> otherUserList = new ArrayList<String>();
private Collection<SVNLogEntry> logEntries;
private Map<String, Set<String>> cateMap = new HashMap<String, Set<String>>();
private List<String> cateList = new ArrayList<String>();
private Map<String, Set<String>> delOrCopyMap = new HashMap<String, Set<String>>();
private List<String> delOrCopyList = new ArrayList<String>();
private String tempDir = System.getProperty("java.io.tmpdir");
private DefaultSVNOptions options = SVNWCUtil.createDefaultOptions(true);
private SVNRepository repos;
private ISVNAuthenticationManager authManager;
/**
* 構造方法
*/
public SvnLogConversionUtil(String userName, String password, String svnUrl, String staDate, String endDate,
String writeUrl, String nativeProjectUrl, String contentDirectory, List<String> otherUserList) {
try {
this.userName = userName;
this.password = password;
this.svnUrl = svnUrl;
this.staDate = staDate;
this.endDate = endDate;
this.writeUrl = writeUrl;
this.nativeProjectUrl = nativeProjectUrl;
this.contentDirectory = contentDirectory;
this.otherUserList = otherUserList;
init();
} catch (SVNException e) {
e.printStackTrace();
}
}
/**
* 初始化
*/
private void init() throws SVNException{
log.info("init start...");
authManager = SVNWCUtil.createDefaultAuthenticationManager(new File(tempDir+"/auth"), userName, password.toCharArray());
options.setDiffCommand("-x -w");
repos = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(svnUrl));
repos.setAuthenticationManager(authManager);
log.info("init completed");
}
/**
* 獲取指定版本號區間所有提交記錄
*/
public void pathConversion() throws SVNException, IOException, ParseException {
//先清空相關文件夾
delAllFile(new File(writeUrl+userName));
//初始化工具類
init();
//獲取提交記錄
queryLogByRevision();
//清洗數據
washData();
//寫數據
writeWellFile();
//執行cmd命令
exeCommand();
}
/**
* 獲取指定版本號區間所有提交記錄
*/
@SuppressWarnings("unchecked")
private void queryLogByRevision() throws SVNException, IOException, ParseException {
long startRevision = repos.getDatedRevision(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(staDate));
long endRevision = repos.getDatedRevision(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(endDate));
logEntries = repos.log(new String[] {""}, null, startRevision, endRevision, true, true);
}
/**
* 數據清洗
*/
private String strReplace(String path) {
path = path.replace("/", java.io.File.separator);
if (path.contains("src")) {
path = contentDirectory + java.io.File.separator + "WEB-INF" + java.io.File.separator
+ "classes" + path.substring(path.indexOf("src") + 3, path.length());
}
if (path.contains(contentDirectory)) {
path = nativeProjectUrl + java.io.File.separator
+ path.substring(path.indexOf(contentDirectory), path.length());
}
if (path.contains(".java")) {
path = path.replace(".java", ".class");
}
return path;
}
/**
* 數據清洗
*/
private void washData() throws IOException {
for (Iterator<SVNLogEntry> entries = logEntries.iterator(); entries.hasNext();) {
SVNLogEntry logEntry = (SVNLogEntry) entries.next();
if (logEntry.getAuthor().equals(userName) || otherUserList.contains(logEntry.getAuthor())) {
if (logEntry.getChangedPaths().size() > 0) {
Set<String> changedPathsSet = logEntry.getChangedPaths().keySet();
for (Iterator<String> changedPaths = changedPathsSet.iterator(); changedPaths.hasNext();) {
SVNLogEntryPath entryPath = (SVNLogEntryPath) logEntry.getChangedPaths().get(changedPaths.next());
String path = entryPath.getPath();
path = this.strReplace(path);
//新增、修改類變更文件
if (new File(path).exists() && path.contains(".")) {
String key = path.substring(path.lastIndexOf(".") + 1);
if (cateMap.containsKey(key)) {
Set<String> cateSet = cateMap.get(key);
cateSet.add(path);
} else {
Set<String> cateSet = new HashSet<String>();
cateSet.add(path);
cateMap.put(key, cateSet);
cateList.add(key);
}
}
//刪除類變更文件
if (!new File(path).exists() && path.contains(".")) {
String key = path.substring(path.lastIndexOf(".") + 1);
if (delOrCopyMap.containsKey(key)) {
Set<String> cateSet = delOrCopyMap.get(key);
cateSet.add(path);
} else {
Set<String> cateSet = new HashSet<String>();
cateSet.add(path);
delOrCopyMap.put(key, cateSet);
delOrCopyList.add(key);
}
}
//複製類變更文件
String copyPath = entryPath.getCopyPath();
if(copyPath !=null) {
copyPath = this.strReplace(copyPath);
String key = copyPath.substring(copyPath.lastIndexOf(".") + 1);
if (delOrCopyMap.containsKey(key)) {
Set<String> cateSet = delOrCopyMap.get(key);
cateSet.add(copyPath);
} else {
Set<String> cateSet = new HashSet<String>();
cateSet.add(copyPath);
delOrCopyMap.put(key, cateSet);
delOrCopyList.add(key);
}
}
}
}
}
}
}
/**
* 寫入文檔
*/
private void writeWellFile() throws IOException {
File file = new File(writeUrl + java.io.File.separator + userName);
File writeName = new File(writeUrl + java.io.File.separator + userName + java.io.File.separator + userName + ".txt");
if (!file.exists()) {
file.mkdirs();
}
writeName.createNewFile();
FileWriter writer = new FileWriter(writeName);
@SuppressWarnings("resource")
BufferedWriter out = new BufferedWriter(writer);
for (String cate : cateList) {
Iterator<Entry<String, Set<String>>> iteratorMap = cateMap.entrySet().iterator();
while (iteratorMap.hasNext()) {
Entry<String, Set<String>> entry = iteratorMap.next();
if (entry.getKey().equals(cate)) {
Set<String> cateSet = entry.getValue();
Iterator<String> iteratorSet = cateSet.iterator();
while (iteratorSet.hasNext()) {
String str = iteratorSet.next();
//System.lineSeparator()本質是"\r\n", jdk1.8方法,如果報錯,自行替換即可
str += System.lineSeparator();
out.write(str);
}
}
}
out.write("\n");
}
out.flush();
if(delOrCopyList.size()>0) {
writeWarnFile();
}
log.info("write catalog file completed, please check here >>> " + writeUrl + userName + java.io.File.separator + userName);
}
/**
* 寫入警告文檔
*/
private void writeWarnFile() throws IOException {
File file = new File(writeUrl + java.io.File.separator + userName);
File writeName = new File(writeUrl + java.io.File.separator + userName + java.io.File.separator + "警告.txt");
if (!file.exists()) {
file.mkdirs();
}
writeName.createNewFile();
FileWriter writer = new FileWriter(writeName);
@SuppressWarnings("resource")
BufferedWriter out = new BufferedWriter(writer);
out.write("注意 >>> 以下內容爲複製改名項或本地已刪除項,請確認目標環境是否需要變更!\r\n");
out.write("\r\n");
for (String cate : delOrCopyList) {
Iterator<Entry<String, Set<String>>> iteratorMap = delOrCopyMap.entrySet().iterator();
while (iteratorMap.hasNext()) {
Entry<String, Set<String>> entry = iteratorMap.next();
if (entry.getKey().equals(cate)) {
Set<String> cateSet = entry.getValue();
Iterator<String> iteratorSet = cateSet.iterator();
while (iteratorSet.hasNext()) {
String str = iteratorSet.next();
str += System.lineSeparator();
out.write(str);
}
}
}
out.write("\n");
}
out.flush();
}
/**
* 執行cmd命令
*/
private void exeCommand() throws IOException {
Runtime.getRuntime().exec("cmd /c " + writeUrl.substring(0, 2) + " && cd " + writeUrl + userName + " && start jar -cvf " + userName + ".jar @" + userName + ".txt");
boolean flg1 = true;
while (flg1) {
if (new File(writeUrl + userName + java.io.File.separator + userName + ".jar").exists()) {
flg1 = false;
log.info("create jar file completed, please check here >>> " + writeUrl + userName + java.io.File.separator + userName + ".jar");
Runtime.getRuntime().exec("cmd /c " + writeUrl.substring(0, 2) + " && cd " + writeUrl + userName + " && jar xvf " + userName + ".jar");
boolean flg2 = true;
while (flg2) {
if (new File(writeUrl + userName + java.io.File.separator + "META-INF").exists()) {
flg2 = false;
log.info("decompression file completed, please check here >>> " + writeUrl + userName);
}
}
}
}
}
/**
* 刪除文件或文件夾
*/
private void delAllFile(File directory){
if (!directory.isDirectory()){
directory.delete();
} else{
File [] files = directory.listFiles();
// 空文件夾
if (files.length == 0){
directory.delete();
//System.out.println("刪除" + directory.getAbsolutePath());
return;
}
// 刪除子文件夾和子文件
for (File file : files){
if (file.isDirectory()){
delAllFile(file);
} else {
file.delete();
//System.out.println("刪除" + file.getAbsolutePath());
}
}
// 刪除文件夾本身
directory.delete();
//System.out.println("刪除" + directory.getAbsolutePath());
}
}
}
測試類代碼如下:
package com.svnkit;
import java.util.ArrayList;
import java.util.List;
public class TestSvnKit{
public static void main(String[] arg) {
try {
// svn賬戶、密碼、地址
String userName = "ITL520";
String password = "xxx";
String svnUrl = "svn://xxxxxx/Image&PCM/ImageCode/imaging";
// 查詢提交記錄的時間區間
String staDate = "2020-05-20 00:00:00";
String endDate = "2020-05-21 23:59:59";
// 文件輸出路徑
String writeUrl = "C:\\Users\\86158\\Desktop";
// 本地開發機上項目路徑,截止到項目名
String nativeProjectUrl = "D:\\Software\\Java\\WorkSpace\\imaging";
// 項目的content directory名: WebContent 或者 WebRoot
String contentDirectory = "WebRoot";
// 如果多人協同操作同一模塊,將參與用戶的svn賬戶配到此處
List<String> otherUserList = new ArrayList<String>();
// otherUserList.add("ITL521");
SvnLogConversionUtil SvnUtil = new SvnLogConversionUtil(userName, password, svnUrl, staDate, endDate, writeUrl, nativeProjectUrl, contentDirectory, otherUserList);
SvnUtil.pathConversion();
} catch (Exception e) {
e.printStackTrace();
}
}
}
運行演示
第一步:運行 TestSvnKit 中的 main 方法,桌面生成文件夾如下圖:
第二步:打開文件夾,內部結構如下:
txt文檔裏面寫入的是變更了那些類文件的本地絕對路徑信息,會根據文件類型自動分類,打開如下:
如果有刪除或者給某個文件改名了,會額外生成一個 警告.txt 文檔,記錄變更信息,以便開發人員覈對。
Software 文件夾裏面便是所有編譯後的變更文件,按照項目分級目錄存放,可以直接上傳目標服務器進行部署,如下: