新增rss功能

This commit is contained in:
2025-05-15 12:53:47 +08:00
parent b689c013d7
commit 22e9aefe1a
24 changed files with 318 additions and 151 deletions

View File

@@ -10,6 +10,7 @@
string Script,
int PageSizeOfHomepage,
int PageSizeOfListpage,
int RssGenerationIntervalHours,
string ArticleRoute,
string PageRoute,
string CategoryRoute,

View File

@@ -1,89 +0,0 @@

namespace Yes.Application.Blogs
{
public record GetRssQuery() : IRequest<GetRssQueryResponse>;
public record GetRssQueryResponse(string Xml);
public class GetRssQueryHandler(
IOptionsMonitor<BlogSettings> options,
BlogDbContext db,
IMapper mapper) : IRequestHandler<GetRssQuery, GetRssQueryResponse>
{
private readonly BlogSettings _settings = options.CurrentValue;
private readonly BlogDbContext _db = db;
public async Task<GetRssQueryResponse> Handle(GetRssQuery request, CancellationToken cancellationToken)
{
var queryArticle = _db.Articles.Where(x => x.Type == ArticleTypeEnum.Article && x.Status == ArticleStatusEnum.Normal).AsQueryable();
var pubDate = queryArticle.Any() ? _db.Articles.Min(x => x.PublishDate) : DateTime.MinValue;
var channel = new Channel
{
Title = _settings.Name,
Link = _settings.Url,
Description = _settings.Description,
Language = "zh-CN",
FeedUrl = $"{_settings.Url}/feed/",
PubDate = pubDate
};
// 添加扩展元素(示例)
channel.Extensions.Add(new XElement("generator", _settings.Name));
channel.Extensions.Add(new XElement("docs", "http://www.rssboard.org/rss-specification"));
var query = from article in queryArticle
join categoryTemp in _db.Categories.AsNoTracking() on article.CategoryId equals categoryTemp.Id into tempCategory
from category in tempCategory.DefaultIfEmpty()
join userTemp in _db.Users.AsNoTracking() on article.UserId equals userTemp.Id into tempUser
from user in tempUser.DefaultIfEmpty()
orderby article.Id descending
select new ArticleModel
{
Id = article.Id,
CanComment = article.CanComment,
CommentCount = article.CommentCount,
CategoryId = article.CategoryId,
ModifyDate = article.ModifyDate.ToSafeTime(),
PublishDate = article.PublishDate.ToSafeTime(),
CreateDate = article.CreateDate.ToSafeTime(),
Content = article.Content,
CoverUrl = article.CoverUrl,
Slug = article.Slug,
ReadCount = article.ReadCount,
Title = article.Title,
Summary = article.Summary,
UserId = article.UserId,
Category = new CategoryModel
{
CoverUrl = category.CoverUrl,
Description = category.Description,
Id = category.Id,
Name = category.Name,
Slug = category.Slug
}
};
var items = query.Select(x => new Item
{
Content = x.Content,
Title = x.Title,
Url = _settings.ArticleRoute.ToRoute(x.Category, x),
Description = x.Summary,
CommentsCount = x.CommentCount,
Creator = x.UserId.ToString(),
PublishDate = x.PublishDate,
CommentsUrl = $"{_settings.ArticleRoute.ToRoute(x.Category, x)}#comments",
CommentRssUrl = $"{_settings.Url}/feed/"
});
var generator = new RssGenerator();
string xml = generator.GenerateRss(channel, items);
return new GetRssQueryResponse(xml);
}
}
}

View File

@@ -0,0 +1,38 @@

namespace Yes.Application.Core.HostedServices
{
public class HangfireJobRegistrationService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly BlogSettings _settings;
public HangfireJobRegistrationService(
IServiceProvider serviceProvider,
IOptionsMonitor<BlogSettings> options)
{
_serviceProvider = serviceProvider;
_settings = options.CurrentValue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var scope = _serviceProvider.CreateScope();
var recurringJobManager = scope.ServiceProvider.GetRequiredService<IRecurringJobManager>();
var intervalHours = _settings.RssGenerationIntervalHours;
//recurringJobManager.AddOrUpdate<RssGenerationJob>(
// nameof(RssGenerationJob),
// x => x.Execute(),
// Cron.Hourly(intervalHours)
//);
recurringJobManager.AddOrUpdate<RssGenerationJob>(
nameof(RssGenerationJob),
x => x.Execute(),
Cron.Minutely
);
}
}
}

