文档章节

迁移TFS,批量将文档导入SharePoint 2013 文档库

 木宛城主
发布于 2015/03/02 19:42
字数 1821
阅读 8
收藏 0

一、需求分析

公司需要将存在于旧系统(TFS)所有的文档迁移至新系统(SharePoint 2013)。现已经将50G以上的文档拷贝到SharePoint 2013 Server上。这些文档是一些不规则的资料,除了常见的Office文件、PDF,还包括图片、RAR等,甚至还包括一些快捷方式(.link)这类的"脏数据"。除此之外,这些存在于TFS中的文档,名称也是"不规则",即包含了SharePoint 2013文档命名不支持的字符如"&", "\"", "?", "<", ">", "#", "{", "}", "%", "~", "/", "\\"。所以,这对导入又增加了复杂度。

了解了文档内容和命名规则后,接下来就是分析怎样导入至SharePoint文档库中:

  • 首先,每一个二级文件夹的命名是有规则的,正好是项目编号(Project Number),如GCP-xxxx-xxx-xxx。再根据此编号创建一个子站点。
  • 值得一提的是,根据编号创建的子站点并不是随意创建的,而是需要考虑究竟要在哪一个Site Collection下创建子站点,并且还要给予独立权限的分配,即为子站点打断权限继承,为其增加两个组(Owners和Members),并向组里添加对应的人员。
  • 对应的创建规则存在于如下List中

其中Project Number即项目编号,与TFS中文件夹的名称一致。Department 即需要将此子站点创建于哪个Site Collection中,包含两个值SMO和CO。PM列是一个Person Or Group类型的字段,需要将此字段的值加入到Owner组,Domain Group列也是一个Person Or Group类型的字段,需要将此字段的值加入到Member组中。

  • 接下来,是最重要的一步,找到最佳实践去创建各个Level的文件夹并传入文档。

二、分析和构建导入程序

首先,文件夹的目录结构如下图所示:

文档目录结构图

  • 根据上图文档目录结构图,分割字符串(E:\TFS\GCP0401-S\4.Project Management\3 Document Management\TMF),获取文件夹的名称,即Project Number(GCP0401-S)。然后根据此Project Number找到对应的Department、PM、Domain Group,最后创建子站点。逻辑代码如下图所示:

 

private SPWeb CreateSubSite(string webUrl, string webTitle, string description)
        {
            var result = (from e in projectInfos where e.ProjectNumber == webUrl && tempArray.Contains(webUrl) select e).FirstOrDefault();
            if (result==null)
            {
                logger.Debug("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&以下Sub Site没有创建********");
                logger.Debug("Web Url="+webUrl);
                logger.Debug("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*********");
                return null;
            }
            logger.Debug("开始创建在Site Collection:"+result.Department.ToUpper());
            using (SPSite site = result.Department.ToUpper()=="SMO"
                ? new SPSite("http://sp/sites/smo")
                :new SPSite("http://sp/sites/cro"))
                {//{*/using(SPSite site=new SPSite("http://reus")){
                using (SPWeb web = site.OpenWeb())
                {
                    try
                    {
                        SPWeb subWeb = null;
                        if (site.AllWebs[webUrl].Exists)
                        {
                            subWeb = site.AllWebs[webUrl];
                        }
                        else
                        {

                            logger.Debug("不存在"+webUrl+",则创建新的WebSite");
                            //不存在则创建新的WebSite
                            subWeb = site.AllWebs.Add(webUrl, webTitle, description, 1033, "STS#0", true, false);
                            
                           
                            string groupTitleForOwners = subWeb.Title + " Owners";
                            subWeb.AssociatedOwnerGroup = EnsureGroup(subWeb,groupTitleForOwners , "Full Control");

                            string groupTitleForMembers = subWeb.Title + " Members";
                            subWeb.AssociatedMemberGroup = EnsureGroup(subWeb,groupTitleForMembers , "Edit");

                            subWeb.Groups[groupTitleForOwners].AddUser(subWeb.Site.Owner);
                            
                            
                            if (result.PM!=null)
                            {
                                 subWeb.Groups[groupTitleForOwners].AddUser(result.PM);
                            }
                            if (result.DomainGroup!=null)
                            {
                                subWeb.Groups[groupTitleForMembers].AddUser(result.DomainGroup);
                            }


                            subWeb.Update();
                            logger.Debug(webUrl+"创建成功");
                            richTextBox2.Text += webUrl + "创建OK" + "\r\n";
                        }
                       
                        return subWeb;
                    }
                    catch (Exception es)
                    {
                       logger.Debug(es.ToString());
                        return null;
                    }
                    
                }
            }

        }
  • 从上文档目录结构图可以分析出,二级目录是项目编号,即对应要创建的子站点。在此目录下有"无限级"的子文件夹。那应该怎样在子站点的文档库中创建如此多的文件夹呢,这需要好好考虑一下。对,用递归,得到每一个分支最底层的文件夹路径即可。具体实现如下所示:
