Files
16-Xikai-MesProjectNew/Interface_WebAPI/SlMesDbIterface/WebAPI/Controller/CraftFileController.cs
2026-05-29 09:34:04 +08:00

459 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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";
}
}
}