修改文件上传 添加文件转存
This commit is contained in:
parent
aa0e7c27e5
commit
25d481d7d6
@ -17,6 +17,19 @@ namespace LMS.Repository.DTO
|
|||||||
public Dictionary<string, string> Metadata { get; set; } = new();
|
public Dictionary<string, string> Metadata { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL上传请求
|
||||||
|
/// </summary>
|
||||||
|
public class UrlUploadRequest
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[Url]
|
||||||
|
public required string Url { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public required string FileName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class UploadResult
|
public class UploadResult
|
||||||
{
|
{
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
|
|||||||
@ -9,4 +9,6 @@ public class PromptNameDto
|
|||||||
public string PromptTypeId { get; set; }
|
public string PromptTypeId { get; set; }
|
||||||
|
|
||||||
public string? Remark { get; set; }
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
70
LMS.Tools/FileTool/FileService.cs
Normal file
70
LMS.Tools/FileTool/FileService.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using LMS.DAO.OptionDAO;
|
||||||
|
using LMS.Tools.ImageTool;
|
||||||
|
using static LMS.Repository.DTO.FileUploadDto;
|
||||||
|
|
||||||
|
namespace LMS.Tools.FileTool
|
||||||
|
{
|
||||||
|
public static class FileService
|
||||||
|
{
|
||||||
|
public static byte[]? ConvertBase64ToBytes(string base64String)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(base64String))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 检查是否以 "data:" 开头并包含 base64 编码,如果是则提取实际的 base64 部分
|
||||||
|
if (base64String.StartsWith("data:"))
|
||||||
|
{
|
||||||
|
// 提取 base64 编码部分
|
||||||
|
var commaIndex = base64String.IndexOf(',');
|
||||||
|
if (commaIndex >= 0)
|
||||||
|
{
|
||||||
|
base64String = base64String.Substring(commaIndex + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 尝试将 base64 字符串转换为字节数组
|
||||||
|
return Convert.FromBase64String(base64String);
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
// 如果格式不正确,返回 null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsValidImageFile(byte[] fileBytes)
|
||||||
|
{
|
||||||
|
if (fileBytes == null || fileBytes.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImageTypeDetector.IsValidImage(fileBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<UploadResult> CheckFileSize(byte[] fileBytes, double MaxFileSize)
|
||||||
|
{
|
||||||
|
if (fileBytes == null || fileBytes.Length == 0)
|
||||||
|
{
|
||||||
|
return new UploadResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "文件不能为空"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileBytes.Length > MaxFileSize * 1024 * 1024)
|
||||||
|
{
|
||||||
|
return new UploadResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"文件大小({fileBytes.Length} bytes)超过限制({MaxFileSize * 1024 * 1024} bytes)"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UploadResult { Success = true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
LMS.Tools/FileTool/IQiniuService.cs
Normal file
60
LMS.Tools/FileTool/IQiniuService.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using LMS.Repository.DB;
|
||||||
|
using LMS.Repository.FileUpload;
|
||||||
|
using Qiniu.Http;
|
||||||
|
using static LMS.Repository.DTO.FileUploadDto;
|
||||||
|
|
||||||
|
namespace LMS.Tools.FileTool
|
||||||
|
{
|
||||||
|
public interface IQiniuService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 检查文件的字节大小是否符合要求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileBytes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<UploadResult> CheckFileBytesSize(byte[] fileBytes);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成七牛云上传的路径 key
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
string GenerateFileKey(long userId, string fileName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算文件的 SHA1 哈希值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
string ComputeSHA1Hash(byte[] data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取七牛云的配置 用于上传图片
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<QiniuSettings> InitQiniuSetting();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成七牛的上传凭证
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="qiniuSettings"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
string GeneratePolicy(QiniuSettings qiniuSettings);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 byte 数组上传到七牛云
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileBytes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<FileUploads> UploadFileToQiNiu(byte[] fileBytes, long userId, string fileName, string fileKey);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建文件的访问 URL
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="domain"></param>
|
||||||
|
/// <param name="fileKey"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
string BuildFileUrl(string domain, string fileKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
135
LMS.Tools/FileTool/QiniuService.cs
Normal file
135
LMS.Tools/FileTool/QiniuService.cs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
using LMS.Common.Extensions;
|
||||||
|
using LMS.DAO.OptionDAO;
|
||||||
|
using LMS.Repository.DB;
|
||||||
|
using LMS.Repository.FileUpload;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Qiniu.Http;
|
||||||
|
using Qiniu.IO;
|
||||||
|
using Qiniu.IO.Model;
|
||||||
|
using Qiniu.Util;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using static LMS.Repository.DTO.FileUploadDto;
|
||||||
|
|
||||||
|
namespace LMS.Tools.FileTool
|
||||||
|
{
|
||||||
|
public class QiniuService(OptionGlobalDAO optionGlobalDAO, ILogger<QiniuService> logger) : IQiniuService
|
||||||
|
{
|
||||||
|
private readonly OptionGlobalDAO _optionGlobalDAO = optionGlobalDAO;
|
||||||
|
private readonly UploadManager _uploadManager = new UploadManager();
|
||||||
|
private readonly ILogger<QiniuService> _logger = logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查文件的字节大小是否符合要求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileBytes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<UploadResult> CheckFileBytesSize(byte[] fileBytes)
|
||||||
|
{
|
||||||
|
if (fileBytes == null || fileBytes.Length == 0)
|
||||||
|
{
|
||||||
|
return new UploadResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "文件字节数据不能为空"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
double MaxFileSize = await _optionGlobalDAO.FindAndReturnOption<double>("SYS_MaxUploadFileSize");
|
||||||
|
|
||||||
|
if (fileBytes.Length > MaxFileSize * 1024 * 1024)
|
||||||
|
{
|
||||||
|
return new UploadResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"文件大小不能超过 {MaxFileSize}MB"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UploadResult { Success = true, Message = string.Empty };
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ComputeSHA1Hash(byte[] data)
|
||||||
|
{
|
||||||
|
var hash = SHA1.HashData(data);
|
||||||
|
return Convert.ToHexString(hash).ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateFileKey(long userId, string fileName)
|
||||||
|
{
|
||||||
|
var date = DateTime.Now.ToString("yyyyMMdd");
|
||||||
|
//var extension = Path.GetExtension(fileName);
|
||||||
|
return $"user/{userId}/{date}/{fileName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<QiniuSettings> InitQiniuSetting()
|
||||||
|
{
|
||||||
|
QiniuSettings? qiniuSettings = await _optionGlobalDAO.FindAndReturnOption<QiniuSettings>("SYS_QiniuSetting");
|
||||||
|
|
||||||
|
if (qiniuSettings == null || string.IsNullOrEmpty(qiniuSettings.AccessKey) || string.IsNullOrEmpty(qiniuSettings.SecretKey) || string.IsNullOrEmpty(qiniuSettings.BucketName) || string.IsNullOrEmpty(qiniuSettings.Domain))
|
||||||
|
{
|
||||||
|
throw new Exception("七牛云配置不完整,请检查配置,请联系管理员");
|
||||||
|
}
|
||||||
|
return qiniuSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GeneratePolicy(QiniuSettings qiniuSettings)
|
||||||
|
{
|
||||||
|
Mac mac = new(qiniuSettings.AccessKey, qiniuSettings.SecretKey);
|
||||||
|
var putPolicy = new PutPolicy
|
||||||
|
{
|
||||||
|
Scope = qiniuSettings.BucketName
|
||||||
|
};
|
||||||
|
if (qiniuSettings.DeleteDay != null)
|
||||||
|
{
|
||||||
|
putPolicy.DeleteAfterDays = qiniuSettings.DeleteDay.Value; // 设置过期时间
|
||||||
|
}
|
||||||
|
putPolicy.SetExpires(3600);
|
||||||
|
string token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString());
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildFileUrl(string domain, string fileKey)
|
||||||
|
{
|
||||||
|
return $"{domain}/{fileKey}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FileUploads> UploadFileToQiNiu(byte[] fileBytes, long userId, string fileName, string fileKey)
|
||||||
|
{
|
||||||
|
QiniuSettings qiniuSettings = await InitQiniuSetting();
|
||||||
|
string token = GeneratePolicy(qiniuSettings);
|
||||||
|
string hash = ComputeSHA1Hash(fileBytes);
|
||||||
|
HttpResult uploadResult;
|
||||||
|
|
||||||
|
_logger.LogInformation("开始上传文件, 用户ID: {userId}, 文件名: {fileName}, 文件大小: {fileLength} 字节, 文件Key: {fileKey}", userId, fileName, fileBytes.Length, fileKey);
|
||||||
|
|
||||||
|
using (var stream = new MemoryStream(fileBytes))
|
||||||
|
{
|
||||||
|
uploadResult = await _uploadManager.UploadStreamAsync(stream, fileKey, token);
|
||||||
|
}
|
||||||
|
// 8. 检查上传结果
|
||||||
|
if (uploadResult.Code != 200)
|
||||||
|
{
|
||||||
|
_logger.LogError("文件上传失败, 上传用户ID: {userId}, 错误信息: {error}", userId, uploadResult.Text);
|
||||||
|
throw new Exception(uploadResult.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
var qiniuUrl = BuildFileUrl(qiniuSettings.Domain, fileKey);
|
||||||
|
|
||||||
|
_logger.LogInformation("文件上传成功, 上传用户ID: {userId}, 文件Key: {fileKey},文件链接: {url}", userId, fileKey, qiniuUrl);
|
||||||
|
return new FileUploads
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
FileName = fileName,
|
||||||
|
FileKey = fileKey,
|
||||||
|
FileSize = fileBytes.Length,
|
||||||
|
ContentType = "application/octet-stream",
|
||||||
|
Hash = hash,
|
||||||
|
QiniuUrl = qiniuUrl,
|
||||||
|
UploadTime = DateTime.Now,
|
||||||
|
Status = "active",
|
||||||
|
CreatedAt = DateTime.Now,
|
||||||
|
DeleteTime = qiniuSettings.DeleteDay != null ? BeijingTimeExtension.GetBeijingTime().AddDays((double)qiniuSettings.DeleteDay) : DateTime.MaxValue // 默认未删除
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
173
LMS.Tools/HttpTool/HttpService.cs
Normal file
173
LMS.Tools/HttpTool/HttpService.cs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
using LMS.Tools.FileTool;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LMS.Tools.HttpTool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HTTP网络请求服务
|
||||||
|
/// </summary>
|
||||||
|
public class HttpService : IHttpService
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly ILogger<HttpService> _logger;
|
||||||
|
|
||||||
|
public HttpService(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
ILogger<HttpService> logger)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载文件并返回字节数组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">文件URL</param>
|
||||||
|
/// <returns>文件字节数组</returns>
|
||||||
|
public async Task<byte[]?> DownloadFileAsync(string url, double maxFileSize)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
throw new ArgumentException("URL不能为空", nameof(url));
|
||||||
|
|
||||||
|
if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||||
|
throw new ArgumentException("无效的URL格式", nameof(url));
|
||||||
|
|
||||||
|
using var httpClient = _httpClientFactory.CreateClient("HttpService");
|
||||||
|
using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
throw new HttpRequestException($"HTTP请求失败,状态码: {response.StatusCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件大小
|
||||||
|
if (response.Content.Headers.ContentLength.HasValue)
|
||||||
|
{
|
||||||
|
if (response.Content.Headers.ContentLength.Value > maxFileSize * 1024 * 1024)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"文件大小({response.Content.Headers.ContentLength.Value} bytes)超过限制({maxFileSize * 1024 * 1024} bytes)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileBytes = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
|
||||||
|
if (fileBytes.Length > maxFileSize * 1024 * 1024)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"下载的文件大小({fileBytes.Length} bytes)超过限制({maxFileSize * 1024 * 1024} bytes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileBytes;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "HTTP请求异常: {Url}", url);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "请求超时: {Url}", url);
|
||||||
|
throw new TimeoutException("请求超时", ex);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "下载文件失败: {Url}", url);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送GET请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">请求URL</param>
|
||||||
|
/// <returns>响应内容</returns>
|
||||||
|
public async Task<string> GetAsync(string url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
throw new ArgumentException("URL不能为空", nameof(url));
|
||||||
|
|
||||||
|
using var httpClient = _httpClientFactory.CreateClient("HttpService");
|
||||||
|
var response = await httpClient.GetStringAsync(url);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "GET请求失败: {Url}", url);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送POST请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">请求URL</param>
|
||||||
|
/// <param name="content">请求内容</param>
|
||||||
|
/// <returns>响应内容</returns>
|
||||||
|
public async Task<string> PostAsync(string url, string content)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
throw new ArgumentException("URL不能为空", nameof(url));
|
||||||
|
|
||||||
|
using var httpClient = _httpClientFactory.CreateClient("HttpService");
|
||||||
|
var httpContent = new StringContent(content, Encoding.UTF8, "application/json");
|
||||||
|
var response = await httpClient.PostAsync(url, httpContent);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "POST请求失败: {Url}", url);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查URL是否可访问
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">要检查的URL</param>
|
||||||
|
/// <returns>是否可访问</returns>
|
||||||
|
public async Task<bool> IsUrlAccessibleAsync(string url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var httpClient = _httpClientFactory.CreateClient("HttpService");
|
||||||
|
using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
return response.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取URL的Content-Type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">要检查的URL</param>
|
||||||
|
/// <returns>Content-Type</returns>
|
||||||
|
public async Task<string?> GetContentTypeAsync(string url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var httpClient = _httpClientFactory.CreateClient("HttpService");
|
||||||
|
using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return response.Content.Headers.ContentType?.MediaType;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
LMS.Tools/HttpTool/IHttpService.cs
Normal file
45
LMS.Tools/HttpTool/IHttpService.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
namespace LMS.Tools.HttpTool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HTTP服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IHttpService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 下载文件并返回字节数组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">文件URL</param>
|
||||||
|
/// <param name="maxFileSize">最大文件大小限制(字节)</param>
|
||||||
|
/// <returns>文件字节数组</returns>
|
||||||
|
Task<byte[]?> DownloadFileAsync(string url, double maxFileSize);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送GET请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">请求URL</param>
|
||||||
|
/// <returns>响应内容</returns>
|
||||||
|
Task<string> GetAsync(string url);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送POST请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">请求URL</param>
|
||||||
|
/// <param name="content">请求内容</param>
|
||||||
|
/// <returns>响应内容</returns>
|
||||||
|
Task<string> PostAsync(string url, string content);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查URL是否可访问
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">要检查的URL</param>
|
||||||
|
/// <returns>是否可访问</returns>
|
||||||
|
Task<bool> IsUrlAccessibleAsync(string url);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取URL的Content-Type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">要检查的URL</param>
|
||||||
|
/// <returns>Content-Type</returns>
|
||||||
|
Task<string?> GetContentTypeAsync(string url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Qiniu.Shared" Version="7.2.15" />
|
||||||
<PackageReference Include="Quartz.AspNetCore" Version="3.14.0" />
|
<PackageReference Include="Quartz.AspNetCore" Version="3.14.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,8 @@ using LMS.service.Service.PromptService;
|
|||||||
using LMS.service.Service.RoleService;
|
using LMS.service.Service.RoleService;
|
||||||
using LMS.service.Service.SoftwareService;
|
using LMS.service.Service.SoftwareService;
|
||||||
using LMS.service.Service.UserService;
|
using LMS.service.Service.UserService;
|
||||||
|
using LMS.Tools.FileTool;
|
||||||
|
using LMS.Tools.HttpTool;
|
||||||
using LMS.Tools.MJPackage;
|
using LMS.Tools.MJPackage;
|
||||||
|
|
||||||
namespace Lai_server.Configuration
|
namespace Lai_server.Configuration
|
||||||
@ -67,7 +69,16 @@ namespace Lai_server.Configuration
|
|||||||
services.AddScoped<ITaskConcurrencyManager, TaskConcurrencyManager>();
|
services.AddScoped<ITaskConcurrencyManager, TaskConcurrencyManager>();
|
||||||
services.AddScoped<ITaskService, TaskService>();
|
services.AddScoped<ITaskService, TaskService>();
|
||||||
services.AddScoped<IQiniuUploadService, QiniuUploadService>();
|
services.AddScoped<IQiniuUploadService, QiniuUploadService>();
|
||||||
|
services.AddScoped<IQiniuService, QiniuService>();
|
||||||
|
|
||||||
|
// 注册HTTP服务(注意:由于HttpService是单例,这里使用工厂模式)
|
||||||
|
services.AddSingleton<IHttpService, HttpService>(); // 注册为单例
|
||||||
|
services.AddHttpClient("HttpService", client =>
|
||||||
|
{
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(30);
|
||||||
|
client.DefaultRequestHeaders.Add("User-Agent",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
|
||||||
|
});
|
||||||
|
|
||||||
// 注册后台服务
|
// 注册后台服务
|
||||||
services.AddHostedService<RsaConfigurattions>();
|
services.AddHostedService<RsaConfigurattions>();
|
||||||
|
|||||||
@ -54,34 +54,17 @@ namespace LMS.service.Controllers
|
|||||||
return await _qiniuUploadService.GetFilesByUser(requestUserId, userId, page, pageSize);
|
return await _qiniuUploadService.GetFilesByUser(requestUserId, userId, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL转存文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="machineId"></param>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("{machineId}")]
|
||||||
|
public async Task<ActionResult<APIResponseModel<UploadResult>>> UrlUpload(string machineId, [FromBody] UrlUploadRequest request)
|
||||||
|
{
|
||||||
|
return await _qiniuUploadService.UrlUpload(machineId, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///// <summary>
|
|
||||||
///// 删除文件
|
|
||||||
///// </summary>
|
|
||||||
///// <param name="fileId">文件ID</param>
|
|
||||||
///// <returns></returns>
|
|
||||||
//[HttpDelete("files/{fileId}")]
|
|
||||||
//public async Task<IActionResult> DeleteFile(long fileId)
|
|
||||||
//{
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// var userId = GetCurrentUserId();
|
|
||||||
// var success = await _qiniuUploadService.DeleteFileAsync(fileId, userId);
|
|
||||||
|
|
||||||
// if (success)
|
|
||||||
// {
|
|
||||||
// return Ok(new { message = "删除成功" });
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// return NotFound(new { message = "文件不存在或无权限删除" });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// return BadRequest(new { message = $"删除失败: {ex.Message}" });
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using LMS.service.Service;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
|
using System.Text.Json;
|
||||||
using static LMS.Common.Enums.ResponseCodeEnum;
|
using static LMS.Common.Enums.ResponseCodeEnum;
|
||||||
|
|
||||||
namespace LMS.service.Controllers;
|
namespace LMS.service.Controllers;
|
||||||
@ -11,9 +12,10 @@ namespace LMS.service.Controllers;
|
|||||||
[Route("lms/[controller]/[action]")]
|
[Route("lms/[controller]/[action]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
|
|
||||||
public class ForwardController(ForwardWordService forwardWordService) : ControllerBase
|
public class ForwardController(ForwardWordService forwardWordService, ILogger<ForwardController> logger) : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly ForwardWordService _forwardWordService = forwardWordService;
|
private readonly ForwardWordService _forwardWordService = forwardWordService;
|
||||||
|
private readonly ILogger<ForwardController> _logger = logger;
|
||||||
|
|
||||||
|
|
||||||
#region 非流转发接口,需要系统数据
|
#region 非流转发接口,需要系统数据
|
||||||
@ -114,4 +116,22 @@ public class ForwardController(ForwardWordService forwardWordService) : Controll
|
|||||||
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("/lms/v1/chat/completions")]
|
||||||
|
public async Task<ActionResult<APIResponseModel<object>>> Chat(JsonElement json)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Received chat request: {Json}", json.GetRawText());
|
||||||
|
return APIResponseModel<object>.CreateSuccessResponseModel("");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("/lms/mj-relax/mj/submit/video")]
|
||||||
|
[Route("/lms/mj-fast/mj/submit/video")]
|
||||||
|
[Route("/lms/mj/submit/video")]
|
||||||
|
public async Task<ActionResult<APIResponseModel<object>>> Imagine(JsonElement json)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Received chat request: {Json}", json.GetRawText());
|
||||||
|
return APIResponseModel<object>.CreateSuccessResponseModel("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ EXPOSE 8081
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
ARG BUILD_CONFIGURATION=Release
|
ARG BUILD_CONFIGURATION=Release
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
# 复制所有的项目文件
|
# 复制所有的项目文件
|
||||||
COPY ["LMS.service/LMS.service.csproj", "LMS.service/"]
|
COPY ["LMS.service/LMS.service.csproj", "LMS.service/"]
|
||||||
COPY ["LMS.Common/LMS.Common.csproj", "LMS.Common/"]
|
COPY ["LMS.Common/LMS.Common.csproj", "LMS.Common/"]
|
||||||
COPY ["LMS.DAO/LMS.DAO.csproj", "LMS.DAO/"]
|
COPY ["LMS.DAO/LMS.DAO.csproj", "LMS.DAO/"]
|
||||||
|
|||||||
@ -18,10 +18,10 @@ builder.Services.AddControllers();
|
|||||||
|
|
||||||
builder.Services.AddAuthentication();
|
builder.Services.AddAuthentication();
|
||||||
|
|
||||||
// 添加跨域
|
// 添加跨域
|
||||||
builder.Services.AddCorsServices();
|
builder.Services.AddCorsServices();
|
||||||
|
|
||||||
// 配置注入自定义服务方法
|
// 配置注入自定义服务方法
|
||||||
builder.Services.AddServices();
|
builder.Services.AddServices();
|
||||||
|
|
||||||
builder.Services.ConfigureApplicationCookie(options =>
|
builder.Services.ConfigureApplicationCookie(options =>
|
||||||
@ -31,12 +31,12 @@ builder.Services.ConfigureApplicationCookie(options =>
|
|||||||
options.Cookie.SameSite = SameSiteMode.None;
|
options.Cookie.SameSite = SameSiteMode.None;
|
||||||
});
|
});
|
||||||
|
|
||||||
//配置JWT
|
//配置JWT
|
||||||
builder.Services.AddJWTAuthentication();
|
builder.Services.AddJWTAuthentication();
|
||||||
builder.Services.AddAutoMapper(typeof(AutoMapperConfig));
|
builder.Services.AddAutoMapper(typeof(AutoMapperConfig));
|
||||||
builder.Services.AddLoggerService();
|
builder.Services.AddLoggerService();
|
||||||
builder.Host.UseSerilog();
|
builder.Host.UseSerilog();
|
||||||
// 关键步骤:注册 Serilog.ILogger 到 DI 容器
|
// 关键步骤:注册 Serilog.ILogger 到 DI 容器
|
||||||
builder.Services.AddSingleton(Log.Logger);
|
builder.Services.AddSingleton(Log.Logger);
|
||||||
builder.Services.AddQuartzTaskSchedulerService();
|
builder.Services.AddQuartzTaskSchedulerService();
|
||||||
|
|
||||||
@ -46,22 +46,22 @@ builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
|||||||
options.UseMySql(connectionString, ServerVersion.Parse("8.0.18-mysql"));
|
options.UseMySql(connectionString, ServerVersion.Parse("8.0.18-mysql"));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加内存缓存(用于存储速率限制计数器)
|
// 添加内存缓存(用于存储速率限制计数器)
|
||||||
builder.Services.AddMemoryCache();
|
builder.Services.AddMemoryCache();
|
||||||
|
|
||||||
// 加载通用配置(从appsettings.json)
|
// 加载通用配置(从app settings.json)
|
||||||
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));
|
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));
|
||||||
|
|
||||||
builder.Services.Configure<FileUploadSettings>(
|
builder.Services.Configure<FileUploadSettings>(
|
||||||
builder.Configuration.GetSection("FileUploadSettings"));
|
builder.Configuration.GetSection("FileUploadSettings"));
|
||||||
|
|
||||||
// 配置模型验证
|
// 配置模型验证
|
||||||
builder.Services.Configure<ApiBehaviorOptions>(options =>
|
builder.Services.Configure<ApiBehaviorOptions>(options =>
|
||||||
{
|
{
|
||||||
options.SuppressModelStateInvalidFilter = false; // 确保模型验证生效
|
options.SuppressModelStateInvalidFilter = false; // 确保模型验证生效
|
||||||
});
|
});
|
||||||
|
|
||||||
// 注入计数器和规则存储
|
// 注入计数器和规则存储
|
||||||
builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
|
builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
|
||||||
builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
|
builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
|
||||||
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
|
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
|
||||||
@ -71,20 +71,20 @@ builder.Services.AddSingleton<IProcessingStrategy, AsyncKeyLockProcessingStrateg
|
|||||||
|
|
||||||
builder.Services.AddIdentityCore<User>(options =>
|
builder.Services.AddIdentityCore<User>(options =>
|
||||||
{
|
{
|
||||||
options.SignIn.RequireConfirmedAccount = true; //已有账号才能登录
|
options.SignIn.RequireConfirmedAccount = true; //已有账号才能登录
|
||||||
options.SignIn.RequireConfirmedEmail = true; //
|
options.SignIn.RequireConfirmedEmail = true; //
|
||||||
options.Password.RequireDigit = true; // 数据库中至少有一个数字
|
options.Password.RequireDigit = true; // 数据库中至少有一个数字
|
||||||
options.Password.RequireLowercase = true; // 数据库中至少有一个小写字母
|
options.Password.RequireLowercase = true; // 数据库中至少有一个小写字母
|
||||||
options.Password.RequireUppercase = true; // 数据库中至少有一个大写字母
|
options.Password.RequireUppercase = true; // 数据库中至少有一个大写字母
|
||||||
options.Password.RequireNonAlphanumeric = true; // 数据库中至少有一个特殊字符
|
options.Password.RequireNonAlphanumeric = true; // 数据库中至少有一个特殊字符
|
||||||
options.Password.RequiredLength = 8; // 密码长度最少8位
|
options.Password.RequiredLength = 8; // 密码长度最少8位
|
||||||
|
|
||||||
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); // 锁定时间
|
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); // 锁定时间
|
||||||
options.Lockout.MaxFailedAccessAttempts = 10; // 尝试次数
|
options.Lockout.MaxFailedAccessAttempts = 10; // 尝试次数
|
||||||
options.Lockout.AllowedForNewUsers = true; // 新用户是否可以锁定
|
options.Lockout.AllowedForNewUsers = true; // 新用户是否可以锁定
|
||||||
|
|
||||||
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; // 用户名允许的字符
|
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; // 用户名允许的字符
|
||||||
options.User.RequireUniqueEmail = true; // 允许重复邮箱
|
options.User.RequireUniqueEmail = true; // 允许重复邮箱
|
||||||
//options.User.
|
//options.User.
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ idBuilder.AddEntityFrameworkStores<ApplicationDbContext>()
|
|||||||
|
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
// 注入Swagger
|
// 注入Swagger
|
||||||
builder.Services.AddSwaggerService();
|
builder.Services.AddSwaggerService();
|
||||||
|
|
||||||
|
|
||||||
@ -124,16 +124,18 @@ app.UseAuthentication();
|
|||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
// 在管道中使用IP速率限制中间件
|
// 你好
|
||||||
|
|
||||||
|
// 在管道中使用IP速率限制中间件
|
||||||
app.UseIpRateLimiting();
|
app.UseIpRateLimiting();
|
||||||
|
|
||||||
// 添加动态权限的中间件
|
// 添加动态权限的中间件
|
||||||
app.UseMiddleware<DynamicPermissionMiddleware>();
|
app.UseMiddleware<DynamicPermissionMiddleware>();
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
_ = endpoints.MapControllers();
|
_ = endpoints.MapControllers();
|
||||||
});
|
});
|
||||||
|
|
||||||
Log.Information("后台启动成功,系统版本:" + version);
|
Log.Information("后台启动成功,系统版本:" + version);
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
@ -1,19 +1,51 @@
|
|||||||
using LMS.Repository.DB;
|
using LMS.Repository.DTO;
|
||||||
using LMS.Repository.DTO;
|
|
||||||
using LMS.Repository.FileUpload;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static LMS.Repository.DTO.FileUploadDto;
|
using static LMS.Repository.DTO.FileUploadDto;
|
||||||
using static LMS.Repository.FileUpload.FileRequestReturn;
|
using static LMS.Repository.FileUpload.FileRequestReturn;
|
||||||
|
|
||||||
namespace LMS.service.Service.FileUploadService
|
namespace LMS.service.Service.FileUploadService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 七牛云文件上传服务接口
|
||||||
|
/// 提供文件上传、文件列表查询等功能
|
||||||
|
/// </summary>
|
||||||
public interface IQiniuUploadService
|
public interface IQiniuUploadService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 上传本地的文件到七牛云,接收 Base64 格式的文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">Base64格式的文件上传请求参数</param>
|
||||||
|
/// <param name="machineId">机器码标识</param>
|
||||||
|
/// <returns>返回上传结果,包含文件URL、Hash等信息</returns>
|
||||||
Task<APIResponseModel<UploadResult>> UploadBase64Async(ByteUploadRequest request, string machineId);
|
Task<APIResponseModel<UploadResult>> UploadBase64Async(ByteUploadRequest request, string machineId);
|
||||||
//Task<List<FileUploads>> GetUserFilesAsync(string userId, int page = 1, int pageSize = 20);
|
|
||||||
//Task<int> GetUserFilesCountAsync(string userId);
|
/// <summary>
|
||||||
//Task<bool> DeleteFileAsync(long fileId, string userId);
|
/// 通过网络URL转存文件到七牛云
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="machineId">机器码标识</param>
|
||||||
|
/// <param name="request">URL上传请求参数,包含文件URL和文件名</param>
|
||||||
|
/// <returns>返回转存结果,包含文件URL、Hash等信息</returns>
|
||||||
|
Task<ActionResult<APIResponseModel<UploadResult>>> UrlUpload(string machineId, UrlUploadRequest request);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户文件列表,通过机器码查询
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="machineId">机器码标识</param>
|
||||||
|
/// <param name="page">查看页码,从1开始</param>
|
||||||
|
/// <param name="pageSize">分页大小,每页显示的文件数量</param>
|
||||||
|
/// <returns>返回分页的文件列表,包含文件基本信息</returns>
|
||||||
Task<ActionResult<APIResponseModel<CollectionResponse<FileMachineRequestReturn>>>> GetFilesByMachineId(string machineId, int page, int pageSize);
|
Task<ActionResult<APIResponseModel<CollectionResponse<FileMachineRequestReturn>>>> GetFilesByMachineId(string machineId, int page, int pageSize);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定用户的文件列表
|
||||||
|
/// 如果请求用户是超级管理员,可以查看所有用户的文件
|
||||||
|
/// 普通用户只能查看自己的文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requestUserId">请求用户的ID</param>
|
||||||
|
/// <param name="userId">要查询的用户ID</param>
|
||||||
|
/// <param name="page">查看页码,从1开始</param>
|
||||||
|
/// <param name="pageSize">分页大小,每页显示的文件数量</param>
|
||||||
|
/// <returns>返回分页的用户文件列表,包含文件详细信息</returns>
|
||||||
Task<ActionResult<APIResponseModel<CollectionResponse<FileUserRequestReturn>>>> GetFilesByUser(long requestUserId, long userId, int page, int pageSize);
|
Task<ActionResult<APIResponseModel<CollectionResponse<FileUserRequestReturn>>>> GetFilesByUser(long requestUserId, long userId, int page, int pageSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,64 +5,78 @@ using LMS.DAO.UserDAO;
|
|||||||
using LMS.Repository.DB;
|
using LMS.Repository.DB;
|
||||||
using LMS.Repository.DTO;
|
using LMS.Repository.DTO;
|
||||||
using LMS.Repository.FileUpload;
|
using LMS.Repository.FileUpload;
|
||||||
using LMS.Tools.ImageTool;
|
using LMS.Tools.FileTool;
|
||||||
|
using LMS.Tools.HttpTool;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Qiniu.Http;
|
|
||||||
using Qiniu.IO;
|
|
||||||
using Qiniu.IO.Model;
|
|
||||||
using Qiniu.Util;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using static LMS.Common.Enums.ResponseCodeEnum;
|
using static LMS.Common.Enums.ResponseCodeEnum;
|
||||||
using static LMS.Repository.DTO.FileUploadDto;
|
using static LMS.Repository.DTO.FileUploadDto;
|
||||||
using static LMS.Repository.FileUpload.FileRequestReturn;
|
using static LMS.Repository.FileUpload.FileRequestReturn;
|
||||||
|
|
||||||
namespace LMS.service.Service.FileUploadService
|
namespace LMS.service.Service.FileUploadService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 七牛云文件上传服务实现类
|
||||||
|
/// 提供Base64文件上传、URL转存、文件列表查询等功能
|
||||||
|
/// 支持图片格式验证、文件大小限制、用户权限控制
|
||||||
|
/// </summary>
|
||||||
public class QiniuUploadService : IQiniuUploadService
|
public class QiniuUploadService : IQiniuUploadService
|
||||||
{
|
{
|
||||||
|
#region 私有字段
|
||||||
|
|
||||||
private readonly FileUploadSettings _uploadSettings;
|
private readonly FileUploadSettings _uploadSettings;
|
||||||
private readonly ApplicationDbContext _dbContext;
|
private readonly ApplicationDbContext _dbContext;
|
||||||
private readonly UploadManager _uploadManager;
|
|
||||||
private readonly ILogger<QiniuUploadService> _logger;
|
private readonly ILogger<QiniuUploadService> _logger;
|
||||||
private readonly UserBasicDao _userBasicDao;
|
private readonly UserBasicDao _userBasicDao;
|
||||||
private readonly OptionGlobalDAO _optionGlobalDAO;
|
private readonly OptionGlobalDAO _optionGlobalDAO;
|
||||||
|
private readonly IQiniuService _qiniuService;
|
||||||
|
private readonly IHttpService _httpService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 构造函数
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化七牛云上传服务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uploadSettings">文件上传配置</param>
|
||||||
|
/// <param name="logger">日志记录器</param>
|
||||||
|
/// <param name="userBasicDao">用户基础数据访问对象</param>
|
||||||
|
/// <param name="optionGlobalDAO">全局选项数据访问对象</param>
|
||||||
|
/// <param name="qiniuService">七牛云服务接口</param>
|
||||||
|
/// <param name="fileService">文件服务接口</param>
|
||||||
|
/// <param name="dbContext">数据库上下文</param>
|
||||||
public QiniuUploadService(
|
public QiniuUploadService(
|
||||||
IOptions<FileUploadSettings> uploadSettings,
|
IOptions<FileUploadSettings> uploadSettings,
|
||||||
ILogger<QiniuUploadService> logger,
|
ILogger<QiniuUploadService> logger,
|
||||||
UserBasicDao userBasicDao,
|
UserBasicDao userBasicDao,
|
||||||
OptionGlobalDAO optionGlobalDAO,
|
OptionGlobalDAO optionGlobalDAO,
|
||||||
|
IQiniuService qiniuService,
|
||||||
|
IHttpService httpService,
|
||||||
ApplicationDbContext dbContext)
|
ApplicationDbContext dbContext)
|
||||||
{
|
{
|
||||||
_uploadSettings = uploadSettings.Value;
|
_uploadSettings = uploadSettings.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
_optionGlobalDAO = optionGlobalDAO;
|
_optionGlobalDAO = optionGlobalDAO;
|
||||||
_userBasicDao = userBasicDao; ;
|
_userBasicDao = userBasicDao;
|
||||||
_uploadManager = new UploadManager();
|
_qiniuService = qiniuService;
|
||||||
|
_httpService = httpService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
#endregion
|
||||||
/// 通过字节数组上传文件到七牛云
|
|
||||||
/// </summary>
|
#region 公共方法
|
||||||
/// <param name="request"></param>
|
|
||||||
/// <param name="machineId"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<APIResponseModel<UploadResult>> UploadBase64Async(ByteUploadRequest request, string machineId)
|
public async Task<APIResponseModel<UploadResult>> UploadBase64Async(ByteUploadRequest request, string machineId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 将 文件的base64 字符串转换为字节数组
|
// 将文件的base64字符串转换为字节数组
|
||||||
var fileBytes = ConvertBase64ToBytes(request.File);
|
byte[] fileBytes = FileService.ConvertBase64ToBytes(request.File) ?? [];
|
||||||
if (fileBytes == null || fileBytes.Length == 0)
|
|
||||||
{
|
|
||||||
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的文件数据");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 验证数据
|
// 1. 验证数据 - 使用FileService中的验证方法
|
||||||
var validationResult = ValidateUploadRequest(request, fileBytes);
|
var validationResult = await ValidateUploadRequest(request, fileBytes);
|
||||||
if (!validationResult.Success)
|
if (!validationResult.Success)
|
||||||
{
|
{
|
||||||
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, validationResult.Message);
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, validationResult.Message);
|
||||||
@ -82,67 +96,12 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "今日上传文件数量已达上限,请明天再试");
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "今日上传文件数量已达上限,请明天再试");
|
||||||
}
|
}
|
||||||
|
|
||||||
QiniuSettings? qiniuSettings = await _optionGlobalDAO.FindAndReturnOption<QiniuSettings>("SYS_QiniuSetting");
|
string fileKey = $"diantu/user/{userId}/{DateTime.Now:yyyyMMdd}/{request.FileName}";
|
||||||
if (qiniuSettings == null || string.IsNullOrEmpty(qiniuSettings.AccessKey) || string.IsNullOrEmpty(qiniuSettings.SecretKey) || string.IsNullOrEmpty(qiniuSettings.BucketName) || string.IsNullOrEmpty(qiniuSettings.Domain))
|
|
||||||
{
|
|
||||||
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "配置不完整,请检查配置,请联系管理员");
|
|
||||||
}
|
|
||||||
|
|
||||||
Mac mac = new(qiniuSettings.AccessKey, qiniuSettings.SecretKey);
|
// 4. 上传到七牛云
|
||||||
|
FileUploads fileUpload = await _qiniuService.UploadFileToQiNiu(fileBytes, userId.Value, request.FileName, fileKey);
|
||||||
|
|
||||||
// 4. 生成文件key
|
|
||||||
var fileKey = GenerateFileKey(userId.Value, request.FileName);
|
|
||||||
|
|
||||||
|
|
||||||
// 5. 生成上传凭证
|
|
||||||
var putPolicy = new PutPolicy
|
|
||||||
{
|
|
||||||
Scope = qiniuSettings.BucketName
|
|
||||||
};
|
|
||||||
if (qiniuSettings.DeleteDay != null)
|
|
||||||
{
|
|
||||||
putPolicy.DeleteAfterDays = qiniuSettings.DeleteDay.Value; // 设置过期时间
|
|
||||||
}
|
|
||||||
putPolicy.SetExpires(3600);
|
|
||||||
var token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString());
|
|
||||||
|
|
||||||
// 6. 计算文件哈希
|
|
||||||
var hash = ComputeSHA1Hash(fileBytes);
|
|
||||||
|
|
||||||
// 7. 上传到七牛云
|
|
||||||
HttpResult uploadResult;
|
|
||||||
using (var stream = new MemoryStream(fileBytes))
|
|
||||||
{
|
|
||||||
uploadResult = await _uploadManager.UploadStreamAsync(stream, fileKey, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. 检查上传结果
|
|
||||||
if (uploadResult.Code != 200)
|
|
||||||
{
|
|
||||||
_logger.LogError($"文件上传失败, 上传用户ID: {userId}, 错误信息: {uploadResult.Text}");
|
|
||||||
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.SystemError, $"文件上传失败: {uploadResult.Text}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 9. 构建访问URL
|
|
||||||
var qiniuUrl = BuildFileUrl(qiniuSettings.Domain, fileKey);
|
|
||||||
|
|
||||||
// 10. 保存到数据库
|
|
||||||
var fileUpload = new FileUploads
|
|
||||||
{
|
|
||||||
UserId = userId.Value,
|
|
||||||
FileName = request.FileName,
|
|
||||||
FileKey = fileKey,
|
|
||||||
FileSize = fileBytes.Length,
|
|
||||||
ContentType = request.ContentType ?? "application/octet-stream",
|
|
||||||
Hash = hash,
|
|
||||||
QiniuUrl = qiniuUrl,
|
|
||||||
UploadTime = DateTime.Now,
|
|
||||||
Status = "active",
|
|
||||||
CreatedAt = DateTime.Now,
|
|
||||||
DeleteTime = qiniuSettings.DeleteDay != null ? BeijingTimeExtension.GetBeijingTime().AddDays((double)qiniuSettings.DeleteDay) : DateTime.MaxValue // 默认未删除
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// 5. 修改数据库
|
||||||
_dbContext.FileUploads.Add(fileUpload);
|
_dbContext.FileUploads.Add(fileUpload);
|
||||||
await _dbContext.SaveChangesAsync();
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
@ -150,9 +109,9 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
{
|
{
|
||||||
Success = true,
|
Success = true,
|
||||||
Message = "上传成功",
|
Message = "上传成功",
|
||||||
Url = qiniuUrl,
|
Url = fileUpload.QiniuUrl,
|
||||||
FileKey = fileKey,
|
FileKey = fileUpload.FileKey,
|
||||||
Hash = hash,
|
Hash = fileUpload.Hash,
|
||||||
FileId = fileUpload.Id,
|
FileId = fileUpload.Id,
|
||||||
FileSize = fileBytes.Length
|
FileSize = fileBytes.Length
|
||||||
});
|
});
|
||||||
@ -165,27 +124,21 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取对应机器码上传的图片信息
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="machineId"></param>
|
|
||||||
/// <param name="page"></param>
|
|
||||||
/// <param name="pageSize"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<ActionResult<APIResponseModel<CollectionResponse<FileMachineRequestReturn>>>> GetFilesByMachineId(string machineId, int page, int pageSize)
|
public async Task<ActionResult<APIResponseModel<CollectionResponse<FileMachineRequestReturn>>>> GetFilesByMachineId(string machineId, int page, int pageSize)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 1. 判断机器码 是不是 存在 并且获取对应的ID
|
// 1. 验证机器码并获取关联的用户ID
|
||||||
long? userId = await GetUserIdFromMachine(machineId);
|
long? userId = await GetUserIdFromMachine(machineId);
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return APIResponseModel<CollectionResponse<FileMachineRequestReturn>>.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的机器ID或未找到关联用户");
|
return APIResponseModel<CollectionResponse<FileMachineRequestReturn>>.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的机器ID或未找到关联用户");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 获取用户的文件列表
|
// 2. 获取用户的文件列表
|
||||||
var filesList = await GetUserFilesAsync(userId.Value, page, pageSize);
|
var filesList = await GetUserFilesAsync(userId.Value, page, pageSize);
|
||||||
|
|
||||||
// 4. 构建返回结果
|
// 3. 构建返回结果,将数据库实体转换为响应模型
|
||||||
var fileList = filesList.fileList.Select(f => new FileMachineRequestReturn
|
var fileList = filesList.fileList.Select(f => new FileMachineRequestReturn
|
||||||
{
|
{
|
||||||
MachineId = machineId,
|
MachineId = machineId,
|
||||||
@ -198,34 +151,37 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
CreatedAt = f.CreatedAt,
|
CreatedAt = f.CreatedAt,
|
||||||
DeleteTime = f.DeleteTime
|
DeleteTime = f.DeleteTime
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
var response = new CollectionResponse<FileMachineRequestReturn>
|
var response = new CollectionResponse<FileMachineRequestReturn>
|
||||||
{
|
{
|
||||||
Current = page,
|
Current = page,
|
||||||
Total = filesList.totlaCount,
|
Total = filesList.totlaCount,
|
||||||
Collection = fileList
|
Collection = fileList
|
||||||
};
|
};
|
||||||
|
|
||||||
return APIResponseModel<CollectionResponse<FileMachineRequestReturn>>.CreateSuccessResponseModel(response);
|
return APIResponseModel<CollectionResponse<FileMachineRequestReturn>>.CreateSuccessResponseModel(response);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// 这里可以记录日志或处理异常
|
|
||||||
_logger.LogError(ex, $"获取文件列表失败, 机器码: {machineId}");
|
_logger.LogError(ex, $"获取文件列表失败, 机器码: {machineId}");
|
||||||
return APIResponseModel<CollectionResponse<FileMachineRequestReturn>>.CreateErrorResponseModel(ResponseCode.SystemError, "获取文件列表失败");
|
return APIResponseModel<CollectionResponse<FileMachineRequestReturn>>.CreateErrorResponseModel(ResponseCode.SystemError, "获取文件列表失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取指定的用户的文件列表,如果是超级管理员则获取所有用户的文件列表
|
/// 获取指定用户的文件列表
|
||||||
|
/// 超级管理员可以查看所有用户的文件,普通用户只能查看自己的文件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="requestUserId"></param>
|
/// <param name="requestUserId">请求用户的ID</param>
|
||||||
/// <param name="page"></param>
|
/// <param name="userId">要查询的目标用户ID</param>
|
||||||
/// <param name="pageSize"></param>
|
/// <param name="page">页码,从1开始</param>
|
||||||
/// <returns></returns>
|
/// <param name="pageSize">每页显示的文件数量</param>
|
||||||
|
/// <returns>分页的用户文件列表响应</returns>
|
||||||
public async Task<ActionResult<APIResponseModel<CollectionResponse<FileUserRequestReturn>>>> GetFilesByUser(long requestUserId, long userId, int page, int pageSize)
|
public async Task<ActionResult<APIResponseModel<CollectionResponse<FileUserRequestReturn>>>> GetFilesByUser(long requestUserId, long userId, int page, int pageSize)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 1. 判断用户是不是超级管理员,不是超级管理员只能获取自己的
|
// 1. 检查用户权限:判断是否为超级管理员
|
||||||
bool isSuperAdmin = await _userBasicDao.CheckUserIsSuperAdmin(requestUserId);
|
bool isSuperAdmin = await _userBasicDao.CheckUserIsSuperAdmin(requestUserId);
|
||||||
|
|
||||||
var fileMessage = (0, new List<FileUploads>());
|
var fileMessage = (0, new List<FileUploads>());
|
||||||
@ -236,14 +192,16 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// 普通用户权限检查:只能查看自己的文件
|
||||||
if (requestUserId != userId)
|
if (requestUserId != userId)
|
||||||
{
|
{
|
||||||
return APIResponseModel<CollectionResponse<FileUserRequestReturn>>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
|
return APIResponseModel<CollectionResponse<FileUserRequestReturn>>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
|
||||||
}
|
}
|
||||||
// 普通用户只能获取自己的文件
|
// 获取用户自己的文件列表
|
||||||
fileMessage = await GetUserFilesAsync(requestUserId, page, pageSize);
|
fileMessage = await GetUserFilesAsync(requestUserId, page, pageSize);
|
||||||
}
|
}
|
||||||
// 2. 构建返回结果
|
|
||||||
|
// 2. 构建返回结果,将数据库实体转换为响应模型
|
||||||
var fileList = fileMessage.Item2.Select(f => new FileUserRequestReturn
|
var fileList = fileMessage.Item2.Select(f => new FileUserRequestReturn
|
||||||
{
|
{
|
||||||
Id = f.Id,
|
Id = f.Id,
|
||||||
@ -257,12 +215,14 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
CreatedAt = f.CreatedAt,
|
CreatedAt = f.CreatedAt,
|
||||||
DeleteTime = f.DeleteTime
|
DeleteTime = f.DeleteTime
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
var response = new CollectionResponse<FileUserRequestReturn>
|
var response = new CollectionResponse<FileUserRequestReturn>
|
||||||
{
|
{
|
||||||
Current = page,
|
Current = page,
|
||||||
Total = fileMessage.Item1,
|
Total = fileMessage.Item1,
|
||||||
Collection = fileList
|
Collection = fileList
|
||||||
};
|
};
|
||||||
|
|
||||||
return APIResponseModel<CollectionResponse<FileUserRequestReturn>>.CreateSuccessResponseModel(response);
|
return APIResponseModel<CollectionResponse<FileUserRequestReturn>>.CreateSuccessResponseModel(response);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -274,49 +234,49 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
|
|
||||||
public async Task<(int totlaCount, List<FileUploads> fileList)> GetUserFilesAsync(long userId, int page = 1, int pageSize = 10)
|
public async Task<(int totlaCount, List<FileUploads> fileList)> GetUserFilesAsync(long userId, int page = 1, int pageSize = 10)
|
||||||
{
|
{
|
||||||
// 获取用户的文件总数
|
// 获取用户的活跃文件总数
|
||||||
int totalCount = await _dbContext.FileUploads
|
int totalCount = await _dbContext.FileUploads
|
||||||
.Where(f => f.UserId == userId && f.Status == "active")
|
.Where(f => f.UserId == userId && f.Status == "active")
|
||||||
.CountAsync();
|
.CountAsync();
|
||||||
// 获取用户的文件列表
|
|
||||||
|
// 如果没有文件,直接返回空结果
|
||||||
if (totalCount == 0)
|
if (totalCount == 0)
|
||||||
{
|
{
|
||||||
return (0, new List<FileUploads>());
|
return (0, new List<FileUploads>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分页查询用户文件列表,按上传时间倒序排列
|
||||||
var fileList = _dbContext.FileUploads
|
var fileList = _dbContext.FileUploads
|
||||||
.Where(f => f.UserId == userId && f.Status == "active")
|
.Where(f => f.UserId == userId && f.Status == "active")
|
||||||
.OrderByDescending(f => f.UploadTime)
|
.OrderByDescending(f => f.UploadTime)
|
||||||
.Skip((page - 1) * pageSize)
|
.Skip((page - 1) * pageSize)
|
||||||
.Take(pageSize)
|
.Take(pageSize)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
return (totalCount, await fileList);
|
return (totalCount, await fileList);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(int totleCount, List<FileUploads> fileList)> GetAllUserFilesAsync(int page = 1, int pageSize = 10)
|
public async Task<(int totleCount, List<FileUploads> fileList)> GetAllUserFilesAsync(int page = 1, int pageSize = 10)
|
||||||
{
|
{
|
||||||
// 获取所有用户的文件总数
|
// 获取所有活跃文件的总数
|
||||||
int totalCount = await _dbContext.FileUploads
|
int totalCount = await _dbContext.FileUploads
|
||||||
.Where(f => f.Status == "active")
|
.Where(f => f.Status == "active")
|
||||||
.CountAsync();
|
.CountAsync();
|
||||||
|
|
||||||
if (totalCount == 0)
|
if (totalCount == 0)
|
||||||
{
|
{
|
||||||
return (0, new List<FileUploads>());
|
return (0, new List<FileUploads>());
|
||||||
}
|
}
|
||||||
// 获取所有用户的文件列表
|
|
||||||
|
// 分页查询所有用户的文件列表,按上传时间倒序排列
|
||||||
List<FileUploads> fileUploads = await _dbContext.FileUploads
|
List<FileUploads> fileUploads = await _dbContext.FileUploads
|
||||||
.Where(f => f.Status == "active")
|
.Where(f => f.Status == "active")
|
||||||
.OrderByDescending(f => f.UploadTime)
|
.OrderByDescending(f => f.UploadTime)
|
||||||
.Skip((page - 1) * pageSize)
|
.Skip((page - 1) * pageSize)
|
||||||
.Take(pageSize)
|
.Take(pageSize)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
return (totalCount, fileUploads);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> GetUserFilesCountAsync(long userId)
|
return (totalCount, fileUploads);
|
||||||
{
|
|
||||||
return await _dbContext.FileUploads
|
|
||||||
.CountAsync(f => f.UserId == userId && f.Status == "active");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GetUserUploadToday(long userId)
|
private async Task<int> GetUserUploadToday(long userId)
|
||||||
@ -325,44 +285,17 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
.CountAsync(f => f.UserId == userId && f.CreatedAt.Date == BeijingTimeExtension.GetBeijingTime().Date);
|
.CountAsync(f => f.UserId == userId && f.CreatedAt.Date == BeijingTimeExtension.GetBeijingTime().Date);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[]? ConvertBase64ToBytes(string base64String)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(base64String))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 检查是否以 "data:" 开头并包含 base64 编码
|
|
||||||
if (base64String.StartsWith("data:"))
|
|
||||||
{
|
|
||||||
// 提取 base64 编码部分
|
|
||||||
var commaIndex = base64String.IndexOf(',');
|
|
||||||
if (commaIndex >= 0)
|
|
||||||
{
|
|
||||||
base64String = base64String.Substring(commaIndex + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 判断会不会出现异常
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 尝试将 base64 字符串转换为字节数组
|
|
||||||
return Convert.FromBase64String(base64String);
|
|
||||||
}
|
|
||||||
catch (FormatException)
|
|
||||||
{
|
|
||||||
// 如果格式不正确,返回 null
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<long?> GetUserIdFromMachine(string machineId)
|
private async Task<long?> GetUserIdFromMachine(string machineId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(machineId))
|
if (string.IsNullOrWhiteSpace(machineId))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询有效的机器码(未过期或无过期时间)
|
||||||
Machine? machine = await _dbContext.Machine
|
Machine? machine = await _dbContext.Machine
|
||||||
.Where(m => m.MachineId == machineId && (m.DeactivationTime > BeijingTimeExtension.GetBeijingTime() || m.DeactivationTime == null))
|
.Where(m => m.MachineId == machineId && (m.DeactivationTime > BeijingTimeExtension.GetBeijingTime() || m.DeactivationTime == null))
|
||||||
.FirstOrDefaultAsync(); // 改回原来的 FirstOrDefaultAsync
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (machine != null)
|
if (machine != null)
|
||||||
{
|
{
|
||||||
@ -374,19 +307,102 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public async Task<ActionResult<APIResponseModel<UploadResult>>> UrlUpload(string machineId, UrlUploadRequest request)
|
||||||
// 私有方法
|
|
||||||
private UploadResult ValidateUploadRequest(ByteUploadRequest request, byte[] fileBytes)
|
|
||||||
{
|
{
|
||||||
if (fileBytes == null || fileBytes.Length == 0)
|
try
|
||||||
{
|
{
|
||||||
return new UploadResult
|
// 1. 获取用户
|
||||||
|
long? userId = await GetUserIdFromMachine(machineId);
|
||||||
|
if (userId == null)
|
||||||
{
|
{
|
||||||
Success = false,
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的机器ID或未找到关联用户");
|
||||||
Message = "文件字节数据不能为空"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 验证URL格式
|
||||||
|
if (!Uri.IsWellFormedUriString(request.Url, UriKind.Absolute))
|
||||||
|
{
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的URL格式");
|
||||||
|
}
|
||||||
|
|
||||||
|
//4.下载网络图片获取字节数组
|
||||||
|
byte[] fileBytes;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
double maxFileSize = await _optionGlobalDAO.FindAndReturnOption<double>("SYS_MaxUploadFileSize");
|
||||||
|
fileBytes = await _httpService.DownloadFileAsync(request.Url, maxFileSize) ?? [];
|
||||||
|
if (fileBytes == null || fileBytes.Length == 0)
|
||||||
|
{
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "无法下载指定的文件或文件为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex) when (ex.Message.Contains("文件大小") && ex.Message.Contains("超过限制"))
|
||||||
|
{
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "文件大小超过限制");
|
||||||
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "下载文件超时,请检查网络连接或URL有效性");
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, $"下载文件失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"下载文件时发生未知错误: {request.Url}");
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "下载文件失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 生成文件路径
|
||||||
|
string fileKey = $"upfile/user/{userId}/{DateTime.Now:yyyyMMdd}/transfer_{request.FileName}";
|
||||||
|
|
||||||
|
// 8. 上传到七牛云
|
||||||
|
FileUploads fileUpload = await _qiniuService.UploadFileToQiNiu(fileBytes, userId.Value, request.FileName, fileKey);
|
||||||
|
|
||||||
|
// 10. 返回成功结果
|
||||||
|
return APIResponseModel<UploadResult>.CreateSuccessResponseModel(new UploadResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "URL转存成功",
|
||||||
|
Url = fileUpload.QiniuUrl,
|
||||||
|
FileKey = fileUpload.FileKey,
|
||||||
|
Hash = fileUpload.Hash,
|
||||||
|
FileId = fileUpload.Id,
|
||||||
|
FileSize = fileBytes.Length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"URL转存文件失败, 上传机器码: {machineId},URL: {request.Url}");
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.SystemError, $"转存失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region 私有辅助方法
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证上传请求的文件是否符合要求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">上传请求参数</param>
|
||||||
|
/// <param name="fileBytes">文件字节数组</param>
|
||||||
|
/// <param name="allowedContentTypes">允许的文件类型列表</param>
|
||||||
|
/// <param name="maxFileSize">最大文件大小(字节)</param>
|
||||||
|
/// <returns>验证结果</returns>
|
||||||
|
public async Task<UploadResult> ValidateUploadRequest(ByteUploadRequest request, byte[] fileBytes)
|
||||||
|
{
|
||||||
|
|
||||||
|
double maxFileSize = await _optionGlobalDAO.FindAndReturnOption<double>("SYS_MaxUploadFileSize");
|
||||||
|
// 1. 检查文件大小
|
||||||
|
var sizeCheckResult = await FileService.CheckFileSize(fileBytes, maxFileSize);
|
||||||
|
if (!sizeCheckResult.Success)
|
||||||
|
{
|
||||||
|
return sizeCheckResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 校验文件名不能为空
|
||||||
if (string.IsNullOrEmpty(request.FileName))
|
if (string.IsNullOrEmpty(request.FileName))
|
||||||
{
|
{
|
||||||
return new UploadResult
|
return new UploadResult
|
||||||
@ -396,18 +412,11 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileBytes.Length > _uploadSettings.MaxFileSize)
|
List<string> allowedContentTypes = await _optionGlobalDAO.FindAndReturnOption<List<string>>("SYS.AllowedContentTypes") ?? [];
|
||||||
{
|
// 3. 检查文件类型(如果有配置允许的类型)
|
||||||
return new UploadResult
|
if (allowedContentTypes.Count > 0 &&
|
||||||
{
|
|
||||||
Success = false,
|
|
||||||
Message = $"文件大小不能超过 {_uploadSettings.MaxFileSize / (1024 * 1024)}MB"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_uploadSettings.AllowedContentTypes.Count != 0 &&
|
|
||||||
!string.IsNullOrEmpty(request.ContentType) &&
|
!string.IsNullOrEmpty(request.ContentType) &&
|
||||||
!_uploadSettings.AllowedContentTypes.Contains(request.ContentType.ToLower()))
|
!allowedContentTypes.Contains(request.ContentType.ToLower()))
|
||||||
{
|
{
|
||||||
return new UploadResult
|
return new UploadResult
|
||||||
{
|
{
|
||||||
@ -416,9 +425,8 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查实际的文件类型是否在允许的列表中
|
// 4. 检查是否为有效的图片文件
|
||||||
// 只检查是否为图片,不是图片就拒绝
|
if (!FileService.IsValidImageFile(fileBytes))
|
||||||
if (!ImageTypeDetector.IsValidImage(fileBytes))
|
|
||||||
{
|
{
|
||||||
return new UploadResult
|
return new UploadResult
|
||||||
{
|
{
|
||||||
@ -430,23 +438,8 @@ namespace LMS.service.Service.FileUploadService
|
|||||||
return new UploadResult { Success = true };
|
return new UploadResult { Success = true };
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateFileKey(long userId, string fileName)
|
#endregion
|
||||||
{
|
|
||||||
var date = DateTime.Now.ToString("yyyyMMdd");
|
|
||||||
var guid = Guid.NewGuid().ToString("N");
|
|
||||||
var extension = Path.GetExtension(fileName);
|
|
||||||
return $"diantu/user/{userId}/{date}/{guid}{extension}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ComputeSHA1Hash(byte[] data)
|
|
||||||
{
|
|
||||||
var hash = SHA1.HashData(data);
|
|
||||||
return Convert.ToHexString(hash).ToLower();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildFileUrl(string domain, string fileKey)
|
|
||||||
{
|
|
||||||
return $"{domain}/{fileKey}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -361,7 +361,8 @@ public class PromptService(UserBasicDao userBasicDao, ApplicationDbContext conte
|
|||||||
Id = prompt.Id,
|
Id = prompt.Id,
|
||||||
Name = prompt.Name,
|
Name = prompt.Name,
|
||||||
PromptTypeId = prompt.PromptTypeId,
|
PromptTypeId = prompt.PromptTypeId,
|
||||||
Remark = prompt.Remark
|
Remark = prompt.Remark,
|
||||||
|
Description = prompt.Description
|
||||||
};
|
};
|
||||||
promptNames.Add(promptName);
|
promptNames.Add(promptName);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user