private void GetDeepestFoleder(string sDir)
        {
            string dir = string.Empty;
            try
            {
                foreach (string path in Directory.GetDirectories(sDir))
                {
                    dir = path;

                    if (!arrayList.Contains(dir))
                    {
                        arrayList.Add(dir);
                    }

                    arrayList.Remove(dir.Substring(0, dir.LastIndexOf("\\")));

                    GetDeepestFoleder(dir);
                }
            }
            catch (Exception excpt)
            {
                logger.Debug(excpt.ToString());
            }
        }
  • 得到所有最内层文件夹的URL之后,接着就是在SharePoint 文档库中创建一级一级的文件夹了。
private void CreateForderForDocumentLibrary(string folderUrl, SPWeb currentWeb)
        {
            try
            {
                SPFolder newFolder = null;
                string spFolderUrl = currentWeb.ServerRelativeUrl  + "/Shared Documents";
                logger.Debug("SPFolder Url="+spFolderUrl);
                //分割字符串,得到父子Folder的Url,在文档库中创建文件夹
                foreach (string strUrl in folderUrl.Split('\\'))
                {

                    //todo:有空格会报错吗?
                    string tempStrUrl = strUrl.Trim();
                    //SharePoint 文档库中文件名有严格的格式要求
                    var r = new[] { "&", "\"", "?", "<", ">", "#", "{", "}", "%", "~", "/", "\\"}.Any(tempStrUrl.Contains);
                    if (r)
                    {
                        tempStrUrl = tempStrUrl.Clean();
                    }

                    if (tempStrUrl.StartsWith(".") || tempStrUrl.EndsWith("."))
                    {
                        tempStrUrl = tempStrUrl.Replace(".", "-");
                    }          
                    

                    spFolderUrl += "/" + tempStrUrl;
                    logger.Debug("SPFolder Url="+spFolderUrl);
                    SPList list = currentWeb.Lists.TryGetList("Documents");
                    SPFolderCollection folderCollection = list.RootFolder.SubFolders;
                    newFolder = folderCollection.Add(spFolderUrl);
                    logger.Debug(spFolderUrl + "创建成功");
                }
            }
            catch (Exception ex)
            {
                logger.Debug(ex.ToString());
                throw;
            }
        }
  • 以上代码逻辑中包含了字符串的处理,因为SharePoint 2013的文档、文件夹命名有严格的要求,不能包含非法字符。并且也不能以字符 "."开始或者结束。所以添加了字符串处理操作功能。
public static class MyStringExtention
    {
        public static string Clean(this string s)
        {
            StringBuilder sb = new StringBuilder(s);
            //"&", "\"", "?", "<", ">", "#", "{", "}", "%", "~", "/", "\\" uu.uu可以,但uu.就不行
            sb.Replace("&", "-");
            sb.Replace("\"", "-");
            sb.Replace("?", "-");
            sb.Replace("<", "-");
            sb.Replace(">", "-");
            sb.Replace("#", "-");
            sb.Replace("{", "-");
            sb.Replace("}", "-");
            sb.Replace("%", "-");
            sb.Replace("~", "-");
            sb.Replace("/", "-");
            sb.Replace("\\", "-");
           // sb.Replace(".", "-");

            return sb.ToString();
        }
    }
  • 在成功创建了子站点并在文档库中创建了所有文件夹后,接下来就是将文档上传至指定的文件夹中了。所以接下来, 需要获取指定目录下所有的文件,我使用了一个队列来保存文件路径, 而不是使用递归或者使用.NET 4.0提供的基于文件迭代的功能(Directory.EnumerateFiles)来获取所有文件,原因有2点:
    • Directory.EnumerateFiles内置的递归方法容易抛出异常,比如没有权限访问等。
    • Queue<String> 避免了多次递归时调用堆栈从而会创建大数组。
