網站地名資訊系統含 70 萬檔案,直接存檔案系統面臨:
結構化存儲可將大量檔案封裝為單一大檔案,繞過檔案數限制,並整合 GZIP 壓縮輸出。
EnumElements 未釋放 COM 物件導致控制代碼洩漏的 Bug。VirtualPathProvider:架構較複雜、彈性不足、難整合 GZIP。FileSystem、衍生 FileSystemFFS(結構化存儲)、FileSystemOS(實體檔案)。using System;
using System.IO;
using System.Web;
using System.IO.Compression;
namespace Step1.WebFileSystem
{
public class FileSystem
{
protected string path, physicsPath;
protected Handler handle;
protected HttpContext httpContext = null;
protected Stream stream = null;
public FileSystem(string path, Handler handle)
{
httpContext = HttpContext.Current;
this.path = path;
this.physicsPath = httpContext.Server.MapPath(path);
this.handle = handle;
}
public virtual bool Exists() => false;
public virtual DateTime GetLastWriteTime() => DateTime.Now;
public virtual Stream GetWriter()
{
stream = httpContext.Response.OutputStream;
return handle.UseGzip ? (Stream)new GZipStream(stream, CompressionMode.Compress) : stream;
}
public virtual Stream GetReader() => null;
public virtual void TransmitFile() { return; }
public virtual void Close()
{
if (stream != null) { stream.Close(); stream = null; }
}
public virtual void Dispose() { Close(); }
public void TransmitStream(bool unZip)
{
Stream reader = unZip ? new GZipStream(GetReader(), CompressionMode.Decompress) : GetReader();
byte[] buffer = new byte[1024];
int p;
while ((p = reader.Read(buffer, 0, 1024)) > 0)
{
httpContext.Response.OutputStream.Write(buffer, 0, p);
httpContext.Response.Flush();
}
Close();
}
public void ResponseFile()
{
string acceptEncoding = httpContext.Request.Headers["Accept-Encoding"];
if (handle.UseGzip && acceptEncoding != null && acceptEncoding.IndexOf("gzip") >= 0)
{
httpContext.Response.AddHeader("Content-Encoding", "gzip");
TransmitFile();
}
else
{
if (handle.UseGzip) TransmitStream(true);
else TransmitFile();
}
}
protected void CreateFolder(string path, bool createCurrent)
{
string folder = Path.GetDirectoryName(path);
if (!Directory.Exists(folder)) CreateFolder(folder, true);
if (createCurrent) Directory.CreateDirectory(path);
}
public static FileSystem Create(string path)
{
Configuration config = Configuration.Instance();
if (config != null)
{
for (int i = 0; i < config.handlers.Length; i++)
{
if (config.handlers[i].UrlRegex.IsMatch(path))
{
switch (config.handlers[i].HandlerType)
{
case "FFS": return new FileSystemFFS(path, config.handlers[i]);
case "OS": return new FileSystemOS(path, config.handlers[i]);
default: return new FileSystem(path, config.handlers[i]);
}
}
}
}
return new FileSystem(path, new Handler());
}
}
}
using System;
using System.IO;
using System.Web;
using ExpertLib;
using ExpertLib.IO;
using ExpertLib.IO.Storage;
using System.Text.RegularExpressions;
namespace Step1.WebFileSystem
{
public class FileSystemFFS : FileSystem
{
private Storage ffsFile = null;
private string filePath, ffsPath;
private bool writeError = false;
public FileSystemFFS(string path, Handler handle) : base(path, handle)
{
this.ffsPath = httpContext.Server.MapPath(Regex.Replace(path, handle.Pattern, handle.SavePath));
this.filePath = Regex.Replace(path, handle.Pattern, handle.SaveName).Replace('/', '_');
}
private void Open()
{
ffsFile = StorageFile.OpenStorageFile(this.ffsPath);
}
public override bool Exists()
{
if (ffsFile == null)
{
FileInfo fi = new FileInfo(this.ffsPath);
if (!fi.Exists) return false;
Open();
}
return ffsFile.IsElementExist(this.filePath);
}
public override DateTime GetLastWriteTime()
{
if (ffsFile == null)
{
FileInfo fi = new FileInfo(this.ffsPath);
if (!fi.Exists) return DateTime.Now;
Open();
}
var elements = ffsFile.GetChildElementsInfo();
for (int i = elements.Count - 1; i >= 0; i--)
if (elements[i].Name == filePath) return elements[i].LastModifyTime;
return DateTime.Now;
}
public override Stream GetWriter()
{
Close();
if (ffsFile == null)
{
if (!File.Exists(this.ffsPath))
{
CreateFolder(this.ffsPath, false);
ffsFile = StorageFile.CreateStorageFile(this.ffsPath, StorageCreateMode.Create,
StorageReadWriteMode.ReadWrite, StorageShareMode.ShareDenyWrite, StorageTransactedMode.Direct);
}
else Open();
}
stream = (Stream)ffsFile.CreateStream(filePath);
if (stream == null) { writeError = true; return base.GetWriter(); }
return handle.UseGzip ? (Stream)new GZipStream(stream, CompressionMode.Compress) : stream;
}
public override Stream GetReader()
{
Close();
if (ffsFile == null)
{
FileInfo fi = new FileInfo(this.ffsPath);
if (!fi.Exists) return null;
Open();
}
return stream = (Stream)ffsFile.OpenStream(filePath);
}
public override void TransmitFile()
{
if (writeError) base.TransmitFile();
else TransmitStream(false);
}
public override void Dispose()
{
Close();
if (ffsFile != null) { ffsFile.Dispose(); ffsFile = null; }
}
}
}
DateTime lastWriteTime = DateTime.Now;
bool isExists = wfs.Exists() && DateTime.Now.Subtract(lastWriteTime = wfs.GetLastWriteTime()).TotalDays <= fileKeepDays;
lastWriteTime = isExists ? lastWriteTime : DateTime.Now;
Response.Cache.SetExpires(lastWriteTime.AddDays(fileKeepDays));
Response.Cache.SetLastModified(lastWriteTime.AddMinutes(-1));
Response.ContentType = "text/html";
if (!isExists)
{
Stream writer = null;
try
{
writer = wfs.GetWriter();
// 這裡寫入頁面內容,通常用 XSLT 輸出
}
finally
{
if (writer != null) writer.Close();
wfs.Dispose();
}
}
wfs.ResponseFile();
wfs.Dispose();
<Handler pattern="^/place/cn/((?:[^$/]+/){3})([^$]+).aspx"
handlerType="FFS" useGzip="true"
savePath="/place/cn/$1FFS.resx" saveName="$2" />
<Handler pattern="^/place/cn/([^$]+).aspx"
handlerType="OS" useGzip="true"
savePath="/place/cn/$1.aspx" />
前三級行政區(省市縣)用實體檔案快取,後續層級用結構化存儲(按縣分檔)。
EnumElements 取得的 IEnumSTATSTG 需呼叫 Marshal.ReleaseComObject 釋放,否則檔案控制代碼洩漏,下次開啟失敗。
// IsElementExist 修正
public bool IsElementExist(string elementName)
{
// ... 略 ...
IEnumSTATSTG statstg;
ComTypes.STATSTG stat;
uint k;
this.storage.EnumElements(0, IntPtr.Zero, 0, out statstg);
statstg.Reset();
bool found = false;
while (statstg.Next(1, out stat, out k) == HRESULT.S_OK)
{
if (string.Compare(stat.pwcsName, elementName, true) == 0) { found = true; break; }
}
Marshal.ReleaseComObject(statstg); // 新增:釋放 COM 列舉器
return found;
}
// GetChildElementsInfo 修正
public List<StgElementInfo> GetChildElementsInfo()
{
// ... 略 ...
IEnumSTATSTG statstg;
ComTypes.STATSTG stat;
uint k;
List<StgElementInfo> list = new List<StgElementInfo>();
this.storage.EnumElements(0, IntPtr.Zero, 0, out statstg);
statstg.Reset();
while (statstg.Next(1, out stat, out k) == HRESULT.S_OK)
list.Add(new StgElementInfo(stat));
Marshal.ReleaseComObject(statstg); // 新增:釋放 COM 列舉器
return list;
}