using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Dpz.Core.Public.ViewModel.Request;
using Dpz.Core.Public.ViewModel.Request.HandlerRequest;
using Dpz.Core.Public.ViewModel.RequestEvent;
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();
        }

        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 是一个部分类(partial class),使用了依赖注入来获取所需的服务:

  • IArticleService - 文章业务服务
  • IObjectStorageOperation - 对象存储服务
  • IMediator - 中介者模式实现
  • ILogger<ArticleController> - 日志服务

主要功能方法

1. Index 方法 - 文章列表页

public async Task<IActionResult> Index(...)
  • 支持按标签筛选文章
  • 分页显示(页大小10-100,默认10)
  • 支持PJAX异步加载(检查请求头判断是否返回部分视图)
  • 返回包含推荐文章、最新文章、标签等完整页面数据

2. Read 方法 - 文章详情页

public async Task<IActionResult> Read(...)
  • 通过ID和文本参数获取文章内容
  • 使用中介者模式发送请求
  • 设置页面元数据(描述、关键词等)
  • 使用正则表达式清理文章介绍中的HTML标签

3. Upload 方法 - 图片上传

[CheckAuthorize]
[HttpPost]
public async Task<IActionResult> Upload(...)
  • 需要授权验证
  • 只接受图片文件
  • 按日期组织存储路径
  • 生成唯一文件名(使用ObjectId)
  • 返回JSON格式的上传结果

4. Publish 方法 - 发布/编辑文章

[HttpPost, CheckAuthorize]
public async Task<IActionResult> Publish(...)
  • 需要授权验证
  • 支持新增标签功能
  • 区分新增和编辑操作
  • 验证用户权限(只能编辑自己的文章)
  • 包含完整的错误处理

5. Delete 方法 - 删除文章

[CheckAuthorize, HttpPost]
public async Task<IActionResult> Delete(...)
  • 需要授权验证
  • 验证文章存在性和用户权限
  • 只允许删除自己发布的文章
[HttpGet]
public async Task<IActionResult> Search(...)
  • 支持关键词搜索
  • 返回搜索结果和所有标签
  • 设置搜索页面标题

技术特点

  1. 异步编程:所有方法都使用 async/await 模式
  2. 依赖注入:通过构造函数注入所需服务
  3. 模型验证:使用数据注解进行参数验证
  4. 权限控制:使用 [CheckAuthorize] 特性进行授权验证
  5. PJAX支持:支持异步页面加载技术
  6. 正则表达式:使用源生成器优化正则表达式性能
  7. 错误处理:完善的异常处理和用户友好的错误信息
  8. RESTful设计:遵循REST API设计原则

这个控制器实现了一个完整的文章管理系统,包括浏览、搜索、发布、编辑、删除等核心功能,代码结构清晰,功能完善。

评论加载中...