private IEnumerable<string> GetFiles(string path)
        {
            Queue<string> queue = new Queue<string>();
            queue.Enqueue(path);
            while (queue.Count > 0)
            {
                path = queue.Dequeue();
                try
                {
                    foreach (string subDir in Directory.GetDirectories(path))
                    {
                        queue.Enqueue(subDir);
                    }
                }
                catch (Exception ex)
                {
                    logger.Debug("===========发生错误啦*========================");
                    logger.Debug(ex.ToString());
                    logger.Debug("===========发生错误了========================");
                }
                string[] files = null;
                try
                {
                    files = Directory.GetFiles(path);
                }
                catch (Exception ex)
                {
                    logger.Debug("===========发生错误啦&========================");
                    logger.Debug(ex.ToString());
                    logger.Debug("===========发生错误了========================");
                }
                if (files != null)
                {
                    for (int i = 0; i < files.Length; i++)
                    {
                        yield return files[i];
                    }
                }
            }
        }
  • 在获取了所有文件之后,上传至指定文档库即可,别忘记处理非法字符。
private void UploadFileToDocumentLibrary(string folderUrl, string filePath, SPWeb currentWeb)
        {

            try
            {
                //todo:不同文件名字相同会覆盖吗todo
                SPFolder newFolder = null;
                string spFolderUrl = currentWeb.ServerRelativeUrl+ "/Shared Documents";
                //极端情况 GCP0117 TFS List.xls
                //分割字符串,得到父子Folder的Url,在文档库中创建文件夹
                foreach (string strUrl in folderUrl.Split('\\'))
                {

                    //todo:有空格会报错吗?
                    string tempStrUrl = strUrl.Trim();
                    //SharePoint 文档库中文件名有严格的格式要求
                    var result = new[] { "&", "\"", "?", "<", ">", "#", "{", "}", "%", "~", "/", "\\" }.Any(tempStrUrl.Contains);
                    if (result)
                    {
                        tempStrUrl = tempStrUrl.Clean();
                    }

                    if (tempStrUrl.StartsWith(".") || tempStrUrl.EndsWith("."))
                    {
                        tempStrUrl = tempStrUrl.Replace(".", "-");
                    }


                    spFolderUrl += "/" + tempStrUrl;
                }
                newFolder = currentWeb.GetFolder(spFolderUrl);


                string fileName = System.IO.Path.GetFileName(filePath);
                //SharePoint 文档库中文件名有严格的格式要求
                var r=new[] { "&", "\"", "?", "<", ">", "#", "{", "}", "%", "~", "/", "\\"}.Any(fileName.Contains);
                if (r)
                {
                    logger.Debug("***********File Name包含了Invalid Value***********************");
                    logger.Debug("***********File Name="+fileName);
                    logger.Debug("***********File Path="+filePath);

                    fileName = fileName.Clean();
                    logger.Debug("*********替换过后的File Name************************************");
                    logger.Debug(fileName);
                }

                if (fileName.StartsWith(".") || fileName.EndsWith("."))
                {
                    fileName=fileName.Replace(".", "-");
                }

                //判断文件是否已经存在,若存在,则不再重复上传

                string spFileUrl = spFolderUrl + "/" + fileName;
                SPFile newSpFile = currentWeb.GetFile(spFileUrl);
                if (!newSpFile.Exists)
                {
                    using (FileStream fs = File.OpenRead(filePath))
                    {
                        byte[] contentBytes = new byte[fs.Length];
                        fs.Read(contentBytes, 0, (int)fs.Length);
                        if (newFolder != null)
                        {
                            SPFile spFile = newFolder.Files.Add(fileName, contentBytes, true);
                            newFolder.Update();
                        }

                    }
                    logger.Debug(fileName + "上传成功 and FilePath=" + filePath);
                }
                else
                {
                    logger.Debug(spFileUrl+"已存在");
                }


                
            }
            catch (Exception ex)
            {
                logger.Debug(ex.ToString());
                throw;
            }
        }