View File

@@ -0,0 +1,100 @@
namespace Yes.Application.Core.HostedServices
{
public class RssGenerationJob
{
private readonly BlogSettings _settings;
private readonly BlogDbContext _db;
private readonly ILogger<RssGenerationJob> _logger;
private readonly IWebHostEnvironment _env;
public RssGenerationJob(
IOptionsMonitor<BlogSettings> options,
BlogDbContext db,
IWebHostEnvironment env,
ILogger<RssGenerationJob> logger)
{
_settings = options.CurrentValue;
_db = db;
_env = env;
_logger = logger;
}
public async Task Execute()
{
try
{
var queryArticle = _db.Articles.Where(x => x.Type == ArticleTypeEnum.Article && x.Status == ArticleStatusEnum.Normal).AsQueryable();
var pubDate = queryArticle.Any() ? _db.Articles.Min(x => x.PublishDate) : DateTime.MinValue;
var channel = new Channel
{
Title = _settings.Name,
Link = _settings.Url,
Description = _settings.Description,
Language = "zh-CN",
FeedUrl = $"{_settings.Url}/feed/",
PubDate = pubDate
};
// 添加扩展元素(示例)
channel.Extensions.Add(new XElement("generator", _settings.Name));
channel.Extensions.Add(new XElement("docs", "http://www.rssboard.org/rss-specification"));
var query = from article in queryArticle
join categoryTemp in _db.Categories.AsNoTracking() on article.CategoryId equals categoryTemp.Id into tempCategory
from category in tempCategory.DefaultIfEmpty()
join userTemp in _db.Users.AsNoTracking() on article.UserId equals userTemp.Id into tempUser
from user in tempUser.DefaultIfEmpty()
orderby article.Id descending
select new ArticleModel
{
Id = article.Id,
CanComment = article.CanComment,
CommentCount = article.CommentCount,
CategoryId = article.CategoryId,
ModifyDate = article.ModifyDate.ToSafeTime(),
PublishDate = article.PublishDate.ToSafeTime(),
CreateDate = article.CreateDate.ToSafeTime(),
Content = article.Content,
CoverUrl = article.CoverUrl,
Slug = article.Slug,
ReadCount = article.ReadCount,
Title = article.Title,
Summary = article.Summary,
UserId = article.UserId,
Category = new CategoryModel
{
CoverUrl = category.CoverUrl,
Description = category.Description,
Id = category.Id,
Name = category.Name,
Slug = category.Slug
}
};
var items = query.Select(x => new Item
{
Content = x.Content,
Title = x.Title,
Url = $"{_settings.Url}/{_settings.ArticleRoute.ToRoute(x.Category, x)}",
Description = x.Summary,
CommentsCount = x.CommentCount,
Creator = x.UserId.ToString(),
PublishDate = x.PublishDate,
CommentsUrl = $"{_settings.Url}/{_settings.ArticleRoute.ToRoute(x.Category, x)}#comments",
CommentRssUrl = $"{_settings.Url}/feed/"
});
var generator = new RssGenerator();
string xml = generator.GenerateRss(channel, items);
File.WriteAllText(Path.Combine(_env.WebRootPath, "feed.xml"), xml);
}
catch (Exception ex)
{
_logger.LogError(ex, "RSS generation failed");
}
}
}
}

View File

@@ -39,5 +39,6 @@
}
}
}

View File

@@ -40,3 +40,8 @@ global using System.Text;
global using Yes.Infrastructure.Authorizations.Identity.Authroizations;
global using System.Xml.Linq;
global using Yes.Infrastructure.Rss;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.Logging;
global using Hangfire;
global using Microsoft.Extensions.Hosting;

View File

@@ -11,6 +11,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.18" />
<PackageReference Include="Hangfire.Core" Version="1.8.18" />
<PackageReference Include="Hangfire.MySqlStorage" Version="2.0.3" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.20.12" />
<PackageReference Include="Hangfire.SqlServer" Version="1.8.18" />
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Qiniu" Version="8.7.0" />