feat: 新增公告管理
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||
|
||||
/// <summary>
|
||||
/// 创建公告输入
|
||||
/// </summary>
|
||||
public class AnnouncementCreateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "标题不能为空")]
|
||||
[StringLength(200, ErrorMessage = "标题不能超过200个字符")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 内容列表
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "内容不能为空")]
|
||||
[MinLength(1, ErrorMessage = "至少需要一条内容")]
|
||||
public List<string> Content { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[StringLength(500, ErrorMessage = "备注不能超过500个字符")]
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片url
|
||||
/// </summary>
|
||||
[StringLength(500, ErrorMessage = "图片URL不能超过500个字符")]
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "开始时间不能为空")]
|
||||
public DateTime StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 活动结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 公告类型
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "公告类型不能为空")]
|
||||
public AnnouncementTypeEnum Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 跳转链接
|
||||
/// </summary>
|
||||
[StringLength(500, ErrorMessage = "跳转链接不能超过500个字符")]
|
||||
public string? Url { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||
|
||||
/// <summary>
|
||||
/// 公告 DTO(后台管理使用)
|
||||
/// </summary>
|
||||
public class AnnouncementDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 公告ID
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 内容列表
|
||||
/// </summary>
|
||||
public List<string> Content { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片url
|
||||
/// </summary>
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间
|
||||
/// </summary>
|
||||
public DateTime StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 活动结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 公告类型
|
||||
/// </summary>
|
||||
public AnnouncementTypeEnum Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 跳转链接
|
||||
/// </summary>
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreationTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||
|
||||
/// <summary>
|
||||
/// 获取公告列表输入
|
||||
/// </summary>
|
||||
public class AnnouncementGetListInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 搜索关键字
|
||||
/// </summary>
|
||||
public string? SearchKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 跳过数量
|
||||
/// </summary>
|
||||
public int SkipCount { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 最大结果数量
|
||||
/// </summary>
|
||||
public int MaxResultCount { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// 公告类型
|
||||
/// </summary>
|
||||
public AnnouncementTypeEnum? Type { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||
|
||||
/// <summary>
|
||||
/// 更新公告输入
|
||||
/// </summary>
|
||||
public class AnnouncementUpdateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 公告ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "公告ID不能为空")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "标题不能为空")]
|
||||
[StringLength(200, ErrorMessage = "标题不能超过200个字符")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 内容列表
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "内容不能为空")]
|
||||
[MinLength(1, ErrorMessage = "至少需要一条内容")]
|
||||
public List<string> Content { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[StringLength(500, ErrorMessage = "备注不能超过500个字符")]
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片url
|
||||
/// </summary>
|
||||
[StringLength(500, ErrorMessage = "图片URL不能超过500个字符")]
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "开始时间不能为空")]
|
||||
public DateTime StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 活动结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 公告类型
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "公告类型不能为空")]
|
||||
public AnnouncementTypeEnum Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 跳转链接
|
||||
/// </summary>
|
||||
[StringLength(500, ErrorMessage = "跳转链接不能超过500个字符")]
|
||||
public string? Url { get; set; }
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||
@@ -8,8 +9,42 @@ namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||
public interface IAnnouncementService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取公告信息
|
||||
/// 获取公告信息(前端首页使用)
|
||||
/// </summary>
|
||||
/// <returns>公告信息</returns>
|
||||
Task<List<AnnouncementLogDto>> GetAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取公告列表(后台管理使用)
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>分页公告列表</returns>
|
||||
Task<PagedResultDto<AnnouncementDto>> GetListAsync(AnnouncementGetListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取公告
|
||||
/// </summary>
|
||||
/// <param name="id">公告ID</param>
|
||||
/// <returns>公告详情</returns>
|
||||
Task<AnnouncementDto> GetByIdAsync(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// 创建公告
|
||||
/// </summary>
|
||||
/// <param name="input">创建输入</param>
|
||||
/// <returns>创建的公告</returns>
|
||||
Task<AnnouncementDto> CreateAsync(AnnouncementCreateInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新公告
|
||||
/// </summary>
|
||||
/// <param name="input">更新输入</param>
|
||||
/// <returns>更新后的公告</returns>
|
||||
Task<AnnouncementDto> UpdateAsync(AnnouncementUpdateInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除公告
|
||||
/// </summary>
|
||||
/// <param name="id">公告ID</param>
|
||||
Task DeleteAsync(Guid id);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Caching;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||
@@ -31,8 +35,9 @@ public class AnnouncementService : ApplicationService, IAnnouncementService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取公告信息
|
||||
/// 获取公告信息(前端首页使用,允许匿名访问)
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
public async Task<List<AnnouncementLogDto>> GetAsync()
|
||||
{
|
||||
// 使用 GetOrAddAsync 从缓存获取或添加数据,缓存1小时
|
||||
@@ -48,18 +53,124 @@ public class AnnouncementService : ApplicationService, IAnnouncementService
|
||||
return cacheData?.Logs ?? new List<AnnouncementLogDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取公告列表(后台管理使用)
|
||||
/// </summary>
|
||||
[Authorize(Roles = "admin")]
|
||||
[HttpGet("announcement/list")]
|
||||
public async Task<PagedResultDto<AnnouncementDto>> GetListAsync(AnnouncementGetListInput input)
|
||||
{
|
||||
var query = _announcementRepository._DbQueryable
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.SearchKey),
|
||||
x => x.Title.Contains(input.SearchKey!) || (x.Remark != null && x.Remark.Contains(input.SearchKey!)))
|
||||
.WhereIF(input.Type.HasValue, x => x.Type == input.Type!.Value)
|
||||
.OrderByDescending(x => x.StartTime);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.Skip(input.SkipCount)
|
||||
.Take(input.MaxResultCount)
|
||||
.ToListAsync();
|
||||
|
||||
return new PagedResultDto<AnnouncementDto>(
|
||||
totalCount,
|
||||
items.Adapt<List<AnnouncementDto>>()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取公告
|
||||
/// </summary>
|
||||
[Authorize(Roles = "admin")]
|
||||
[HttpGet("{id}")]
|
||||
public async Task<AnnouncementDto> GetByIdAsync(Guid id)
|
||||
{
|
||||
var entity = await _announcementRepository.GetByIdAsync(id);
|
||||
if (entity == null)
|
||||
{
|
||||
throw new Exception("公告不存在");
|
||||
}
|
||||
return entity.Adapt<AnnouncementDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建公告
|
||||
/// </summary>
|
||||
[Authorize(Roles = "admin")]
|
||||
[HttpPost]
|
||||
public async Task<AnnouncementDto> CreateAsync(AnnouncementCreateInput input)
|
||||
{
|
||||
var entity = input.Adapt<AnnouncementAggregateRoot>();
|
||||
await _announcementRepository.InsertAsync(entity);
|
||||
|
||||
// 清除缓存
|
||||
await _announcementCache.RemoveAsync(AnnouncementCacheKey);
|
||||
|
||||
return entity.Adapt<AnnouncementDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新公告
|
||||
/// </summary>
|
||||
[Authorize(Roles = "admin")]
|
||||
[HttpPut]
|
||||
public async Task<AnnouncementDto> UpdateAsync(AnnouncementUpdateInput input)
|
||||
{
|
||||
var entity = await _announcementRepository.GetByIdAsync(input.Id);
|
||||
if (entity == null)
|
||||
{
|
||||
throw new Exception("公告不存在");
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
entity.Title = input.Title;
|
||||
entity.Content = input.Content;
|
||||
entity.Remark = input.Remark;
|
||||
entity.ImageUrl = input.ImageUrl;
|
||||
entity.StartTime = input.StartTime;
|
||||
entity.EndTime = input.EndTime;
|
||||
entity.Type = input.Type;
|
||||
entity.Url = input.Url;
|
||||
|
||||
await _announcementRepository.UpdateAsync(entity);
|
||||
|
||||
// 清除缓存
|
||||
await _announcementCache.RemoveAsync(AnnouncementCacheKey);
|
||||
|
||||
return entity.Adapt<AnnouncementDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除公告
|
||||
/// </summary>
|
||||
[Authorize(Roles = "admin")]
|
||||
[HttpDelete("announcement/{id}")]
|
||||
public async Task DeleteAsync(Guid id)
|
||||
{
|
||||
var entity = await _announcementRepository.GetByIdAsync(id);
|
||||
if (entity == null)
|
||||
{
|
||||
throw new Exception("公告不存在");
|
||||
}
|
||||
|
||||
await _announcementRepository.DeleteAsync(entity);
|
||||
|
||||
// 清除缓存
|
||||
await _announcementCache.RemoveAsync(AnnouncementCacheKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库加载公告数据
|
||||
/// </summary>
|
||||
private async Task<AnnouncementCacheDto> LoadAnnouncementDataAsync()
|
||||
{
|
||||
// 1️⃣ 一次性查出全部公告(不排序)
|
||||
// 一次性查出全部公告(不排序)
|
||||
var logs = await _announcementRepository._DbQueryable
|
||||
.ToListAsync();
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 2️⃣ 内存中处理排序
|
||||
// 内存中处理排序
|
||||
var orderedLogs = logs
|
||||
.OrderByDescending(x =>
|
||||
x.StartTime <= now &&
|
||||
|
||||
Reference in New Issue
Block a user