三、异常处理

主要发生的异常是文件名包含Invalid字符,对SharePoint而言,文档库Folder和File的名字都有严格的限制,不能包含#、%等,现在处理异常是记录到日志然后手动去修改名称

  • 报错的异常

 

  • 将异常记录至日志里,方便修改。

四、检查是否导入成功

  • 导入成功界面

  • 检查日志

  • 登陆系统,检查是否全部导入,并且检查权限设置是否正确。

  • 查看文件夹和文档是否成功创建和上传

 

 

 

 

 

© 著作权归作者所有

共有 人打赏支持
粉丝 2
博文 222
码字总数 199010
作品 0
黄浦
Office 365 SharePoint 迁移浅谈 (三)使用SP Migration API迁移

下边开始进入正题,首先来谈谈如何使用SharePoint Migration API进行文档库的迁移。 操作说明之前先来说下SharePoint Migration API他的大概功能,本身SharePoint Migration API其实已经有不...

mxy00000
06/26
0
0
Office 365 SharePoint 迁移浅谈 (四)使用Migration Tools

下边我们再来谈下微软最近推出的另外一款可以迁移SharePoint 的产品,SharePoint Migration Tools,它的功能会比SharePoint Migration API要强很多,本身也是图形化界面的操作方式,比较简单...

mxy00000
03/02
0
0
再谈 SharePoint 大局观

前言 我对SharePoint这个产品很有感情,因为曾经有相当长一段时间,在很多个夜深人静、月黑风高的晚上,我都是在和它形影不离,在一个一个项目实践中相爱相杀。今天这个产品早已经不是我最初...

CSharpKit
2017/12/23
0
0
如何迁移SharePoint 2010至新的站点

转载自:http://blog.sina.com.cn/s/blog_a0b42c280101a4wt.html SharePoint使用非常方便,但是有一个问题获取会困扰大家,就是SharePoint的备份和迁移。下面我们来看一下如何把SharePoint迁...

yuxye
2015/07/17
0
0
Project Server 2016 新特性

从 IT Pro的角度, Project 2016 主要有如下一些新的特性: 资源预订 资源预订是 Project Server 2016 中的一项新功能,有助于项目经理和资源经理就与项目相关联的特定资源的具体工作量和工作...

冯立超
2017/03/31
0
0

没有更多内容

加载失败,请刷新页面

加载更多

ajax 提交返回map集合 获取不到值

后台java代码 @RequestMapping("/cameraList") @ResponseBody public Map<String, Object> cameraListForPage(@RequestParam(defaultValue = "1", value = "page") Integer page......

S三少S
5分钟前
0
0
TypeScrip最污的技术课-技术胖TypeScript图文视频教程

近日Node.js之父瑞安达尔(Ryan Dahl)发布新的开源项目 deno,从官方介绍来看,可以认为它是下一代 Node,使用 rust 语言代替 C++ 重新编写跨平台底层内核驱动,上层仍然使用 V8 引擎,最终...

JamesView
7分钟前
5
0
Es学习笔记

1.过滤排重聚合查询 筛选出某一个聚合值的个数统计。相当于mysql的distinct. 关键字:cardinality "aggs": { "2":{ "cardinality": { "field": "field" } ...

Gmupload
10分钟前
0
0
h5语义化标签

语义化HTML:用最恰当的HTML元素标签做恰当的事情。 优点: 提升可访问性; SEO; 结构清晰,利于维护; 通用容器:div——块级通用容器;span——短语内容无语义容器。 <title></title>:简...

莫西摩西
16分钟前
0
0
修改11g rac中 asm diskstring的发现路径

问题 : 如果我 们asm_disking以前是/dev/oracleasm/disks/* ,并且现在已经有磁盘组再用这个磁盘串了,那么,我们无法直接修改这个发现串为 ORCL:*,修改会报错,提示存在的磁盘无法使用新的...

tututu_jiang
19分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部