using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Dpz.Core.Public.ViewModel.Request;
using Dpz.Core.Service.Mediator.Features.Article.Queries;
using Dpz.Core.Service.ObjectStorage.Services;
using Dpz.Core.Web.Library.Api;
namespace Dpz.Core.Web.Controllers;
public partial class ArticleController(
IArticleService articleService,
IObjectStorageOperation objectStorageService,
IMediator mediator,
ILogger<ArticleController> logger
) : Controller
{
public async Task<IActionResult> Index(
[FromServices] IHomeCacheService cacheService,
string? tag = "",
[Range(10, 100)] int pageSize = 10,
int pageIndex = 1,
CancellationToken cancellationToken = default
)
{
if (!ModelState.IsValid)
{
return NotFound();
}
var tags = string.IsNullOrEmpty(tag) ? [] : new[] { tag };
this.SetTitle(string.IsNullOrEmpty(tag) ? "文章列表" : $"标签-{tag}-文章列表");
var list = await articleService.GetPagesAsync(
pageIndex,
pageSize,
tags: tags,
cancellationToken: cancellationToken
);
if (Request.Headers["X-PJAX"] == "true" && Request.Query["_pjax"] == "#article-list")
{
return PartialView("_ArticleListPartial", list);
}
var model = new ArticleIndexModel
{
LikeArticle = await cacheService.GetRandomArticlesAsync(cancellationToken),
List = list,
News = await cacheService.GetLatestArticlesAsync(cancellationToken),
Tags = await cacheService.GetArticleTagsAsync(cancellationToken),
};
return View(model);
}
public async Task<IActionResult> Read(
string id = "",
string text = "",
CancellationToken cancellationToken = default
)
{
var article = await mediator.Send(
new ArticleReadRequest { Id = id, Text = text },
cancellationToken
);
if (article == null)
{
return NotFound();
}
await articleService.ViewAsync(id, cancellationToken);
this.SetTitle(article.Title);
var pageMetaPage = new VmPageMetadata
{
Description = ClearIntroductionRegex().Replace(article.Introduction ?? "", ""),
Keywords = [article.Title],
Relations = ["Article", "Read", id],
};
ViewData["PageMetadata"] = pageMetaPage;
ViewData["Text"] = text;
return View(article);
}
[CheckAuthorize]
[HttpPost]
public async Task<IActionResult> Upload(CancellationToken cancellationToken)
{
var file = Request.Form.Files.FirstOrDefault();
if (file is { Length: > 0 } && file.ContentType.Contains("image"))
{
//var userInfo = User.GetIdentity();
var path = new[] { "images", "article", DateTime.Now.ToString("yyyy-MM-dd") };
var fileName =
ObjectId.GenerateNewId() + file.FileName[file.FileName.LastIndexOf('.')..];
var result = await objectStorageService.UploadAsync(
file.OpenReadStream(),
path,
fileName,
cancellationToken
);
return Json(
new
{
success = 1,
message = "",
url = result.AccessUrl,
}
);
}
return Json(
new
{
success = 0,
message = "请选择一张图片!",
url = "",
}
);
}
[HttpPost, CheckAuthorize]
public async Task<IActionResult> Publish(
EditArticleRequest model,
string newTag = "",
CancellationToken cancellationToken = default
)
{
var userInfo = User.RequiredUserInfo;
var checkResult = ModelState.CheckModelState();
if (checkResult.IsValid)
{
return Json(new ResultInfo(string.Join("\n", checkResult.ErrorMessages)));
}
if (!string.IsNullOrWhiteSpace(newTag))
{
var newTags = newTag.Split(',').Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
model.Tags.AddRange(newTags);
}
// 编辑
if (!string.IsNullOrEmpty(model.Id))
{
var article = await articleService.GetArticleAsync(model.Id, cancellationToken);
if (article == null)
{
return Json(new ResultInfo("文章不存在"));
}
if (userInfo.Id != article.Author?.Id)
{
return Json(new ResultInfo("不能修改别人发布的文章!"));
}
await articleService.EditArticleAsync(model, userInfo, cancellationToken);
return Json(new ResultInfo(data: model.Id));
}
try
{
// 新增
var createdArticle = await articleService.CreateArticleAsync(
model,
userInfo,
cancellationToken
);
await articleService.ClearCacheAsync(cancellationToken);
return Json(new ResultInfo(data: createdArticle.Id));
}
catch (Exception ex)
{
logger.LogInformation(ex, "创建文章失败");
return Json(ResultInfo.ToFail(ex.Message));
}
}
[CheckAuthorize, HttpPost]
public async Task<IActionResult> Delete(string id, CancellationToken cancellationToken)
{
var article = await articleService.GetArticleAsync(id, cancellationToken);
if (article == null || User.RequiredUserId != article.Author?.Id)
{
return Json(new ResultInfo("未找到文章,或者不能删除别人发布的文章!"));
}
await articleService.DeleteAsync(id, cancellationToken);
return Json(new ResultInfo(true));
}
[HttpGet]
public async Task<IActionResult> Search(
string? keyword = "",
CancellationToken cancellationToken = default
)
{
var request = new ArticleSearchRequest { Keyword = keyword };
var searchResult = await mediator.Send(request, cancellationToken);
var tags = await articleService.GetAllTagsAsync(cancellationToken);
var model = new ArticleSearchModel(keyword, searchResult, tags);
this.SetTitle($"搜索 - {keyword}");
return View(model);
}
[GeneratedRegex("<[^>]*>")]
private static partial Regex ClearIntroductionRegex();
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个ASP.NET Core MVC的文章管理控制器代码,我来为您详细解释每个部分的功能:
控制器概述
ArticleController 是一个处理文章相关操作的控制器,使用了现代C#的主构造函数语法,注入了以下依赖服务:
IArticleService: 文章业务逻辑服务IObjectStorageOperation: 对象存储服务(用于文件上传)IMediator: 中介者模式服务(处理查询请求)ILogger: 日志服务
主要功能方法
1. Index 方法 - 文章列表页面
public async Task<IActionResult> Index(...)
- 支持按标签筛选文章
- 分页显示文章列表(页面大小10-100可配置)
- 支持PJAX异步加载(提升用户体验)
- 返回包含推荐文章、最新文章、标签等的完整页面模型
2. Read 方法 - 文章阅读页面
public async Task<IActionResult> Read(...)
- 根据ID和文本参数获取文章详情
- 增加文章浏览量
- 设置页面SEO元数据(标题、描述、关键词)
- 使用正则表达式清理文章简介中的HTML标签
3. Upload 方法 - 图片上传
[CheckAuthorize]
[HttpPost]
public async Task<IActionResult> Upload(...)
- 需要用户授权认证
- 仅支持图片文件上传
- 按日期组织存储路径(images/article/yyyy-MM-dd)
- 使用ObjectId生成唯一文件名
- 返回JSON格式的上传结果
4. Publish 方法 - 发布/编辑文章
[HttpPost, CheckAuthorize]
public async Task<IActionResult> Publish(...)
- 需要用户授权认证
- 支持新增和编辑两种模式
- 处理新标签的添加
- 验证用户权限(只能编辑自己的文章)
- 包含完整的错误处理和日志记录
5. Delete 方法 - 删除文章
[CheckAuthorize, HttpPost]
public async Task<IActionResult> Delete(...)
- 需要用户授权认证
- 验证文章存在性和用户权限
- 只允许删除自己发布的文章
6. Search 方法 - 文章搜索
[HttpGet]
public async Task<IActionResult> Search(...)
- 根据关键词搜索文章
- 使用中介者模式处理搜索请求
- 返回搜索结果和所有标签信息
技术特点
- 异步编程: 所有方法都使用
async/await模式 - 取消令牌: 支持请求取消,提高系统响应性
- 数据验证: 使用
DataAnnotations进行参数验证 - 权限控制: 通过
CheckAuthorize特性控制访问权限 - SEO优化: 动态设置页面标题和元数据
- PJAX支持: 提供部分页面更新功能
- 错误处理: 完善的异常处理和用户友好的错误信息
这个控制器设计得很规范,遵循了现代Web开发的最佳实践,具有良好的可维护性和扩展性。
评论加载中...