之前研究过CS,但是CS的问题是只允许附带一个文件,所以并不能够解决附带多个文件的处理问题,尤其是用户可能会传上来大量的图片而之后又废弃不用的情况。
就是说程序在设计的时候就应该考虑好用户上传了一些图片或图片,然后废弃不用的情况,你不能对任何程序都做个图片库或者文件库来管理用户传上来的文件或者图片。
其实这个架构很久之前就总结好了,不过之前一直通过数据库来解决这个问题,最近坐到架构师的位置上,就不得不认真细致的来把这个问题做好。
举个例子:
比如
下面是我做的一个页面的图片上传页面,
对于用户上传上来的临时文件,系统会保存到一个临时文件目录中,同时附加一个xml文件记录该文件的信息,如文件大小、文件类型、上传时间等。而在附加文件的页面则只需保存一个ViewState中保存的string类型的列表通过实际为Guid类型的标识符来跟踪这些文件。
如下面的代码所示:
1 protected const string hiddenFileNamePattern = "{0}.syattachment";
2 protected const string hiddenFileNameMetaDataPattern = "{0}.meta";
3
4
5 /// <summary>
6 /// 插入临时文件
7 /// </summary>
8 /// <param name="key"></param>
9 /// <param name="stream"></param>
10 public static void Insert(string key, FileInfo tempFile)
11 {
12 if (string.IsNullOrEmpty(key))
13 throw new ArgumentNullException("key", "temporary file key can't be null.");
14
15 if (tempFile == null)
16 throw new ArgumentNullException("tempFile", "temporary file can't be null.");
17
18
19 // save the temporary file to temporary file storage location
20 string contentFilename = GetFileName(key, false);
21 FileHelper.SaveToDisk(tempFile.ContentStream, contentFilename);
22
23
24 //生成附属的xml文件,记录该文件的信息
25 string metaFilename = GetFileName(key, true);
26 XmlWriter tempWriter = XmlWriter.Create(metaFilename);
27
28
29
30 tempWriter.WriteStartDocument();
31 tempWriter.WriteStartElement("file");
32 tempWriter.WriteAttributeString("key", key);
33 tempWriter.WriteAttributeString("isTemporary", "true");
34
35 tempWriter.WriteStartElement("fileName");
36 tempWriter.WriteString(tempFile.FileName);
37 tempWriter.WriteEndElement();
38
39 tempWriter.WriteStartElement("extension");
40 tempWriter.WriteString(tempFile.FileExtension);
41 tempWriter.WriteEndElement();
42
43 tempWriter.WriteStartElement("contentType");
44 tempWriter.WriteString(tempFile.ContentType);
45 tempWriter.WriteEndElement();
46
47 tempWriter.WriteStartElement("contentSize");
48 tempWriter.WriteString(tempFile.Length.ToString());
49 tempWriter.WriteEndElement();
50
51 tempWriter.WriteStartElement("createdDate");
52 tempWriter.WriteString(DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss"));
53 tempWriter.WriteEndElement();
54
55 tempWriter.WriteEndDocument();
56
57 tempWriter.Close();
58 }
上面的代码第一部分先保存了文件的数据流,而第二部分则保存了文件的附属信息。2 protected const string hiddenFileNameMetaDataPattern = "{0}.meta";
3
4
5 /// <summary>
6 /// 插入临时文件
7 /// </summary>
8 /// <param name="key"></param>
9 /// <param name="stream"></param>
10 public static void Insert(string key, FileInfo tempFile)
11 {
12 if (string.IsNullOrEmpty(key))
13 throw new ArgumentNullException("key", "temporary file key can't be null.");
14
15 if (tempFile == null)
16 throw new ArgumentNullException("tempFile", "temporary file can't be null.");
17
18
19 // save the temporary file to temporary file storage location
20 string contentFilename = GetFileName(key, false);
21 FileHelper.SaveToDisk(tempFile.ContentStream, contentFilename);
22
23
24 //生成附属的xml文件,记录该文件的信息
25 string metaFilename = GetFileName(key, true);
26 XmlWriter tempWriter = XmlWriter.Create(metaFilename);
27
28
29
30 tempWriter.WriteStartDocument();
31 tempWriter.WriteStartElement("file");
32 tempWriter.WriteAttributeString("key", key);
33 tempWriter.WriteAttributeString("isTemporary", "true");
34
35 tempWriter.WriteStartElement("fileName");
36 tempWriter.WriteString(tempFile.FileName);
37 tempWriter.WriteEndElement();
38
39 tempWriter.WriteStartElement("extension");
40 tempWriter.WriteString(tempFile.FileExtension);
41 tempWriter.WriteEndElement();
42
43 tempWriter.WriteStartElement("contentType");
44 tempWriter.WriteString(tempFile.ContentType);
45 tempWriter.WriteEndElement();
46
47 tempWriter.WriteStartElement("contentSize");
48 tempWriter.WriteString(tempFile.Length.ToString());
49 tempWriter.WriteEndElement();
50
51 tempWriter.WriteStartElement("createdDate");
52 tempWriter.WriteString(DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss"));
53 tempWriter.WriteEndElement();
54
55 tempWriter.WriteEndDocument();
56
57 tempWriter.Close();
58 }
临时文件加到服务器上了,被废弃了的情况下,系统会自动在指定时间后删除不用的临时文件,代码如下:
1 public void DeleteExpiredTemporaryFiles()
2 {
3 HttpContext context = HttpContext.Current;
4 FileUtilsConfiguration fileConfig = FileUtilsConfiguration.Instance();
5 string tempLocation = context.Server.MapPath(fileConfig.TemporaryLocation);
6 int minutes = fileConfig.ExpireTimes;//超时时间,以分钟计
7
8 DateTime now = DateTime.Now;
9
10 //获取该目录下的所有文件
11 string[] fileNames = Directory.GetFiles(tempLocation, "*.meta");
12 foreach (string fileName in fileNames)
13 {
14 XmlDocument doc = new XmlDocument();
15 doc.Load(fileName);
16
17 DateTime createdDate = Convert.ToDateTime(doc.SelectSingleNode("/file/createdDate").InnerText);
18
19 if (createdDate.AddMinutes(20) < now)
20 {
21 string key = System.IO.Path.GetFileNameWithoutExtension(fileName);
22 //删除文件
23 File.Delete(tempLocation + key + ".syattachment");
24
25 //删除文件附属信息
26 File.Delete(tempLocation + key + ".meta");
27 }
28 }
29
30
31
32 }
2 {
3 HttpContext context = HttpContext.Current;
4 FileUtilsConfiguration fileConfig = FileUtilsConfiguration.Instance();
5 string tempLocation = context.Server.MapPath(fileConfig.TemporaryLocation);
6 int minutes = fileConfig.ExpireTimes;//超时时间,以分钟计
7
8 DateTime now = DateTime.Now;
9
10 //获取该目录下的所有文件
11 string[] fileNames = Directory.GetFiles(tempLocation, "*.meta");
12 foreach (string fileName in fileNames)
13 {
14 XmlDocument doc = new XmlDocument();
15 doc.Load(fileName);
16
17 DateTime createdDate = Convert.ToDateTime(doc.SelectSingleNode("/file/createdDate").InnerText);
18
19 if (createdDate.AddMinutes(20) < now)
20 {
21 string key = System.IO.Path.GetFileNameWithoutExtension(fileName);
22 //删除文件
23 File.Delete(tempLocation + key + ".syattachment");
24
25 //删除文件附属信息
26 File.Delete(tempLocation + key + ".meta");
27 }
28 }
29
30
31
32 }
上面的代码则说明了如何删除临时文件,他运行在TemporaryFileJob上,类似于CS的Job定时执行线程的逻辑,请不明白这块的人去看看关于Teligent.Tasks的分析文章。首先是获取特定目录下的全部后缀为meta的文件,然后以xml的方式打开查找到createdDate信息,一旦发现过期20分钟的文件,则自动删除。
下面的代码则解释了如何使用上面的临时文件管理架构。
1 protected List<string> PhotoKeys
2 {
3 get
4 {
5 if (ViewState["PhotoKeys"] == null)
6 ViewState["PhotoKeys"] = new List<string>();
7
8 return (List<string>)ViewState["PhotoKeys"];
9 }
10 }
11
12 #endregion
13
14 protected void Page_Load(object sender, EventArgs e)
15 {
16
17 }
18
19 protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
20 {
21
22 }
23
24 protected void UploadPhotoButton_Click(object sender, EventArgs e)
25 {
26 if (eventPhotoUpload.PostedFile.ContentLength <= 0)
27 {
28 Label_AddPhotoMessage.Text = "请先选择要上传的文件";
29 return;
30 }
31
32 if (eventPhotoUpload.PostedFile.ContentLength > 1024 * 1024 * 4)
33 {
34 Label_AddPhotoMessage.Text = "抱歉,照片文件限制在4M以下";
35 return;
36 }
37
38 //保存文件
39 string key = Guid.NewGuid().ToString();
40 FileInfo file = new FileInfo(eventPhotoUpload.PostedFile);
41
42 TemporaryManager.Insert(key, file);
43
44 this.PhotoKeys.Add("t_" + key);
45
46 BindEventPhotos();
47 }
48
49 private void BindEventPhotos()
50 {
51 List<FileMetaData> files = new List<FileMetaData>();
52 foreach (string key in PhotoKeys)
53 {
54 bool isTemp = key.StartsWith("t_");
55 string filekey = key.Substring(2);
56 if (isTemp)
57 {
58 files.Add(TemporaryManager.GetTemporaryFileMetaData(filekey));
59 }
60 }
61
62 this.PhotoList.DataSource = files;
63 this.PhotoList.DataBind();
64 }
上面的代码是指把图片标识保存到以ViewState为存储后台的string类型的列表中,采用string类型的原因是我想在临时文件的标识前加"t_"以提高文件的获取效率。2 {
3 get
4 {
5 if (ViewState["PhotoKeys"] == null)
6 ViewState["PhotoKeys"] = new List<string>();
7
8 return (List<string>)ViewState["PhotoKeys"];
9 }
10 }
11
12 #endregion
13
14 protected void Page_Load(object sender, EventArgs e)
15 {
16
17 }
18
19 protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
20 {
21
22 }
23
24 protected void UploadPhotoButton_Click(object sender, EventArgs e)
25 {
26 if (eventPhotoUpload.PostedFile.ContentLength <= 0)
27 {
28 Label_AddPhotoMessage.Text = "请先选择要上传的文件";
29 return;
30 }
31
32 if (eventPhotoUpload.PostedFile.ContentLength > 1024 * 1024 * 4)
33 {
34 Label_AddPhotoMessage.Text = "抱歉,照片文件限制在4M以下";
35 return;
36 }
37
38 //保存文件
39 string key = Guid.NewGuid().ToString();
40 FileInfo file = new FileInfo(eventPhotoUpload.PostedFile);
41
42 TemporaryManager.Insert(key, file);
43
44 this.PhotoKeys.Add("t_" + key);
45
46 BindEventPhotos();
47 }
48
49 private void BindEventPhotos()
50 {
51 List<FileMetaData> files = new List<FileMetaData>();
52 foreach (string key in PhotoKeys)
53 {
54 bool isTemp = key.StartsWith("t_");
55 string filekey = key.Substring(2);
56 if (isTemp)
57 {
58 files.Add(TemporaryManager.GetTemporaryFileMetaData(filekey));
59 }
60 }
61
62 this.PhotoList.DataSource = files;
63 this.PhotoList.DataBind();
64 }
而BindEventPhotos则会根据标识是否有前缀"t_"来判断是从正式图片目录(或者数据库)还是临时文件目录来提取图片信息。
恩,因为博客园不能放附件的问题,所以需要代码的请收藏该页,待我整理好后即会更新此页面。