chore: initial commit

This commit is contained in:
XingCheng3
2026-05-29 09:34:04 +08:00
commit 78ae9dcd2e
683 changed files with 68110 additions and 0 deletions

View File

@@ -0,0 +1,458 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Net.Http.Headers;
using WinSCP;
namespace WebApi
{
[RoutePrefix("api/craftfile")]
public class CraftFileController : ApiController
{
// 本地缓存根目录
private readonly string _cacheRoot = @"D:\\CraftFilesCache";
private readonly string _sftpHost = "10.107.69.5";
private readonly string _sftpUser = "root";
private readonly string _sftpPassword = "XKYXmes@123!";
public class FileRequestDto
{
// 远程SFTP相对路径或绝对路径如 /root/PLM-MES/.../xxx.pdf
public string RelativePath { get; set; } = "";
}
[HttpPost]
[Route("browse")]
public IHttpActionResult Browse([FromBody] FileRequestDto request)
{
try
{
if (request == null || string.IsNullOrWhiteSpace(request.RelativePath))
return Ok(new { success = false, message = "路径不能为空" });
EnsureCacheRoot();
var remotePath = NormalizeRemotePath(request.RelativePath);
var localPath = ToLocalPath(remotePath);
var isFile = Path.HasExtension(remotePath);
if (isFile)
{
// 确保本地存在
if (!System.IO.File.Exists(localPath))
{
var ok = DownloadFileFromSftp(remotePath, localPath, out string err);
if (!ok) return Ok(new { success = false, message = $"下载失败: {err}" });
}
return GetFilePreviewLocal(localPath, remotePath);
}
else
{
// 目录:优先读取本地,没有则读取远程目录结构
if (Directory.Exists(localPath))
{
return GetDirectoryContentsLocal(localPath, remotePath);
}
else
{
return GetDirectoryContentsRemote(remotePath);
}
}
}
catch (Exception ex)
{
return Ok(new { success = false, message = $"服务器错误: {ex.Message}" });
}
}
[HttpPost]
[Route("download")]
public IHttpActionResult Download([FromBody] FileRequestDto request)
{
try
{
if (request == null || string.IsNullOrWhiteSpace(request.RelativePath))
return Ok(new { success = false, message = "文件路径不能为空" });
EnsureCacheRoot();
var remotePath = NormalizeRemotePath(request.RelativePath);
var localPath = ToLocalPath(remotePath);
if (!Path.HasExtension(remotePath))
return Ok(new { success = false, message = "请传入文件路径" });
if (!System.IO.File.Exists(localPath))
{
var ok = DownloadFileFromSftp(remotePath, localPath, out string err);
if (!ok) return Ok(new { success = false, message = $"下载失败: {err}" });
}
var fileInfo = new FileInfo(localPath);
var contentType = GetContentType(fileInfo.Name);
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(new FileStream(localPath, FileMode.Open, FileAccess.Read, FileShare.Read))
};
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = fileInfo.Name
};
return ResponseMessage(response);
}
catch (Exception ex)
{
return Ok(new { success = false, message = $"下载文件失败: {ex.Message}" });
}
}
// 新增浏览器直接预览文件支持PDF等支持Range分块
[HttpGet]
[Route("preview")]
public HttpResponseMessage Preview([FromUri] string path)
{
try
{
if (string.IsNullOrWhiteSpace(path))
{
return Request.CreateResponse(HttpStatusCode.BadRequest, new { success = false, message = "文件路径不能为空" });
}
EnsureCacheRoot();
var remotePath = NormalizeRemotePath(path);
var localPath = ToLocalPath(remotePath);
if (!Path.HasExtension(remotePath))
{
return Request.CreateResponse(HttpStatusCode.BadRequest, new { success = false, message = "请传入文件路径" });
}
if (!System.IO.File.Exists(localPath))
{
var ok = DownloadFileFromSftp(remotePath, localPath, out string err);
if (!ok)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, new { success = false, message = $"下载失败: {err}" });
}
}
var fileInfo = new FileInfo(localPath);
var contentType = GetContentType(fileInfo.Name);
var totalLength = fileInfo.Length;
var response = new HttpResponseMessage();
var range = Request.Headers.Range;
var stream = new FileStream(localPath, FileMode.Open, FileAccess.Read, FileShare.Read);
if (range != null && range.Ranges.Count > 0 && totalLength > 0)
{
// 处理 Range 请求
var from = range.Ranges.First().From ?? 0;
var to = range.Ranges.First().To ?? (totalLength - 1);
if (to >= totalLength) to = totalLength - 1;
var length = to - from + 1;
stream.Seek(from, SeekOrigin.Begin);
response.StatusCode = HttpStatusCode.PartialContent;
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
response.Content.Headers.ContentLength = length;
response.Content.Headers.ContentRange = new ContentRangeHeaderValue(from, to, totalLength);
response.Headers.AcceptRanges.Add("bytes");
}
else
{
// 全量流式
response.StatusCode = HttpStatusCode.OK;
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
response.Content.Headers.ContentLength = totalLength;
response.Headers.AcceptRanges.Add("bytes");
}
// inline 以支持浏览器内嵌预览
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("inline")
{
FileName = fileInfo.Name
};
// CORS 允许跨域及暴露必要头
if (!response.Headers.Contains("Access-Control-Allow-Origin"))
response.Headers.Add("Access-Control-Allow-Origin", "*");
if (!response.Headers.Contains("Access-Control-Expose-Headers"))
response.Headers.Add("Access-Control-Expose-Headers", "Accept-Ranges, Content-Range, Content-Length, Content-Type, Content-Disposition");
return response;
}
catch (Exception ex)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, new { success = false, message = $"预览失败: {ex.Message}" });
}
}
private IHttpActionResult GetFilePreviewLocal(string localPath, string remotePath)
{
try
{
var fileInfo = new FileInfo(localPath);
var extension = fileInfo.Extension.ToLowerInvariant();
var previewable = IsPreviewableFile(extension);
var contentType = GetContentType(fileInfo.Name);
bool tooLarge = fileInfo.Length > 500 * 1024 * 1024; // 500MB限制
string base64String = null;
string dataUrl = null;
bool canPreview = previewable && !tooLarge;
if (canPreview)
{
var fileBytes = System.IO.File.ReadAllBytes(localPath);
base64String = Convert.ToBase64String(fileBytes);
dataUrl = $"data:{contentType};base64,{base64String}";
}
var result = new
{
success = true,
type = "file",
path = remotePath,
name = fileInfo.Name,
size = fileInfo.Length,
extension = fileInfo.Extension,
contentType = contentType,
isPreviewable = canPreview,
base64Data = base64String,
dataUrl = dataUrl,
created = fileInfo.CreationTime,
modified = fileInfo.LastWriteTime,
message = tooLarge ? "文件过大,无法预览,可下载" : null
};
return Ok(result);
}
catch (Exception ex)
{
return Ok(new { success = false, message = $"获取文件预览失败: {ex.Message}" });
}
}
private IHttpActionResult GetDirectoryContentsLocal(string localPath, string remotePath)
{
try
{
var items = new List<object>();
var directories = Directory.GetDirectories(localPath);
foreach (var dir in directories)
{
var dirInfo = new DirectoryInfo(dir);
var subPath = CombineRemotePath(remotePath, dirInfo.Name);
items.Add(new
{
name = dirInfo.Name,
type = "directory",
path = subPath,
created = dirInfo.CreationTime,
modified = dirInfo.LastWriteTime,
isPreviewable = false
});
}
var files = Directory.GetFiles(localPath);
foreach (var file in files)
{
var fileInfo = new FileInfo(file);
var filePath = CombineRemotePath(remotePath, fileInfo.Name);
var extension = fileInfo.Extension.ToLowerInvariant();
items.Add(new
{
name = fileInfo.Name,
type = "file",
path = filePath,
size = fileInfo.Length,
extension = fileInfo.Extension,
created = fileInfo.CreationTime,
modified = fileInfo.LastWriteTime,
isPreviewable = IsPreviewableFile(extension),
contentType = GetContentType(fileInfo.Name)
});
}
var result = new
{
success = true,
type = "directory",
path = remotePath,
items = items.OrderBy(x => ((dynamic)x).type == "directory" ? 0 : 1)
.ThenBy(x => ((dynamic)x).name)
.ToList()
};
return Ok(result);
}
catch (Exception ex)
{
return Ok(new { success = false, message = $"读取目录失败: {ex.Message}" });
}
}
private IHttpActionResult GetDirectoryContentsRemote(string remotePath)
{
try
{
using (var session = OpenSftp())
{
var dir = session.ListDirectory(remotePath);
var items = new List<object>();
foreach (var subdir in dir.Files.Where(e => e.IsDirectory && e.Name != "." && e.Name != ".."))
{
var subPath = CombineRemotePath(remotePath, subdir.Name);
items.Add(new
{
name = subdir.Name,
type = "directory",
path = subPath,
created = subdir.LastWriteTime,
modified = subdir.LastWriteTime,
isPreviewable = false
});
}
foreach (var file in dir.Files.Where(e => !e.IsDirectory))
{
var filePath = CombineRemotePath(remotePath, file.Name);
var extension = Path.GetExtension(file.Name).ToLowerInvariant();
items.Add(new
{
name = file.Name,
type = "file",
path = filePath,
size = file.Length,
extension = Path.GetExtension(file.Name),
created = file.LastWriteTime,
modified = file.LastWriteTime,
isPreviewable = IsPreviewableFile(extension),
contentType = GetContentType(file.Name)
});
}
var result = new
{
success = true,
type = "directory",
path = remotePath,
items = items.OrderBy(x => ((dynamic)x).type == "directory" ? 0 : 1)
.ThenBy(x => ((dynamic)x).name)
.ToList()
};
return Ok(result);
}
}
catch (Exception ex)
{
return Ok(new { success = false, message = $"读取远程目录失败: {ex.Message}" });
}
}
private bool DownloadFileFromSftp(string remotePath, string localPath, out string error)
{
error = null;
try
{
var localDir = Path.GetDirectoryName(localPath);
if (!Directory.Exists(localDir)) Directory.CreateDirectory(localDir);
using (var session = OpenSftp())
{
var result = session.GetFiles(remotePath, localPath, false);
result.Check();
}
return true;
}
catch (Exception ex)
{
error = ex.Message;
return false;
}
}
private Session OpenSftp()
{
var options = new SessionOptions
{
Protocol = Protocol.Sftp,
HostName = _sftpHost,
UserName = _sftpUser,
Password = _sftpPassword,
SshHostKeyPolicy = SshHostKeyPolicy.GiveUpSecurityAndAcceptAny
};
var session = new Session();
session.Open(options);
return session;
}
private void EnsureCacheRoot()
{
if (!Directory.Exists(_cacheRoot)) Directory.CreateDirectory(_cacheRoot);
}
private string NormalizeRemotePath(string p)
{
if (string.IsNullOrWhiteSpace(p)) return "/";
var s = p.Trim();
// 统一使用正斜杠
s = s.Replace('\\', '/');
return s;
}
private string ToLocalPath(string remotePath)
{
// 去掉开头的 '/'
var relative = remotePath.Trim().TrimStart('/');
var localRelative = relative.Replace('/', Path.DirectorySeparatorChar);
return Path.Combine(_cacheRoot, localRelative);
}
private string CombineRemotePath(string basePath, string name)
{
if (string.IsNullOrEmpty(basePath) || basePath == "/") return $"/{name}";
if (basePath.EndsWith("/")) return basePath + name;
return basePath + "/" + name;
}
private bool IsPreviewableFile(string extension)
{
var previewableExtensions = new[]
{
".bmp", ".jpg", ".jpeg", ".png", ".gif", ".tiff", ".tif", ".webp",
".txt", ".json", ".xml", ".csv", ".log", ".md", ".pdf"
};
return previewableExtensions.Contains(extension);
}
private string GetContentType(string fileName)
{
var extension = Path.GetExtension(fileName).ToLowerInvariant();
if (extension == ".bmp") return "image/bmp";
if (extension == ".jpg" || extension == ".jpeg") return "image/jpeg";
if (extension == ".png") return "image/png";
if (extension == ".gif") return "image/gif";
if (extension == ".tiff" || extension == ".tif") return "image/tiff";
if (extension == ".webp") return "image/webp";
if (extension == ".pdf") return "application/pdf";
if (extension == ".txt") return "text/plain";
if (extension == ".json") return "application/json";
if (extension == ".xml") return "application/xml";
if (extension == ".csv") return "text/csv";
if (extension == ".md") return "text/markdown";
if (extension == ".log") return "text/plain";
if (extension == ".zip") return "application/zip";
if (extension == ".rar") return "application/x-rar-compressed";
return "application/octet-stream";
}
}
}