又遇到了一个功能点,网上的参考内容挺少的,终于把功能实现了,记一下,方便以后遇到坑的时候看看

主要功能点

  • 获取一个SVN目录中的所有子文件夹以及文件
    • 可以利用参数控制是否进行递归获取所有的文件夹中的子类
    • 可以利用参数控制获取的是文件还是文件夹
  • 通过输出流下载SVN中的文件
  • 为SVN中添加目录
  • 将不同级别的目录中的文件目录(内含子类)copy到一个指定的文件夹中

功能实现

获取一个SVN目录中的所有子文件夹以及文件

请不要在意代码中的dto类

 * @Author: winterchen
     * @Description: 获取SVN目录下下一层所有文件信息
     * @Date: 2018/2/6
     * @param dirPath 需要查找的目录名
     * @param fileNameSearch 名称模糊搜索
     * @param typeEnum 文件的类型枚举 -- 这个就是三个参数 ‘dir’,‘file’,‘all’
     */
    public List<PmcFileInfoDto> getNextLvFileList2(String dirPath, String fileNameSearch, SvnGetFileTypeEnum typeEnum){

        if (dirPath.startsWith("/")){
            dirPath = dirPath.replaceFirst("/", "");
        }
        try {
            //获取SVNRepository
            SVNRepository svnRepository = SVNUtil.getSVNRepository();
            //获取路径下的所有文件夹或文件
            List<SVNDirEntry> svnList = SvnOption.getSvnFileBaseInfoDto(svnRepository,dirPath, fileNameSearch, typeEnum,
                    false);
            //这个方法只是做对象转换的,忽略
            return parsePmcFileInfoDtos(svnList);
        } catch (SVNException e) {
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.GET_FILE_LIST_FAIL)
                    .withErrorMessageArguments(e.getMessage());
        }

    }

SVNUtil.java


 public class SVNUtil {
    //svn连接地址
    public static String storeUrl=null;
    private static final String USERNAME = "你的账号";
    private static final String PASSWD = "你的密码";


    static{
        storeUrl =  “https://ip:port/svn/...”
        }
    /**
     * @Author: winterchen
     * @Description: 获取SVNRepository
     * @Date: 2018/2/6
     * @param url
     */
    public static SVNRepository getSVNRepository(String url) throws SVNException{

        SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url));
        ISVNAuthenticationManager authManager =  SVNWCUtil.createDefaultAuthenticationManager( USERNAME , PASSWD );
        repository.setAuthenticationManager( authManager );

        return repository;
    }

    /**
     * @Author: winterchen
     * @Description: 获取SVNRepository
     * @Date: 2018/2/7
     * @param
     */
    public static SVNRepository getSVNRepository() throws SVNException{
        return SVNUtil.getSVNRepository(storeUrl);
    }
}


 /**
     * @Author: winterchen
     * @Description: 获取 SVNUpdateClient
     * @Date: 2018/2/9
     * @param
     */
    public static SVNUpdateClient getSVNUpdateClient(){

        //声明SVN客户端管理类
        SVNClientManager ourClientManager = null;

        //初始化库。 必须先执行此操作。具体操作封装在setupLibrary方法中。
        setupLibrary();



        ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
        //实例化客户端管理类
        ourClientManager = SVNClientManager.newInstance(
                (DefaultSVNOptions) options, USERNAME, PASSWD);

        //通过客户端管理类获得updateClient类的实例。
        SVNUpdateClient updateClient = ourClientManager.getUpdateClient();

        updateClient.setIgnoreExternals(false);

        return updateClient;

    }

    /*
     * 初始化库
     */
    private static void setupLibrary() {
        /*
         * For using over http:// and https://
         */
        DAVRepositoryFactory.setup();
        /*
         * For using over svn:// and svn+xxx://
         */
        SVNRepositoryFactoryImpl.setup();

        /*
         * For using over file:///
         */
        FSRepositoryFactory.setup();
    }

    /**
     * @Author: winterchen
     * @Description: 获取 SVNClientManager
     * @Date: 2018/2/9
     * @param
     */
    public static SVNClientManager getSVNClientManager(){
        DAVRepositoryFactory.setup();
        ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
        //实例化客户端管理类
        return SVNClientManager.newInstance((DefaultSVNOptions) options, USERNAME, PASSWD);
    }


