using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MilliSim.Common.Dtos;
using MilliSim.Common.Models;
using MilliSim.Data;
using MilliSim.Services;
using MilliSim.Services.Infrastructure;
namespace MilliSim.Controllers;
[ApiController]
[Route("api/system")]
public class SystemController : ControllerBase
{
private readonly ISystemService _systemService;
private readonly IFileStorageService _fileStorage;
private readonly IUploadTrackingService _uploadTracking;
private readonly ICurrentUserService _currentUser;
private readonly IOperationLogService _operationLog;
private readonly MilliSimDbContext _db;
///
/// 构造函数
///
/// 系统服务
/// 文件存储服务
/// 上传跟踪服务
/// 当前用户服务
/// 操作日志服务
/// 数据库上下文
public SystemController(
ISystemService systemService,
IFileStorageService fileStorage,
IUploadTrackingService uploadTracking,
ICurrentUserService currentUser,
IOperationLogService operationLog,
MilliSimDbContext db)
{
_systemService = systemService;
_fileStorage = fileStorage;
_uploadTracking = uploadTracking;
_currentUser = currentUser;
_operationLog = operationLog;
_db = db;
}
// ===== 系统设置 =====
[HttpGet("settings")]
[Authorize(Policy = "Admin")]
public async Task>> GetSettings()
{
return await _systemService.GetSettingsAsync();
}
[HttpPost("settings")]
[Authorize(Policy = "Admin")]
public async Task UpdateSettings([FromBody] UpdateSettingsRequest request)
{
return await _systemService.UpdateSettingsAsync(request);
}
[HttpGet("settings/{key}")]
[Authorize(Policy = "Admin")]
public async Task> GetSetting(string key)
{
return await _systemService.GetSettingAsync(key);
}
///
/// 获取公开系统设置(无需鉴权):返回平台名、Logo、备案号等,供前台/后台 Layout 显示
///
[HttpGet("public-settings")]
[AllowAnonymous]
public async Task> GetPublicSettings()
{
return await _systemService.GetPublicSettingsAsync();
}
// ===== 关于我们内容管理 =====
///
/// 获取关于我们设置(公开端点,前台 About 页消费)
///
[HttpGet("about")]
[AllowAnonymous]
public async Task> GetAboutSettings()
{
return await _systemService.GetAboutSettingsAsync();
}
///
/// 更新关于我们设置(仅管理员)
///
[HttpPost("about")]
[Authorize(Policy = "Admin")]
public async Task UpdateAboutSettings([FromBody] AboutSettingsDto dto)
{
return await _systemService.UpdateAboutSettingsAsync(dto);
}
// ===== 工厂重置 =====
///
/// 恢复出厂设置:清空所有业务数据(保留 admin)+ 删除 COS 资源(保留 backups/)
/// 需要在请求体中输入 ConfirmText = "RESET" 确认
///
[HttpPost("factory-reset")]
[Authorize(Policy = "Admin")]
public async Task> FactoryReset([FromBody] FactoryResetRequest request)
{
return await _systemService.FactoryResetAsync(request);
}
// ===== 操作日志 =====
[HttpGet("logs")]
[Authorize(Policy = "Admin")]
public async Task>> GetLogs([FromQuery] OperationLogQueryRequest request)
{
return await _systemService.GetOperationLogsAsync(request);
}
[HttpPost("logs/delete")]
[Authorize(Policy = "Admin")]
public async Task ClearLogs([FromQuery] DateTime? beforeDate)
{
return await _systemService.ClearLogsAsync(beforeDate);
}
// ===== 数据备份 =====
[HttpGet("backups")]
[Authorize(Policy = "Admin")]
public async Task>> GetBackups()
{
return await _systemService.GetBackupsAsync();
}
[HttpPost("backups")]
[Authorize(Policy = "Admin")]
public async Task> CreateBackup()
{
return await _systemService.CreateBackupAsync();
}
[HttpPost("backups/{id}/delete")]
[Authorize(Policy = "Admin")]
public async Task DeleteBackup(long id)
{
return await _systemService.DeleteBackupAsync(id);
}
[HttpGet("backups/{id}/download")]
[Authorize(Policy = "Admin")]
public async Task DownloadBackup(long id)
{
var result = await _systemService.DownloadBackupAsync(id);
if (result.Code != 200 || result.Data.Stream == null)
return NotFound(new { code = result.Code, message = result.Message });
return File(result.Data.Stream, "application/octet-stream", result.Data.FileName);
}
// ===== 唤醒协议 =====
[HttpGet("wake-protocols")]
public async Task>> GetWakeProtocols()
{
return await _systemService.GetWakeProtocolsAsync();
}
[HttpPost("wake-protocols")]
[Authorize(Policy = "Admin")]
public async Task> CreateWakeProtocol([FromBody] CreateWakeProtocolRequest request)
{
return await _systemService.CreateWakeProtocolAsync(request);
}
[HttpPost("wake-protocols/{id}")]
[Authorize(Policy = "Admin")]
public async Task> UpdateWakeProtocol(long id, [FromBody] CreateWakeProtocolRequest request)
{
return await _systemService.UpdateWakeProtocolAsync(id, request);
}
[HttpPost("wake-protocols/{id}/delete")]
[Authorize(Policy = "Admin")]
public async Task DeleteWakeProtocol(long id)
{
return await _systemService.DeleteWakeProtocolAsync(id);
}
// ===== 本地存储目录测试 =====
///
/// 测试本地存储目录是否可读写(保存存储配置前校验)
///
/// 本地存储根目录(相对或绝对路径)
[HttpPost("test-local-path")]
[Authorize(Policy = "Admin")]
public async Task TestLocalPath([FromQuery] string localBasePath)
{
return await _systemService.TestLocalPathAsync(localBasePath);
}
// ===== 通用文件上传 =====
[HttpPost("upload")]
[Authorize]
[RequestSizeLimit(2147483648)] // 2GB
[RequestFormLimits(MultipartBodyLengthLimit = 2147483648)]
public async Task> Upload([FromForm] IFormFile file, [FromForm] string folder)
{
// 检查文件是否为空
if (file == null || file.Length == 0)
return ApiResponse.Fail("请选择要上传的文件");
// 默认目录
if (string.IsNullOrWhiteSpace(folder))
folder = "common";
// 从数据库读取上传大小限制,并验证文件大小
var limit = await GetUploadLimitAsync(file.FileName);
if (file.Length > limit)
{
var limitMB = limit / 1024.0 / 1024.0;
return ApiResponse.Fail($"文件大小超过限制(最大 {limitMB:F1}MB)");
}
// 上传文件,获取对象键或本地URL
var fileUrl = await _fileStorage.UploadAsync(file, folder);
// 生成可访问的签名URL(COS模式下必须,本地模式直接返回)
var signedUrl = _fileStorage.GetSignedUrl(fileUrl);
// 注册到 PendingUpload 跟踪表(用于垃圾资源清理)
await _uploadTracking.RegisterAsync(fileUrl, folder, _currentUser.IsAuthenticated ? _currentUser.UserId : null);
// 返回对象键(用于存储)和签名URL(用于预览)
return ApiResponse.Success(new UploadResultDto { Url = signedUrl, Key = fileUrl });
}
///
/// 删除未使用的上传文件(仅限 pending 状态,已被实体消费的文件不会被删除)
/// 用于前端取消表单、替换文件时即时清理垃圾资源
///
/// 文件对象键
[HttpPost("upload/delete")]
[Authorize]
public async Task DeleteUpload([FromQuery] string key)
{
if (string.IsNullOrWhiteSpace(key))
return ApiResponse.Fail("文件 key 不能为空");
var deleted = await _uploadTracking.DeletePendingAsync(key);
return ApiResponse.Success(deleted ? "已删除" : "文件不存在或已使用");
}
///
/// 根据文件扩展名从数据库读取上传大小限制
///
/// 文件名
/// 上传大小限制(字节)
private async Task GetUploadLimitAsync(string fileName)
{
// 根据文件扩展名确定限制类型
var ext = Path.GetExtension(fileName).ToLower();
var key = ext switch
{
// 图片类型
".jpg" or ".jpeg" or ".png" or ".gif" or ".svg" or ".webp" => "UploadLimitImage",
// 视频类型
".mp4" or ".webm" or ".avi" or ".mov" or ".mkv" => "UploadLimitVideo",
// 资源包类型
".zip" or ".rar" or ".7z" or ".apk" or ".exe" or ".dmg" => "UploadLimitPackage",
// 文档类型(默认)
_ => "UploadLimitDocument"
};
// 从数据库读取限制值
var setting = await _db.SystemSettings.FirstOrDefaultAsync(s => s.Key == key);
if (setting != null && long.TryParse(setting.Value, out var limit))
return limit;
// 默认限制 20MB
return 20L * 1024 * 1024;
}
// ===== COS → Local 迁移 =====
///
/// 将 COS 中所有文件迁移到本地存储
/// 用于从 COS 切换到 Local 模式后,补齐旧数据对应的本地文件
///
[HttpPost("migrate-cos-to-local")]
[Authorize(Policy = "Admin")]
public async Task MigrateCosToLocal()
{
try
{
var count = await _fileStorage.MigrateCosToLocalAsync();
await _operationLog.LogAsync("系统设置", "迁移",
$"COS 迁移到本地存储完成,共迁移 {count} 个文件");
return ApiResponse.Success($"迁移完成,共下载 {count} 个文件到本地");
}
catch (Exception ex)
{
return ApiResponse.Fail($"迁移失败:{ex.Message}");
}
}
///
/// 迁移已有的 APK/IPA 文件扩展名
/// COS 禁止通过默认域名分发 APK/IPA 文件(返回 DownloadForbidden 403)
/// 将对象键从 xxx.apk 复制为 xxx.apk.bin,删除旧对象,更新数据库
///
[HttpPost("migrate-apk-extensions")]
[Authorize(Policy = "Admin")]
public async Task MigrateApkExtensions()
{
try
{
var count = await _fileStorage.MigrateApkExtensionsAsync();
await _operationLog.LogAsync("系统设置", "迁移",
$"APK/IPA 扩展名迁移完成,共迁移 {count} 个文件");
return ApiResponse.Success($"迁移完成,共处理 {count} 个 APK/IPA 文件");
}
catch (Exception ex)
{
return ApiResponse.Fail($"迁移失败:{ex.Message}");
}
}
}