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}"); } } }