一、需求分析
公司需要将存在于旧系统(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,最后创建子站点。逻辑代码如下图所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
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 文档库中创建一级一级的文件夹了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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]; } } } }
- 在获取了所有文件之后,上传至指定文档库即可,别忘记处理非法字符。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
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的名字都有严格的限制,不能包含#、%等,现在处理异常是记录到日志然后手动去修改名称。
- 报错的异常
- 将异常记录至日志里,方便修改。
四、检查是否导入成功
- 导入成功界面
- 检查日志
- 登陆系统,检查是否全部导入,并且检查权限设置是否正确。
- 查看文件夹和文档是否成功创建和上传
本文转自木宛城主博客园博客,原文链接:http://www.cnblogs.com/OceanEyes/p/migrate-tfs-to-sharepoint.html,如需转载请自行联系原作者