一、需求分析

公司需要将存在于旧系统(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(, 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 > )
{
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 = ; 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的名字都有严格的限制,不能包含#、%等,现在处理异常是记录到日志然后手动去修改名称。

  • 报错的异常

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

四、检查是否导入成功

  • 导入成功界面

  • 检查日志

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

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

迁移TFS,批量将文档导入SharePoint 2013 文档库的更多相关文章

  1. sharepoint 2013 列表和库标签 元数据导航配置(2)

    接前面提到的,如何创建一个术语库.sharepoint 2013 列表和库标签 元数据导航配置(1), 现在要做的,就是如何在自定义或者文档库中使用这个术语库,实现标签功能,通过这些标签,找到对应的文 ...

  2. SharePoint 2013 文档库中PPT转换PDF

    通过使用 PowerPoint Automation Services,可以从 PowerPoint 二进制文件格式 (.ppt) 和 PowerPoint Open XML 文件格式 (.pptx) ...

  3. SharePoint 2013 文档上传的多种形式

    SharePoint 2013 中的某些功能需要使用 ActiveX 控件.这会在不支持 ActiveX 的浏览器上产生限制.目前只有 32 位版本的 Internet Explorer 支持此功能. ...

  4. sharepoint 2013 文档库 资源管理器打开报错 在文件资源管理器中打开此位置时遇到问题,将此网站添加到受信任站点列表,然后重试。

    我们在使用sharepoint 2013的文档库或者资源库的时候,经常会需要用到使用“资源管理器”来管理文档,但是有时候,点击“使用资源管理器打开”,会提示如下错误: 在文件资源管理器中打开此位置时遇 ...

  5. sharepoint 2013 文档库eventhandle权限控制

    记录一下如何在sharepoint server 2013文档库中,使用eventhandle控制文档库document library的条目item权限. ///<summary> // ...

  6. SharePoint 2013 文档库“样式”变了

    有朋友反馈说文档库的样式变了. 经查证,原来有人修改了视图的"样式":库设置—视图—样式,改为默认即可. 另外,如果编辑页面,编辑web部件的属性,在"杂项"勾 ...

  7. 启用SharePoint 2013文档版本控制

    cls $PSSnapin = Add-PsSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Nul ...

  8. 跟我学SharePoint 2013视频培训课程——签出、签入文档(9)

    课程简介 第9天,怎样在SharePoint 2013中签出.签入文档 视频 SharePoint 2013 交流群 41032413

  9. SharePoint 2013 入门教程

    以下文章是自己在学习SharePoint的过程中,不断积累和总结的博文,现在总结一个目录,分享给大家.这个博客也是自己从SharePoint入门,到一个SharePoint开发的成长记录,里面记录的都 ...

随机推荐

  1. 微信小程序 - 上拉加载更多组件

    详情用例看demo,点击下载示例:loadmore

  2. 007-Go package 说明

    1:在项目src下面存在两个目录,每个目录里面各有一个go文件 2:add.go package test02 func Add(a int, b int) int{ return a + b } 注 ...

  3. Qt通过ODBC连接SQL Server2008实践总结

    Qt连接数据库的方式很多,这里说明一种最常用也是最实用的方式,因为这种方式在Windows上开发程序使用起来非常方便,并且也是远程连接数据库所需要用到的方式. 前提工作: 在Win7下安装了SQL S ...

  4. hadoop-处理小文件

    一个Hadoop程序的优化过程 – 根据文件实际大小实现CombineFileInputFormat http://www.rigongyizu.com/hadoop-job-optimize-com ...

  5. 使用QML LocalStorage来存储我们的数据

    在先前的样例中.我们能够"使用SQLite offline storage API来存储应用的设置".我们也在例程"怎样在QML应用中动态改动ListModel中的数据并 ...

  6. Percona-XtraBackup系列二:备份恢复

    #在备份较大数据量的时候推荐xtrabackup,这个工具比mysqldump要快很多. 一.Xtrabackup介绍 1,Xtrabackup是什么 Xtrabackup是一个对InnoDB做数据备 ...

  7. cgroup 分析之CPU和内存部分

    https://ggaaooppeenngg.github.io/zh-CN/2017/05/07/cgroups-%E5%88%86%E6%9E%90%E4%B9%8B%E5%86%85%E5%AD ...

  8. SpringBoot配置RestTemplate的代理和超时时间

    application.properties: #代理设置 proxy.enabled=false proxy.host=192.168.18.233 proxy.port=8888 #REST超时配 ...

  9. 搭建Docker私有仓库--自签名方式

    为了能集中管理我们创建好的镜像,方便部署服务,我们会创建私有的Docker仓库.通读了一遍官方文档,Docker为了确保安全使用TLS,需要CA认证,认证时间长的要钱啊,免费过期时间太短,还是用自签名 ...

  10. MySQL Replication主从复制环境下修改主库IP

    因为调整服务器子网, 数据库服务器的IP地址需要修改. 这个过程中, 需要确保的就是从库在断开主库后, 能从正确的位置再次启动. 具体的操作步骤如下 1) 需要获取的前提信息: 主库的新IP, 这在启 ...