个人博客文章归档实现📑

前言

随着博客的文章越来越多,那么归档就显得尤为重要,然后最近也是没什么课,加紧更新一下博客,之前也是更新了评论、留言板。

然后博客是使用的前后的不分离的项目,数据返回一般都是用的.NET的强类型数据返回,也会用到分布视图。

重点是一段sql查询困恼我许久,在后端接口中有详细解释。😓

后端接口的实现

首先,文章归档我实现的思路是根据月份和日期分组降序查询。

思路有了之后,就开始实现。

Service层实现:

首先创建IArcService接口和ArcService类,然后就是继承实现了,看代码:

public interface IArcService
{
    Task<IPagedList<ArcPost>> GetAllAsync(QueryParameters param);
}

下述方法不可取:

public class ArcService:IArcService
{
    private readonly MyDbContext _myDbContext;

    public ArcService(MyDbContext myDbContext)
    {
        _myDbContext = myDbContext;
    }
    public async Task<IPagedList<ArcPost>>GetAllAsync(QueryParameters param)
    {
        var groupKeys = await _myDbContext.posts
            .GroupBy(p => new { p.CreationTime.Year, p.CreationTime.Month })
            .Select(g => new { Year = g.Key.Year, Month = g.Key.Month })
            .OrderByDescending(g => g.Year)
            .ThenByDescending(g => g.Month) 
            .ToListAsync();
        
        var postsByMonth = new List<ArcPost>();
        
        foreach (var key in groupKeys)
        {
            var posts = await _myDbContext.posts
                .Where(p => p.CreationTime.Year == key.Year && p.CreationTime.Month == key.Month)
                .ToListAsync();
        
            postsByMonth.Add(new ArcPost 
            {
                Year = key.Year,
                Month = key.Month,
                Posts = posts
            });
        }
        
        var result = postsByMonth.OrderByDescending(o => o.Year).ToPagedList(param.Page, param.PageSize);
        
        return result;
    }
}

private readonly MyDbContext _myDbContext;是注入EF 上下文服务,用与操作数据库,这里就不细讲了。

然后我在分组查询时出现了一个问题,我原本是这样子写的,重点!!!,正常来说这么写是没问题的

可取的方法:

var query = _myDbContext.posts
    .OrderByDescending(p => p.CreationTime)
    .GroupBy(p => new { p.CreationTime.Year, p.CreationTime.Month })
    .Select(g => new ArcPost
            {
                Year = g.Key.Year,
                Month = g.Key.Month,
                Posts = g.ToList()
            });

var result = await query.ToPagedListAsync(param.Page, param.PageSize);

return result;

但是按上述代码查询会出现如下报错:

摘取报错:

但是他会出现System.InvalidOperationException:无法转换投影中的集合子查询,因为父查询或子查询都没有投影唯一标识它并在客户端正确生成结果所需的必要信息。当尝试关联无钥匙实体类型时,可能会发生这种情况。在“Distinct”之前投影的某些情况下,或者在“GroupBy”的情况下分组键的某些形状也可能发生这种情况。它们应该包含应用该操作的实体的所有键属性,或者只包含简单的属性访问表达式。

然后就是长达1天的bug解决,最后发现升级版本就能解决,我真的是服了。

Posts = g.ToList() 这行代码一直映射不成功,我用的是EF Core 6.0版本,我弄了很久,换了很多种方式,就是映射不出来,然后我升级到EF Core 7.0就不会出现这个问题了。

控制器实现

考虑到后面文章会很多,页面不一次性加载所有,所以用了分页的思想去返回信息,先展示一小部分文章的归档,然后点击加载更多之后,展示后续的归档。

public class ArticleArcController : Controller
{
    private readonly IArcService _arcService;
    private readonly Messages _messages;

    public ArticleArcController(IArcService arcService,Messages messages)
    {
        _arcService = arcService;
        _messages = messages;
    }
    // GET
    public IActionResult Index()
    {
        return View();
    }
    public async Task<IActionResult> GetPageData(int page = 1, int pageSize = 2)
    {
        IPagedList<ArcPost> data = await _arcService.GetAllAsync(new QueryParameters
        {
            Page = page,
            PageSize = pageSize,
        });
        if (data.Count == 0) {
            // 没有更多数据了,返回错误
            return NotFound();
        }
        
        return PartialView("Widgets/ArcBox", data);
    }
}

QueryParameters类就是一个普通的分页参数类 然后分页的插件是X.PagedList,通过Nuget下载即可。

可以看到GetPageData返回的是一个分布视图,后面会讲,上述就是后端的实现,接下来讲解前端的实现。

前端页面实现

Index页面视图

这是一个主视图

<div class="container px-4 py-3">
    <h2 class="d-flex w-100 justify-content-between pb-2 mb-3 border-bottom">
        <div>Article archiving</div>
        <div>文章归档</div>
    </h2>
    <div id="liveAlertPlaceholder"></div>
    <div class="container">
        <div id="data-list">

        </div>
        <div class="ArcBtn" id="ArcBtn">
            <button type="button" class="btn btn-info" id="load-more">加载更多</button>
        </div>
    </div>
</div>

这里主要注意 <div id="data-list">,它将用来加载文章归档的数据

js加载数据

var currentPage = 2;
// 页面加载时渲染第一页的数据
$.ajax({
  url: '/ArticleArc/GetPageData',
  type: 'GET',
  data: { page: 1, pageSize: 2 },
  success: function(data) {
    // 将数据渲染到页面中
    $('#data-list').html(data);
  },
  error: function() {
    // 处理错误
  }
});

这串js代码请求了后端接口,也就是那个返回分部视图的接口,传递的参数页很简单,第一页,显示2条数据。

分布视图展示数据

视图名为ArcBox.cshtml

@model X.PagedList.IPagedList<Personalblog.Model.ViewModels.Arc.ArcPost>
@inject LinkGenerator _generator
@inject IHttpContextAccessor _accessor;

@foreach (var a in @Model)
{
    <div class="Arc">
        <div class="ArcBox toggle-icon" data-bs-toggle="collapse" href="#id_@a.Year@a.Month" aria-expanded="false" aria-controls="collapseExample">
            <div style="width: 100px;margin-left:10px">@a.Year 年 @a.Month 月</div>
            <div style="margin-left: auto;">
                <a class="btn btn-light" role="button">
                    <i class="fa fa-chevron-down cd"></i>
                </a>
            </div>
        </div>
        <div class="collapse" id="id_@a.Year@a.Month">
            <div class="card card-body" style="border:0;padding-bottom:0">
                <ol style="list-style-type: none;margin: 0">
                    @foreach (var ap in a.Posts)
                    {
                        string? url = _generator.GetUriByAction(
                            _accessor.HttpContext!,
                            "Post", "Blog",
                            new { @ap.Id }, "https"
                            );
                        <li>
                            <a href="@url" style="text-decoration: none;">@ap.CreationTime.ToString("MM/dd"):&nbsp;&nbsp;@ap.Title</a>
                        </li>
                    }
                </ol>
            </div>
        </div>
    </div>
}

上述就是分布视图了,也就是数据展示的页面,用的是model强类型去加载数,可以看到上面引入了ArcPost类,它其实就是后端接口返回的那个数据类型。

也就是return PartialView("Widgets/ArcBox", data);中的data

public class ArcPost
{
    public int Year;
    public int Month;
    public List<Post> Posts;
}

上述就是前端关键代码的实现

效果图

微信截图_20230615234021

浏览地址:ZY知识库 · ZY - 文章归档 (pljzy.top)