using Dpz.Core.Entity.Base.Image;
using Dpz.Core.Infrastructure.Imaging;
using Dpz.Core.Public.ViewModel.Request;
using Dpz.Core.Public.ViewModel.Response;
namespace Dpz.Core.WebApi.Controllers;
/// <summary>
/// 图片管理
/// </summary>
[ApiController, Route("api/[controller]")]
public class PictureController(
IPictureRecordService pictureRecordService,
IObjectStorageOperation objectStorageService,
ILogger<PictureController> logger,
IHttpCurrentUserService currentUserService,
IImageFormatDetector imageFormatDetector
) : ControllerBase
{
/// <summary>
/// 图片列表
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType<List<PictureRecordResponse>>(StatusCodes.Status200OK)]
public async Task<IActionResult> GetPictures([FromQuery] PictureQueryParameterDto parameter)
{
var list = await pictureRecordService.GetPagesAsync(
parameter.Tag == null ? null : [parameter.Tag],
parameter.Description,
parameter.PageIndex,
parameter.PageSize
);
list.AddPaginationMetadata(Response.Headers);
return Ok(list);
}
/// <summary>
/// 上传图片
/// </summary>
/// <returns></returns>
[HttpPost]
[Authorize(Policy = "System")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> CreatePicture([FromForm] PictureCreateDto model)
{
if (model.Image.Length <= 0 || !model.Image.ContentType.Contains("image"))
{
return BadRequest("please select image to upload");
}
model.Tags = model.Tags.Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).ToList();
var userInfo = await currentUserService.GetRequiredUserInfoAsync();
using var memoryStream = new MemoryStream();
await model.Image.CopyToAsync(memoryStream);
var bytes = memoryStream.ToArray();
var path = new[] { "images", "album", DateTime.Now.ToString("yyyy-MM") };
var filename = $"{ObjectId.GenerateNewId()}.{GetImageFormat(bytes) ?? "jpg"}";
var result = await objectStorageService.UploadAsync(
model.Image.OpenReadStream(),
path,
filename
);
if (result is not ImageMetadata imageMetadata)
{
return BadRequest("image metadata not found after upload");
}
var createRequest = new CreatePictureRecordRequest
{
Creator = userInfo,
UploadTime = DateTime.Now,
Tags = model.Tags,
Description = model.Description,
Category = PictureCategory.Album,
AccessUrl = imageMetadata.Url,
Length = imageMetadata.Size,
ObjectStorageUploadTime = DateTime.Now,
Md5 = imageMetadata.Hash ?? CalculateMd5(bytes),
Width = imageMetadata.Width,
Height = imageMetadata.Height,
};
await pictureRecordService.CreateRecordAsync(createRequest);
return NoContent();
}
[NonAction]
private static string CalculateMd5(byte[] bytes)
{
using var md5 = MD5.Create();
var hash = md5.ComputeHash(bytes);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
[NonAction]
private string? GetImageFormat(byte[] bytes)
{
try
{
var info = imageFormatDetector.Detect(bytes);
return info.IsKnown ? info.DefaultExtension : null;
}
catch (Exception e)
{
logger.LogError(e, "get image format fail");
return null;
}
}
/// <summary>
/// 修改图像、图像信息
/// </summary>
/// <returns></returns>
[HttpPatch]
[Authorize(Policy = "System")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> EditPicture([FromBody] PictureEditDto model)
{
model.Tags = model.Tags.Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).ToList();
var recordChange = new EditPictureRecordRequest
{
Category = PictureCategory.Album,
Description = model.Description,
Tags = model.Tags,
Id = model.Id,
};
await pictureRecordService.ChangeInformationAsync(recordChange);
return NoContent();
}
/// <summary>
/// 获取图片信息
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType<PictureRecordResponse>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetPicture(string id)
{
var picture = await pictureRecordService.GetPictureRecordAsync(id);
if (picture == null)
{
return NotFound();
}
return Ok(picture);
}
/// <summary>
/// 删除图像
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete("{id}")]
[Authorize(Policy = "System")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(string id)
{
var pictureRecord = await pictureRecordService.GetPictureRecordAsync(id);
if (pictureRecord == null)
{
return NotFound();
}
await pictureRecordService.DeleteAsync(id);
await objectStorageService.DeleteAsync(pictureRecord.AccessUrl);
return NoContent();
}
/// <summary>
/// 获取所有标签
/// </summary>
/// <returns></returns>
[HttpGet("tags")]
[ProducesResponseType<List<string>>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> GetPictureTags()
{
var tags = await pictureRecordService.GetTagsAsync();
return Ok(tags);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个基于 ASP.NET Core 的图片管理 WebAPI 控制器,提供了完整的图片 CRUD(增删改查)功能。
主要功能模块
1. 依赖注入的服务
控制器通过主构造函数注入了以下服务:
IPictureRecordService: 图片记录数据服务IObjectStorageOperation: 对象存储服务(用于上传/删除图片文件)ILogger: 日志服务IHttpCurrentUserService: 当前用户信息服务IImageFormatDetector: 图片格式检测服务
2. API 端点详解
GET /api/picture - 获取图片列表
- 支持分页查询
- 可按标签(Tag)、描述(Description)筛选
- 返回分页元数据到响应头
POST /api/picture - 上传图片
- 🔒 需要 "System" 权限认证
- 验证文件类型必须是图片
- 处理流程:
- 清理和验证标签数据
- 读取图片文件到内存流
- 生成唯一文件名(使用 ObjectId)
- 上传到对象存储(按年月组织目录结构)
- 提取图片元数据(宽高、大小、Hash等)
- 创建数据库记录
PATCH /api/picture - 修改图片信息
- 🔒 需要 "System" 权限认证
- 只能修改描述和标签,不能修改图片本身
GET /api/picture/{id} - 获取单张图片信息
- 根据 ID 查询图片详情
- 不存在返回 404
DELETE /api/picture/{id} - 删除图片
- 🔒 需要 "System" 权限认证
- 同时删除:
- 数据库记录
- 对象存储中的文件
GET /api/picture/tags - 获取所有标签
- 返回系统中所有已使用的图片标签列表
3. 辅助方法
CalculateMd5(byte[] bytes)
- 计算文件的 MD5 哈希值
- 用于文件去重和完整性校验
GetImageFormat(byte[] bytes)
- 检测图片格式(jpg、png等)
- 异常处理后返回 null(防止格式检测失败)
技术特点
- 安全性: 使用
[Authorize]特性保护敏感操作 - 文档化: 使用 XML 注释和
ProducesResponseType生成 Swagger 文档 - 错误处理: 完善的验证和错误响应(400/401/404)
- 存储组织: 按时间(年-月)组织文件存储路径
- 元数据管理: 记录图片的完整元数据(尺寸、Hash、上传时间等)
- 标签系统: 支持多标签分类和查询
数据流向
客户端 → 控制器 → 业务服务层 → 对象存储/数据库
↓
返回响应(JSON/NoContent)
这是一个设计良好、职责清晰的 RESTful API 控制器实现。
AI 正在分析代码…
评论加载中...