SvnOption.java

/**
     * @Author: winterchen
     * @Description: 获取版本库中某一目录下的所有条目。可以根据参数选择是否进行递归
     * @Date: 2018/2/6
     * @param repository
     * @param path  需要查找的svn路径(相对路径)前面不需要/
     * @param typeEnum  文件类型
     * @param fileNameSearch    文件名过滤条件
     * @param isGetNext 是否递归 true  递归
     */
    public static List<SVNDirEntry> getSvnFileBaseInfoDto(SVNRepository repository, String path,
    String fileNameSearch, SvnGetFileTypeEnum typeEnum,
    boolean isGetNext)throws SVNException{
        List<SVNDirEntry> result = new LinkedList<>();
        listEntries(repository,path,result,fileNameSearch,typeEnum,isGetNext);
        return result;
    }
    
     /**
     * @Author: winterchen
     * @Description: 获取版本库中某一目录下的所有条目。可以根据参数选择是否进行递归
     * @Date: 2018/2/6
     * @param repository
     * @param path  需要查找的svn路径(相对路径)前面不需要/
     * @param result    最后封装的容器
     * @param typeEnum  文件类型
     * @param fileNameSearch    文件名过滤条件
     * @param isGetNext 是否递归 true  递归
     */
    public static void listEntries(SVNRepository repository, String path, List<SVNDirEntry> result,
                                   String fileNameSearch, SvnGetFileTypeEnum typeEnum,
                                   boolean isGetNext) throws SVNException {
        //获取版本库的path目录下的所有条目。参数-1表示是最新版本。
        Collection entries = repository.getDir(path, -1, null,
                (Collection) null);
        Iterator iterator = entries.iterator();

        while (iterator.hasNext()) {
            SVNDirEntry entry = (SVNDirEntry) iterator.next();
            boolean flag = true;
            //进行参数的匹配
            if (typeEnum != null) {
                if (!typeEnum.getValue().equals(SvnGetFileTypeEnum.DEFAULT.getValue())) {
                    if (!entry.getKind().toString().equals(typeEnum.getValue())) {
                        flag = false;
                    }
                }
            }
            if (!StringUtils.isEmpty(fileNameSearch)) {
                if (!entry.getName().contains(fileNameSearch)) {
                    flag = false;
                }
            }
            if (flag) { //如果两个条件都不为空,并且都通过了
                result.add(entry);
            }
            //判断是否需要进行递归
            if (isGetNext) {
                /*
                 * 检查此条目是否为目录,如果为目录递归执行
                 */
                if (entry.getKind() == SVNNodeKind.DIR) {
                    listEntries(repository, (path.equals("")) ? entry.getName()
                            : path + "/" + entry.getName(), result, fileNameSearch, typeEnum, isGetNext);
                }
            }
        }
    }

通过输出流下载SVN中的文件

controller

方法是通过将输出流通过HttpServletResponse传给前端进行下载

@ApiOperation("SVN文件的下载")
    @RequestMapping(value = "/svn/download", method = RequestMethod.GET)
    public APIResponse downloadFileBy(
            @ApiParam(name = "path", value = "需要下载文件的相对路径", required = true)
            @RequestParam(name = "path", required = true)
            String path,
            HttpServletResponse response){
        SVNProperties svnProperties = new SVNProperties();
        BufferedOutputStream bos = null;
        //文件名称
        try {
            if (path.startsWith("/")){
                path = path.replaceFirst("/", "");
            }
            String filename = path.substring(path.lastIndexOf("/") + 1);
            String url = path.substring(0,path.lastIndexOf("/"));
            filename = URLEncoder.encode(filename,"UTF-8");
            response.reset();

            response.setContentType("application/force-download");// 设置强制下载不打开
            response.setCharacterEncoding("UTF-8");
            response.addHeader("Content-Disposition",
                    "attachment;fileName=" + filename);// 设置文件名
            bos = new BufferedOutputStream(response.getOutputStream());
            SVNRepository svnRepository = SVNUtil.getSVNRepository(SVNUtil.storeUrl + "/" + url);
            svnHandleService.getSvnFileOutputstream(svnRepository,bos,svnProperties,path);
            response.flushBuffer();
            bos.close();
        } catch (SVNException e) {
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.DOWNLOAD_FILE_FAIL)
                    .withErrorMessageArguments(e.getMessage());
        } catch (IOException e) {
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.DOWNLOAD_FILE_FAIL)
                    .withErrorMessageArguments(e.getMessage());
        }
        return APIResponse.success();
    }
 /**
     * @Author: winterchen
     * @Description: 获取SVN文件的输出流和文件的属性
     * @Date: 2018/2/7
     * @param repository
     * @param outputStream 输出流
     * @param fileProperties 文件属性
     * @param path 文件的相对路径 前面不带/
     */
    public void getSvnFileOutputstream(SVNRepository repository, OutputStream outputStream,
                                       SVNProperties fileProperties, String path){

        if (repository == null || outputStream == null || fileProperties == null || StringUtils.isEmpty(path))
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.DOWNLOAD_FILE_FAIL)
            .withErrorMessageArguments("参数不能为空");
        try {
            SvnOption.getSvnFileOutputStream(repository,outputStream,fileProperties,path);
        } catch (SVNException e) {
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.DOWNLOAD_FILE_FAIL)
                    .withErrorMessageArguments(e.getMessage());
        }
    }

