mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-05-24 09:39:58 +08:00
Initial commit
This commit is contained in:
195
Controllers/AlbumController.cs
Normal file
195
Controllers/AlbumController.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Foxel.Models;
|
||||
using Foxel.Models.Request.Album;
|
||||
using Foxel.Models.Response.Album;
|
||||
using Foxel.Services.Interface;
|
||||
|
||||
namespace Foxel.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[Route("api/album")]
|
||||
public class AlbumController(IAlbumService albumService) : BaseApiController
|
||||
{
|
||||
[HttpGet("get_albums")]
|
||||
public async Task<ActionResult<PaginatedResult<AlbumResponse>>> GetAlbums(
|
||||
[FromQuery] int page = 1, [FromQuery] int pageSize = 10)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
try
|
||||
{
|
||||
var albums = await albumService.GetAlbumsAsync(page, pageSize, userId);
|
||||
return PaginatedSuccess(albums.Data, albums.TotalCount, albums.Page, albums.PageSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return PaginatedError<AlbumResponse>($"获取相册失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("get_album/{id}")]
|
||||
public async Task<ActionResult<BaseResult<AlbumResponse>>> GetAlbumById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var album = await albumService.GetAlbumByIdAsync(id);
|
||||
return Success(album, "相册获取成功");
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return Error<AlbumResponse>("找不到指定相册", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<AlbumResponse>($"获取相册失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("create_album")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<BaseResult<AlbumResponse>>> CreateAlbum([FromBody] CreateAlbumRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
return Error<AlbumResponse>("无法识别用户信息", 401);
|
||||
|
||||
var album = await albumService.CreateAlbumAsync(request.Name, request.Description, userId.Value);
|
||||
return Success(album, "相册创建成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<AlbumResponse>($"创建相册失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("update_album")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<BaseResult<AlbumResponse>>> UpdateAlbum([FromBody] UpdateAlbumRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentUserId = GetCurrentUserId();
|
||||
if (currentUserId == null)
|
||||
return Error<AlbumResponse>("无法识别用户信息", 401);
|
||||
|
||||
var album = await albumService.UpdateAlbumAsync(request.Id, request.Name, request.Description,
|
||||
currentUserId);
|
||||
return Success(album, "相册更新成功");
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Error<AlbumResponse>("您没有权限更新此相册", 403);
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return Error<AlbumResponse>("找不到要更新的相册", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<AlbumResponse>($"更新相册失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("delete_album")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<BaseResult<bool>>> DeleteAlbum([FromBody] int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentUserId = GetCurrentUserId();
|
||||
if (currentUserId == null)
|
||||
return Error<bool>("无法识别用户信息", 401);
|
||||
|
||||
var result = await albumService.DeleteAlbumAsync(id);
|
||||
return Success(result, "相册删除成功");
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Error<bool>("您没有权限删除此相册", 403);
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return Error<bool>("找不到要删除的相册", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<bool>($"删除相册失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("add_pictures")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<BaseResult<bool>>> AddPicturesToAlbum([FromBody] AlbumPicturesRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (request.PictureIds.Count == 0)
|
||||
{
|
||||
return Error<bool>("未提供图片ID");
|
||||
}
|
||||
|
||||
var result = await albumService.AddPicturesToAlbumAsync(request.AlbumId, request.PictureIds);
|
||||
return Success(result, $"已将 {request.PictureIds.Count} 张图片添加到相册");
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Error<bool>("您没有权限修改此相册", 403);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return Error<bool>($"添加失败: {ex.Message}", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<bool>($"添加图片到相册失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("add_picture")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<BaseResult<bool>>> AddPictureToAlbum([FromBody] AlbumPictureRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await albumService.AddPictureToAlbumAsync(request.AlbumId, request.PictureId);
|
||||
return Success(result, "图片已添加到相册");
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Error<bool>("您没有权限修改此相册", 403);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return Error<bool>($"添加失败: {ex.Message}", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<bool>($"添加图片到相册失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("remove_picture")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<BaseResult<bool>>> RemovePictureFromAlbum([FromBody] AlbumPictureRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await albumService.RemovePictureFromAlbumAsync(request.AlbumId, request.PictureId);
|
||||
return Success(result, "图片已从相册移除");
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Error<bool>("您没有权限修改此相册", 403);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return Error<bool>($"移除失败: {ex.Message}", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<bool>($"从相册移除图片失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
173
Controllers/AuthController.cs
Normal file
173
Controllers/AuthController.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System.Security.Claims;
|
||||
using Foxel.Models;
|
||||
using Foxel.Services.Interface;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Foxel.Models.Request.Auth;
|
||||
using Foxel.Models.Response.Auth;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
|
||||
namespace Foxel.Controllers;
|
||||
|
||||
[Route("api/auth")]
|
||||
public class AuthController(IUserService userService) : BaseApiController
|
||||
{
|
||||
[HttpPost("register")]
|
||||
public async Task<ActionResult<BaseResult<AuthResponse>>> Register([FromBody] RegisterRequest request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return Error<AuthResponse>("请求数据无效");
|
||||
}
|
||||
|
||||
var (success, message, user) = await userService.RegisterUserAsync(request);
|
||||
if (!success)
|
||||
{
|
||||
return Error<AuthResponse>(message);
|
||||
}
|
||||
|
||||
var token = await userService.GenerateJwtTokenAsync(user!);
|
||||
var response = new AuthResponse
|
||||
{
|
||||
Token = token,
|
||||
User = new UserProfile
|
||||
{
|
||||
Id = user!.Id,
|
||||
UserName = user.UserName,
|
||||
Email = user.Email,
|
||||
RoleName = user.Role?.Name
|
||||
}
|
||||
};
|
||||
|
||||
return Success(response, "注册成功");
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<ActionResult<BaseResult<AuthResponse>>> Login([FromBody] LoginRequest request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return Error<AuthResponse>("请求数据无效");
|
||||
}
|
||||
|
||||
var (success, message, user) = await userService.AuthenticateUserAsync(request);
|
||||
if (!success)
|
||||
{
|
||||
return Error<AuthResponse>(message, 401);
|
||||
}
|
||||
|
||||
var token = await userService.GenerateJwtTokenAsync(user!);
|
||||
var response = new AuthResponse
|
||||
{
|
||||
Token = token,
|
||||
User = new UserProfile
|
||||
{
|
||||
Id = user!.Id,
|
||||
UserName = user.UserName,
|
||||
Email = user.Email,
|
||||
RoleName = user.Role?.Name
|
||||
}
|
||||
};
|
||||
|
||||
return Success(response, "登录成功");
|
||||
}
|
||||
|
||||
[HttpGet("get_current_user")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<BaseResult<UserProfile>>> GetCurrentUser()
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Error<UserProfile>("用户ID未找到");
|
||||
}
|
||||
|
||||
var user = await userService.GetUserByIdAsync(userId.Value);
|
||||
if (user == null)
|
||||
{
|
||||
return Error<UserProfile>("未找到用户信息", 404);
|
||||
}
|
||||
|
||||
var profile = new UserProfile
|
||||
{
|
||||
Id = userId.Value,
|
||||
Email = user.Email,
|
||||
UserName = user.UserName,
|
||||
RoleName = user.Role?.Name
|
||||
};
|
||||
|
||||
return Success(profile);
|
||||
}
|
||||
|
||||
[HttpGet("github/login")]
|
||||
public IActionResult GitHubLogin(string returnUrl = "/")
|
||||
{
|
||||
try
|
||||
{
|
||||
var properties = new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = Url.Action("GitHubCallback", new { returnUrl }),
|
||||
Items = { { "returnUrl", returnUrl } },
|
||||
// 添加超时设置
|
||||
AllowRefresh = true,
|
||||
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
|
||||
IsPersistent = false
|
||||
};
|
||||
return Challenge(properties, "GitHub");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"GitHub登录异常: {ex}");
|
||||
return Redirect("/login?error=github_login_error");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("github/callback")]
|
||||
public async Task<IActionResult> GitHubCallback(string returnUrl = "/")
|
||||
{
|
||||
try
|
||||
{
|
||||
var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
if (!authenticateResult.Succeeded)
|
||||
{
|
||||
Console.WriteLine("GitHub认证失败: 无法获取认证结果");
|
||||
return Redirect("/login?error=github_auth_failed");
|
||||
}
|
||||
// 获取GitHub用户信息
|
||||
var githubId = authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
var githubLogin = authenticateResult.Principal.FindFirst("urn:github:login")?.Value;
|
||||
var githubEmail = authenticateResult.Principal.FindFirst(ClaimTypes.Email)?.Value;
|
||||
|
||||
Console.WriteLine($"GitHub用户信息: ID={githubId}, Login={githubLogin}, Email={githubEmail}");
|
||||
|
||||
if (string.IsNullOrEmpty(githubId) || string.IsNullOrEmpty(githubLogin))
|
||||
{
|
||||
return Redirect("/login?error=github_missing_info");
|
||||
}
|
||||
|
||||
// 登出Cookie认证会话
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
// 查找或创建用户
|
||||
var (success, message, user) = await userService.FindOrCreateGitHubUserAsync(
|
||||
githubId, githubLogin, githubEmail);
|
||||
|
||||
if (!success || user == null)
|
||||
{
|
||||
return Redirect($"/login?error={Uri.EscapeDataString(message)}");
|
||||
}
|
||||
|
||||
// 生成JWT令牌
|
||||
var token = await userService.GenerateJwtTokenAsync(user);
|
||||
|
||||
// 重定向回前端,携带token参数
|
||||
return Redirect($"{returnUrl}?token={Uri.EscapeDataString(token)}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"GitHub回调处理异常: {ex}");
|
||||
return Redirect("/login?error=github_callback_error");
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Controllers/BackgroundTaskController.cs
Normal file
53
Controllers/BackgroundTaskController.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Foxel.Models;
|
||||
using Foxel.Services.Interface;
|
||||
|
||||
namespace Foxel.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[Route("api/background-tasks")]
|
||||
public class BackgroundTaskController : BaseApiController
|
||||
{
|
||||
private readonly IBackgroundTaskQueue _backgroundTaskQueue;
|
||||
|
||||
public BackgroundTaskController(IBackgroundTaskQueue backgroundTaskQueue)
|
||||
{
|
||||
_backgroundTaskQueue = backgroundTaskQueue;
|
||||
}
|
||||
|
||||
[HttpGet("user-tasks")]
|
||||
public async Task<ActionResult<BaseResult<List<PictureProcessingStatus>>>> GetUserTasks()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
return Error<List<PictureProcessingStatus>>("无法识别用户信息", 401);
|
||||
|
||||
var tasks = await _backgroundTaskQueue.GetUserTasksStatusAsync(userId.Value);
|
||||
return Success(tasks, "成功获取任务列表");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<List<PictureProcessingStatus>>($"获取任务状态失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("picture-status/{pictureId}")]
|
||||
public async Task<ActionResult<BaseResult<PictureProcessingStatus>>> GetPictureStatus(int pictureId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var status = await _backgroundTaskQueue.GetPictureProcessingStatusAsync(pictureId);
|
||||
if (status == null)
|
||||
return Error<PictureProcessingStatus>("找不到该图片的处理状态", 404);
|
||||
|
||||
return Success(status, "成功获取图片处理状态");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<PictureProcessingStatus>($"获取图片处理状态失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Controllers/BaseApiController.cs
Normal file
81
Controllers/BaseApiController.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Foxel.Models;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Foxel.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public abstract class BaseApiController : ControllerBase
|
||||
{
|
||||
protected int? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
return userIdClaim != null ? int.Parse(userIdClaim) : null;
|
||||
}
|
||||
|
||||
protected ActionResult<BaseResult<T>> Success<T>(T data, string message = "操作成功", int statusCode = 200)
|
||||
{
|
||||
return Ok(new BaseResult<T>
|
||||
{
|
||||
Success = true,
|
||||
Message = message,
|
||||
Data = data,
|
||||
StatusCode = statusCode
|
||||
});
|
||||
}
|
||||
|
||||
protected ActionResult<BaseResult<T>> Success<T>(string message = "操作成功", int statusCode = 200)
|
||||
{
|
||||
return Ok(new BaseResult<T>
|
||||
{
|
||||
Success = true,
|
||||
Message = message,
|
||||
StatusCode = statusCode
|
||||
});
|
||||
}
|
||||
|
||||
protected ActionResult<BaseResult<T>> Error<T>(string message, int statusCode = 400)
|
||||
{
|
||||
return StatusCode(statusCode, new BaseResult<T>
|
||||
{
|
||||
Success = false,
|
||||
Message = message,
|
||||
StatusCode = statusCode
|
||||
});
|
||||
}
|
||||
|
||||
protected ActionResult<PaginatedResult<T>> PaginatedSuccess<T>(
|
||||
List<T>? data,
|
||||
int totalCount,
|
||||
int page,
|
||||
int pageSize,
|
||||
string message = "获取成功")
|
||||
{
|
||||
return Ok(new PaginatedResult<T>
|
||||
{
|
||||
Success = true,
|
||||
Message = message,
|
||||
Data = data,
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize,
|
||||
StatusCode = 200
|
||||
});
|
||||
}
|
||||
|
||||
protected ActionResult<PaginatedResult<T>> PaginatedError<T>(string message, int statusCode = 400)
|
||||
{
|
||||
return StatusCode(statusCode, new PaginatedResult<T>
|
||||
{
|
||||
Success = false,
|
||||
Message = message,
|
||||
Data = new List<T>(),
|
||||
TotalCount = 0,
|
||||
Page = 0,
|
||||
PageSize = 0,
|
||||
StatusCode = statusCode
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
90
Controllers/ConfigController.cs
Normal file
90
Controllers/ConfigController.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Foxel.Models;
|
||||
using Foxel.Models.DataBase;
|
||||
using Foxel.Models.Request.Config;
|
||||
using Foxel.Services.Interface;
|
||||
|
||||
namespace Foxel.Controllers;
|
||||
|
||||
[Authorize(Roles = "Administrator")]
|
||||
[Route("api/config")]
|
||||
public class ConfigController(IConfigService configService) : BaseApiController
|
||||
{
|
||||
[HttpGet("get_configs")]
|
||||
public async Task<ActionResult<BaseResult<List<Config>>>> GetConfigs()
|
||||
{
|
||||
try
|
||||
{
|
||||
var configs = await configService.GetAllConfigsAsync();
|
||||
return Success(configs, "获取所有配置成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<List<Config>>($"获取配置失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("get_config/{key}")]
|
||||
public async Task<ActionResult<BaseResult<Config>>> GetConfig(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
return Error<Config>("配置键不能为空");
|
||||
|
||||
var config = await configService.GetConfigAsync(key);
|
||||
|
||||
if (config == null)
|
||||
return Error<Config>($"找不到键为 '{key}' 的配置", 404);
|
||||
|
||||
return Success(config, "获取配置成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<Config>($"获取配置失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("set_config")]
|
||||
public async Task<ActionResult<BaseResult<Config>>> SetConfig([FromBody] SetConfigRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Key))
|
||||
return Error<Config>("配置键不能为空");
|
||||
|
||||
var config = await configService.SetConfigAsync(
|
||||
request.Key.Trim(),
|
||||
request.Value ?? string.Empty,
|
||||
request.Description);
|
||||
|
||||
return Success(config, "配置设置成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<Config>($"设置配置失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("delete_config")]
|
||||
public async Task<ActionResult<BaseResult<bool>>> DeleteConfig([FromBody] string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
return Error<bool>("配置键不能为空");
|
||||
|
||||
var result = await configService.DeleteConfigAsync(key);
|
||||
|
||||
if (!result)
|
||||
return Error<bool>($"找不到键为 '{key}' 的配置", 404);
|
||||
|
||||
return Success(true, $"成功删除键为 '{key}' 的配置");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<bool>($"删除配置失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
322
Controllers/PictureController.cs
Normal file
322
Controllers/PictureController.cs
Normal file
@@ -0,0 +1,322 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Foxel.Models;
|
||||
using Foxel.Models.DataBase;
|
||||
using Foxel.Models.Request.Picture;
|
||||
using Foxel.Models.Response.Picture;
|
||||
using Foxel.Services.Interface;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Foxel.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[Route("api/picture")]
|
||||
public class PictureController(IPictureService pictureService,IConfigService configService) : BaseApiController
|
||||
{
|
||||
[HttpGet("get_pictures")]
|
||||
public async Task<ActionResult<PaginatedResult<PictureResponse>>> GetPictures(
|
||||
[FromQuery] FilteredPicturesRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<string>? tagsList = null;
|
||||
if (!string.IsNullOrWhiteSpace(request.Tags))
|
||||
{
|
||||
tagsList = request.Tags.Split(',')
|
||||
.Select(t => t.Trim())
|
||||
.Where(t => !string.IsNullOrWhiteSpace(t))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
var result = await pictureService.GetPicturesAsync(
|
||||
request.Page,
|
||||
request.PageSize,
|
||||
request.SearchQuery,
|
||||
tagsList,
|
||||
request.StartDate,
|
||||
request.EndDate,
|
||||
currentUserId,
|
||||
request.SortBy,
|
||||
request.OnlyWithGps,
|
||||
request.UseVectorSearch,
|
||||
request.SimilarityThreshold,
|
||||
request.ExcludeAlbumId,
|
||||
request.AlbumId,
|
||||
request.OnlyFavorites,
|
||||
request.OwnerId,
|
||||
request.IncludeAllPublic
|
||||
);
|
||||
|
||||
return PaginatedSuccess(result.Data, result.TotalCount, result.Page, result.PageSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return PaginatedError<PictureResponse>($"获取图片失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("upload_picture")]
|
||||
[Consumes("multipart/form-data")]
|
||||
public async Task<ActionResult<BaseResult<PictureResponse>>> UploadPicture(
|
||||
[FromForm] UploadPictureRequest request)
|
||||
{
|
||||
if (request.File.Length == 0)
|
||||
return Error<PictureResponse>("没有上传文件");
|
||||
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
await using var stream = request.File.OpenReadStream();
|
||||
var result = await pictureService.UploadPictureAsync(
|
||||
request.File.FileName,
|
||||
stream,
|
||||
request.File.ContentType,
|
||||
userId,
|
||||
(PermissionType)request.Permission!,
|
||||
request.AlbumId
|
||||
);
|
||||
|
||||
var picture = result.Picture;
|
||||
|
||||
return Success(picture, "图片上传成功");
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return Error<PictureResponse>(ex.Message, 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<PictureResponse>($"上传图片失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("delete_pictures")]
|
||||
public async Task<ActionResult<BaseResult<object>>> DeleteMultiplePictures(
|
||||
[FromBody] DeleteMultiplePicturesRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentUserId = GetCurrentUserId();
|
||||
if (currentUserId == null)
|
||||
return Error<object>("无法识别用户信息");
|
||||
|
||||
if (!request.PictureIds.Any())
|
||||
return Error<object>("未提供要删除的图片ID");
|
||||
|
||||
// 获取删除结果
|
||||
var results = await pictureService.DeleteMultiplePicturesAsync(request.PictureIds);
|
||||
|
||||
// 权限验证和处理结果
|
||||
var unauthorizedIds = new List<int>();
|
||||
var notFoundIds = new List<int>();
|
||||
var successIds = new List<int>();
|
||||
var errors = new Dictionary<int, string>();
|
||||
|
||||
foreach (var (pictureId, (success, errorMessage, ownerId)) in results)
|
||||
{
|
||||
// 检查权限
|
||||
if (ownerId.HasValue && ownerId.Value != currentUserId.Value)
|
||||
{
|
||||
unauthorizedIds.Add(pictureId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
notFoundIds.Add(pictureId);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
errors[pictureId] = errorMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
successIds.Add(pictureId);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有未授权或其他错误,返回适当的响应
|
||||
if (unauthorizedIds.Any() || notFoundIds.Any() || errors.Any())
|
||||
{
|
||||
var messages = new List<string>();
|
||||
|
||||
if (unauthorizedIds.Any())
|
||||
messages.Add($"无权删除以下图片: {string.Join(", ", unauthorizedIds)}");
|
||||
|
||||
if (notFoundIds.Any())
|
||||
messages.Add($"找不到以下图片: {string.Join(", ", notFoundIds)}");
|
||||
|
||||
if (errors.Any())
|
||||
messages.Add(string.Join("; ", errors.Select(e => $"图片ID {e.Key}: {e.Value}")));
|
||||
|
||||
return StatusCode(207, new BaseResult<object>
|
||||
{
|
||||
Success = successIds.Any(),
|
||||
Message = string.Join("; ", messages),
|
||||
StatusCode = 207,
|
||||
Data = new
|
||||
{
|
||||
SuccessCount = successIds.Count,
|
||||
SuccessIds = successIds,
|
||||
UnauthorizedIds = unauthorizedIds,
|
||||
NotFoundIds = notFoundIds,
|
||||
Errors = errors
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Success<object>($"成功删除 {successIds.Count} 张图片");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<object>($"删除图片失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("update_picture")]
|
||||
public async Task<ActionResult<BaseResult<PictureResponse>>> UpdatePicture(
|
||||
[FromBody] UpdatePictureRequestWithId request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentUserId = GetCurrentUserId();
|
||||
if (currentUserId == null)
|
||||
return Error<PictureResponse>("无法识别用户信息");
|
||||
|
||||
var (picture, ownerId) = await pictureService.UpdatePictureAsync(
|
||||
request.Id, request.Name, request.Description, request.Tags);
|
||||
|
||||
// 权限验证
|
||||
if (ownerId.HasValue && ownerId.Value != currentUserId.Value)
|
||||
{
|
||||
return Error<PictureResponse>("您没有权限更新此图片", 403);
|
||||
}
|
||||
|
||||
return Success(picture, "图片信息已成功更新");
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return Error<PictureResponse>("找不到要更新的图片", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<PictureResponse>($"更新图片失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("favorite")]
|
||||
public async Task<ActionResult<BaseResult<bool>>> FavoritePicture([FromBody] FavoriteRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
return Error<bool>("无法识别用户信息", 401);
|
||||
|
||||
var result = await pictureService.FavoritePictureAsync(request.PictureId, userId.Value);
|
||||
return Success(result, "图片收藏成功");
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return Error<bool>("找不到指定图片", 404);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Error<bool>(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<bool>($"收藏图片失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("unfavorite")]
|
||||
public async Task<ActionResult<BaseResult<bool>>> UnfavoritePicture([FromBody] FavoriteRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
return Error<bool>("无法识别用户信息", 401);
|
||||
|
||||
var result = await pictureService.UnfavoritePictureAsync(request.PictureId, userId.Value);
|
||||
return Success(result, "已取消收藏");
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return Error<bool>("找不到指定图片或收藏记录", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<bool>($"取消收藏失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("get_telegram_file")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> GetTelegramFile([FromQuery] string fileId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var botToken = configService["Storage:TelegramStorageBotToken"];
|
||||
if (string.IsNullOrEmpty(botToken))
|
||||
return BadRequest("Telegram Bot Token 未配置");
|
||||
|
||||
using var httpClient = new HttpClient();
|
||||
var getFileUrl = $"https://api.telegram.org/bot{botToken}/getFile?file_id={fileId}";
|
||||
var getFileResponse = await httpClient.GetAsync(getFileUrl);
|
||||
|
||||
if (!getFileResponse.IsSuccessStatusCode)
|
||||
{
|
||||
var errorContent = await getFileResponse.Content.ReadAsStringAsync();
|
||||
return StatusCode((int)getFileResponse.StatusCode, $"获取文件路径失败: {errorContent}");
|
||||
}
|
||||
|
||||
var getFileContent = await getFileResponse.Content.ReadAsStringAsync();
|
||||
var getFileResult = JsonSerializer.Deserialize<TelegramGetFileResponse>(getFileContent);
|
||||
if (getFileResult == null || !getFileResult.Ok || string.IsNullOrEmpty(getFileResult.Result?.FilePath))
|
||||
{
|
||||
return BadRequest("无法解析 Telegram 文件路径");
|
||||
}
|
||||
|
||||
var filePath = getFileResult.Result.FilePath;
|
||||
var fileUrl = $"https://api.telegram.org/file/bot{botToken}/{filePath}";
|
||||
|
||||
var fileResponse = await httpClient.GetAsync(fileUrl);
|
||||
if (!fileResponse.IsSuccessStatusCode)
|
||||
{
|
||||
return StatusCode((int)fileResponse.StatusCode, "下载文件失败");
|
||||
}
|
||||
|
||||
var contentType = fileResponse.Content.Headers.ContentType?.ToString() ?? "application/octet-stream";
|
||||
var fileStream = await fileResponse.Content.ReadAsStreamAsync();
|
||||
|
||||
return File(fileStream, contentType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"代理获取文件失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 用于解析 Telegram getFile API 响应的辅助类
|
||||
private class TelegramGetFileResponse
|
||||
{
|
||||
[JsonPropertyName("ok")]
|
||||
public bool Ok { get; set; }
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
public TelegramFileResult? Result { get; set; }
|
||||
}
|
||||
|
||||
private class TelegramFileResult
|
||||
{
|
||||
[JsonPropertyName("file_path")]
|
||||
public string? FilePath { get; set; }
|
||||
}
|
||||
}
|
||||
126
Controllers/TagController.cs
Normal file
126
Controllers/TagController.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Foxel.Models;
|
||||
using Foxel.Models.Request.Tag;
|
||||
using Foxel.Models.Response.Tag;
|
||||
using Foxel.Services.Interface;
|
||||
|
||||
namespace Foxel.Controllers;
|
||||
|
||||
[Route("api/tag")]
|
||||
public class TagController(ITagService tagService) : BaseApiController
|
||||
{
|
||||
[HttpGet("get_tags")]
|
||||
public async Task<ActionResult<PaginatedResult<TagResponse>>> GetFilteredTags([FromQuery] FilteredTagsRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await tagService.GetFilteredTagsAsync(
|
||||
request.Page,
|
||||
request.PageSize,
|
||||
request.SearchQuery,
|
||||
request.SortBy,
|
||||
request.SortDirection
|
||||
);
|
||||
|
||||
return PaginatedSuccess(result.Data, result.TotalCount, result.Page, result.PageSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return PaginatedError<TagResponse>($"获取标签失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加一个简化的获取所有标签的方法,内部使用GetFilteredTagsAsync
|
||||
[HttpGet("all")]
|
||||
public async Task<ActionResult<BaseResult<List<TagResponse>>>> GetAllTags()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用过滤方法但获取更多数据
|
||||
var result = await tagService.GetFilteredTagsAsync(
|
||||
page: 1,
|
||||
pageSize: 1000, // 设置一个较大值获取所有标签
|
||||
sortBy: "pictureCount",
|
||||
sortDirection: "desc"
|
||||
);
|
||||
|
||||
return Success(result.Data, "标签获取成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<List<TagResponse>>($"获取标签失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<BaseResult<TagResponse>>> GetTagById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tag = await tagService.GetTagByIdAsync(id);
|
||||
return Success(tag, "标签获取成功");
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return Error<TagResponse>("找不到指定标签", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<TagResponse>($"获取标签失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("create_tag")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<BaseResult<TagResponse>>> CreateTag([FromBody] CreateTagRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tag = await tagService.CreateTagAsync(request.Name, request.Description);
|
||||
return Success(tag, "标签创建成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<TagResponse>($"创建标签失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("update_tag")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<BaseResult<TagResponse>>> UpdateTag([FromBody] UpdateTagRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tag = await tagService.UpdateTagAsync(request.Id, request.Name, request.Description);
|
||||
return Success(tag, "标签更新成功");
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return Error<TagResponse>("找不到要更新的标签", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<TagResponse>($"更新标签失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("delete_tag")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<BaseResult<bool>>> DeleteTag([FromBody] int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await tagService.DeleteTagAsync(id);
|
||||
return Success(result, "标签删除成功");
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return Error<bool>("找不到要删除的标签", 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Error<bool>($"删除标签失败: {ex.Message}", 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user