/**
     * @Author: winterchen
     * @Description: 获取SVN文件的输出流和文件的属性
     * @Date: 2018/2/7
     * @param repository
     * @param outputStream 输出流
     * @param fileProperties 文件属性
     * @param path 文件的相对路径 前面不带/
     */
    public static void getSvnFileOutputStream(SVNRepository repository, OutputStream outputStream,
                                              SVNProperties fileProperties, String path) throws SVNException {

        //进行path的处理
        String url = path.substring(0,path.lastIndexOf("/"));
        String filePath = path.substring(path.lastIndexOf("/") + 1);

        try {
            //获得版本库中文件的类型状态(是否存在、是目录还是文件),参数-1表示是版本库中的最新版本。
            SVNNodeKind nodeKind = repository.checkPath(filePath, -1);

            if (nodeKind == SVNNodeKind.NONE) {
                throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.DOWNLOAD_FILE_FAIL)
                        .withErrorMessageArguments("要查看的文件在 '" + url + "'中不存在");
            } else if (nodeKind == SVNNodeKind.DIR) {
                throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.DOWNLOAD_FILE_FAIL)
                        .withErrorMessageArguments("要查看对应版本的条目在 '" + url
                                + "'中是一个目录.");
            }
            //获取要查看文件的内容和属性,结果保存在baos和fileProperties变量中。
            repository.getFile(filePath, -1, fileProperties, outputStream);

        } catch (SVNException svne) {
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.DOWNLOAD_FILE_FAIL)
                    .withErrorMessageArguments(svne.getMessage());
        }


    }

为SVN中添加目录

将不同级别的目录中的文件目录(内含子类)copy到一个指定的文件夹中,请看注释中的实现思路就知道了


/**
     * @Author: winterchen
     * @Description: 创建项目Svn目录(并加入模板文件)
     * @Date: 2018/2/9
     * @param path
     * @param projId
     */
    public void addNewProjSvnDir(String path, Integer projId) throws SVNException {

        DataDictDomain projDict = dataDictDao.getDataDictByProjId(projId);
        /** 实现思路:
         *  根据项目的类型找到相应的svn目录,docheckout到本地
         *  然后doimport到刚刚创建的文件夹中
         *  finally 删除本地文件夹
         **/


        //将模板文件夹checkOut到本地
        File wcDir = new File(SvnOption.getSysLocalDir(projId.toString()));
        try {
            //将svn的模板目录checkout到本地
            SvnOption.checkOut(wcDir,SVNUtil.storeUrl + "/PMCLIB/" + projDict.getEntryName() + "项目");
            //创建svn目录
            SvnOption.addNewSvnDir(path, projDict.getEntryName());
            //将本地的文件目录import到svn
            SvnOption.imoprtToSvnDir(wcDir,SVNUtil.storeUrl + "/" + path);
        } catch (Exception e) {
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.CREATE_SVN_DIR_FAIL)
                    .withErrorMessageArguments(e.getMessage());
        }finally {
            FileUtil.deleteDir(wcDir);
        }
    }

SvnOption.java

/**
     * @Author: winterchen
     * @Description: 往SVN中添加文件夹
     * @Date: 2018/2/9
     * @param path
     * @param projTypeName
     */
    public static void addNewSvnDir(String path, String projTypeName) throws SVNException {

        String filename = path.substring(path.lastIndexOf("/") + 1);
        String url = SVNUtil.storeUrl + "/" + path.substring(0,path.lastIndexOf("/"));
        //模板文件所在目录
        String copyPath = "svn/" + "PMCLIB/" + projTypeName + "项目";
        SVNRepository repository = null;
        try{
            repository = SVNUtil.getSVNRepository(url);
        }catch (SVNException e){
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.CREATE_SVN_DIR_FAIL)
                    .withErrorMessageArguments("相对路径错误,请检查后重试");
        }

        SVNNodeKind nodeKind = repository.checkPath(filename, -1);
        //获得版本库中文件的状态(是否存在),参数-1表示是版本库中的最新版本。
        ISVNEditor editor = repository.getCommitEditor("logMessage", null,true,null);


        if (nodeKind == SVNNodeKind.NONE){//如果文件夹不存在,创建一个
            editor.openRoot(-1);
            editor.addDir(filename,null, -1);
            editor.closeDir();
            editor.closeEdit();
        }else {
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.CREATE_SVN_DIR_FAIL)
                    .withErrorMessageArguments("文件夹已经存在");
        }

    }

    /**
     * @Author: winterchen
     * @Description: SVN checkOut到本地
     * @Date: 2018/2/9
     * @param wcDir 需要checkOut到本地的文件目录
     * @param rlUrl
     */
    public static void checkOut(File wcDir, String url) throws BusinessException{

        SVNUpdateClient svnUpdateClient = SVNUtil.getSVNUpdateClient();
        //相关变量赋值
        SVNURL repositoryURL = null;


        //执行check out 操作,返回工作副本的版本号。
        long workingVersion = -1;
        try {
            repositoryURL = SVNURL.parseURIEncoded(url);
            workingVersion = svnUpdateClient
                    .doCheckout(repositoryURL, wcDir, SVNRevision.HEAD, SVNRevision.HEAD, SVNDepth.INFINITY,false);
        } catch (SVNException e) {
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.CREATE_SVN_DIR_FAIL)
                    .withErrorMessageArguments(e.getMessage());
        }catch (Exception e) {
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.CREATE_SVN_DIR_FAIL)
                    .withErrorMessageArguments(e.getMessage());
        }
    }

    /**
     * @Author: winterchen
     * @Description: import本地文件目录(包括其下的所有子目录和文件)到svn
     * @Date: 2018/2/9
     * @param localFile
     * @param url
     */
    public static void imoprtToSvnDir(File localFile, String url) throws BusinessException{

        SVNClientManager svnClientManager = SVNUtil.getSVNClientManager();
        //执行导入操作
        SVNCommitInfo commitInfo = null;
        SVNURL repositoryURL = null;
        try {
            repositoryURL = SVNURL.parseURIEncoded(url);
            commitInfo = svnClientManager.getCommitClient().doImport(localFile, repositoryURL,
                    "import operation!",null, false,false,SVNDepth.INFINITY);
        } catch (SVNException e) {
            throw BusinessException.withErrorCode(ErrorsWepm.JiraAndSvnIntf.CREATE_SVN_DIR_FAIL)
                    .withErrorMessageArguments(e.getMessage());
        }
    }

    /**
     * @Author: winterchen
     * @Description: 获取系统临时目录地址
     * @Date: 2018/2/9
     * @param dirName
     */
    public static String getSysLocalDir(String dirName){

        // 获取系统临时文件目录(兼容Windows和Linux)+PMC+时间戳+用户账号作为临时检出目录
        String localCheckDir = System.getProperty(tmpdir_property) + "PMC"
                + new SimpleDateFormat(time_sdf).format(new Date()) + "_" +  dirName;
        localCheckDir = FilePathUtil.getHttpURLPath(localCheckDir); // 转换地址格式:主要目录分隔符都转为正斜杠/

        return localCheckDir;
    }

参看文档

api文档:https://svnkit.com/javadoc/index.html

最后

工作中遇到这些需求,在这些需求中不断找寻答案,然后成长。