using System.Net.Http;
using System.ServiceModel.Syndication;
using System.Xml;
using AngleSharp;
using AngleSharp.Html;
using Dpz.Core.EnumLibrary;
using Dpz.Core.Web.Library.Api;
using Microsoft.AspNetCore.SignalR;
using OpenIddict.Client.AspNetCore;
using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration;
// ReSharper disable UnusedVariable
#pragma warning disable CS0162

namespace Dpz.Core.Web.Controllers;

public class HomeController(
    IBingWallpaper bingWallpaper,
    IConfiguration configuration,
    IFusionCache fusionCache,
    IHomeCacheService cacheService
) : Controller
{
    /// <summary>
    /// home
    /// </summary>
    /// <returns></returns>
    public async Task<IActionResult> Index()
    {
        ViewBag.Menu = MenuItem.Home;
        this.SetTitle("首页");
        var model = await cacheService.GetCacheDataAsync();
        return View(model);
    }

    [Route(("news.html"))]
    public async Task<IActionResult> News(string mode = "")
    {
        var news = await cacheService.GetTodayNewsAsync();
        if (mode == "content")
        {
            return Content(
                news == null
                    ? ""
                    : string.Join(
                        "\n",
                        (news.NewsList ?? []).Select(x =>
                            $"{(news.NewsList ?? []).IndexOf(x) + 1}. {x.Title}"
                        )
                    )
            );
        }

        return View(news);
    }

    public async Task<IActionResult> TodayNews()
    {
        var news = await cacheService.GetTodayNewsAsync();
        return PartialView("_TodayNews", news);
    }

    public async Task<IActionResult> SystemNotifications(
        [FromServices] ISystemNotificationHistoryService notificationService
    )
    {
        var notifications = await notificationService.GetRecentAsync();
        return PartialView("_SystemNotificationsPartial", notifications);
    }

    [HttpGet, Route("datetime")]
    public async Task<IActionResult> Date()
    {
        return await Task.FromResult(View());
    }

#if DEBUG
    public IActionResult Demo(string? msg = null)
    {
        var s = User;
        return Json(new { time = DateTime.Now, message = msg });
    }

    public IActionResult Demo2()
    {
        throw new ArgumentException("param is null");
    }

    public async Task<IActionResult> Demo3(
        [FromServices] IHubContext<Dpz.Core.Web.Library.Hub.Notification> hubContext
    )
    {
        var s = new ProgressMessage
        {
            Message = "测试消息",
            Type = MessageType.Success,
            ProgressValues = [100m, 100m],
        };
        await hubContext.Clients.All.SendCoreAsync("hotTopicMessage", [s]);
        return View();
    }

    public IActionResult LayDemo()
    {
        return View();
    }

    public async Task<IActionResult> Demo4([FromServices] IHttpClientFactory httpClientFactory)
    {
        var httpClient = httpClientFactory.CreateClient("edge");
        var request = new HttpRequestMessage(HttpMethod.Get, "https://api.dpangzi.com/api/Article");

        var response = await httpClient.SendAsync(request);
        var json = await response.Content.ReadAsStringAsync();
        return Content(json, "application/json");
    }
#endif

    [HttpGet("~/connect/oidc")]
    public IActionResult Login(string fromUrl = "/")
    {
        if (User.Authenticated)
        {
            return Redirect(string.IsNullOrWhiteSpace(fromUrl) ? "/" : fromUrl);
        }

        return Challenge(
            new AuthenticationProperties { RedirectUri = fromUrl },
            OpenIddictClientAspNetCoreDefaults.AuthenticationScheme
        );
        //return View(model: fromUrl);
    }

    [Route("markdown-guide.html")]
    public async Task<IActionResult> Markdown(
        [FromServices] IHttpClientFactory httpClientFactory,
        string version = ""
    )
    {
        ViewBag.Menu = MenuItem.Markdown;
        this.SetTitle("Markdown 语法示例");

        var client = httpClientFactory.CreateClient("edge");
        var cdnHost = configuration["S3:CdnHost"];

        var markdown = await fusionCache.GetOrSetAsync<string>(
            "markdown-guide:content",
            async (_, cancellationToken) =>
            {
                var request = new HttpRequestMessage(
                    HttpMethod.Get,
                    $"{cdnHost}/markdown-guide.md"
                );
                var response = await client.SendAsync(request, cancellationToken);
                return await response.Content.ReadAsStringAsync(cancellationToken);
            },
            options => options.SetDuration(TimeSpan.FromDays(1))
        );
        return View(model: markdown);
    }

    [Route("act/{id}")]
    public async Task<IActionResult> Activity(
        [FromServices] IDynamicPageService dynamicPageService,
        string id = ""
    )
    {
        var cache = await fusionCache.TryGetAsync<ActivityModel>(CacheKey.DynamicPageKey(id));
        if (cache.HasValue)
        {
            Response.ContentType = cache.Value.ContentType;
            return View(model: cache.Value.Content);
        }

        var page = await dynamicPageService.FindAsync(id);
        if (page == null)
        {
            return NotFound();
        }

        var content = page.Content ?? "";

        if (page.ContentType == PageContentType.Html)
        {
            var htmlContext = BrowsingContext.New(Configuration.Default);
            var document = await htmlContext.OpenAsync(x => x.Content(content));
            if (page.Styles.Count > 0)
            {
                foreach (var item in page.Styles)
                {
                    var link = document.CreateElement("link");
                    link.SetAttribute("href", Url.Action("Activity", new { id = item.Name }));
                    link.SetAttribute("rel", "stylesheet");
                    document.Head?.Append(link);
                }
            }

            if (page.Scripts.Count > 0)
            {
                foreach (var item in page.Scripts)
                {
                    var script = document.CreateElement("script");
                    script.SetAttribute("src", Url.Action("Activity", new { id = item.Name }));
                    document.Body?.Append(script);
                }
            }

            await using var writer = new StringWriter();
            document.ToHtml(writer, new PrettyMarkupFormatter());
            content = writer.ToString();
        }

        var contentType = (page.ContentType ?? PageContentType.Html).GetDescription();
        Response.ContentType = contentType;

        var cacheValue = new ActivityModel(content, contentType);
        await fusionCache.SetAsync(CacheKey.DynamicPageKey(id), cacheValue, TimeSpan.FromDays(30));

        return View(model: content);
    }

    public async Task<IActionResult> GroupBarrages(
        [FromServices] IBarrageService barrageService,
        string group
    )
    {
        var list = await barrageService.GetGroupBarragesAsync(group);
        return Json(list);
    }

    public async Task<IActionResult> Wallpaper(bool clear = false)
    {
        if (clear)
        {
            await bingWallpaper.ClearCacheAsync();
        }

        var data = await bingWallpaper.GetRandomWallpaperAsync();
        return Json(data);
    }

    public async Task<IActionResult> TodayWallpaper(bool clear = false)
    {
        if (clear)
        {
            await bingWallpaper.ClearCacheAsync();
        }

        var data = await bingWallpaper.GetTodayWallpapersAsync();
        return Json(data);
    }

    [Route("friends.html")]
    public async Task<IActionResult> Friends([FromServices] IAppOptionService service)
    {
        this.SetTitle("友情链接");
        ViewBag.Menu = MenuItem.Friends;
        var list = await service.GetFriendsAsync();
        return View(list);
    }

    [Route("feed.rss")]
    public async Task<IActionResult> Rss([FromServices] IArticleService articleService)
    {
        var cdnHost = configuration["S3:CdnHost"];
        var feed = new SyndicationFeed(
            "叫我阿胖",
            "追赶大佬脚步的阿胖",
            new Uri("https://core.dpangzi.com/feed.rss"),
            "feed.rss",
            DateTime.Now
        )
        {
            ImageUrl = new Uri($"{cdnHost}/logo.png"),
            Copyright = new TextSyndicationContent($"© {DateTime.Now.Year} 叫我阿胖"),
            Language = "zh-Hans",
        };

        var articles = await articleService.GetLatestAsync(50);
        feed.Items = articles
            .Select(x =>
            {
                var content = new TextSyndicationContent(x.Introduction);
                var addr = Url.Action(
                    "Read",
                    "Article",
                    new { id = x.Id },
                    HttpContext.Request.Scheme
                );
                var uri = new Uri(addr!);
                return new SyndicationItem(x.Title, content, uri, x.Id, x.CreateTime)
                {
                    Authors = { new SyndicationPerson(x.Author.Name) },
                    LastUpdatedTime = x.LastUpdateTime,
                };
            })
            .ToList();

        var settings = new XmlWriterSettings
        {
            Encoding = Encoding.UTF8,
            NewLineHandling = NewLineHandling.Entitize,
            NewLineOnAttributes = true,
            Indent = true,
            Async = true,
        };

        using var stream = new MemoryStream();
        await using var xmlWriter = XmlWriter.Create(stream, settings);
        var rssFormatter = new Rss20FeedFormatter(feed, false);
        rssFormatter.WriteTo(xmlWriter);
        await xmlWriter.FlushAsync();
        return File(stream.ToArray(), "text/xml; charset=utf-8"); //application/rss+xml
    }

    [HttpGet("sitemap")]
    public IActionResult Sitemap()
    {
        return RedirectPermanent("/sitemap.xml");
    }

    [HttpGet, Route("blacklist.html")]
    public async Task<IActionResult> GetBlacklist([FromServices] IBlockService blockService)
    {
        var blockAccessThreshold = configuration.GetValue("BlockAccessThreshold", 0);
        var blocks = await blockService.GetBlocksAsync(blockAccessThreshold);
        return View(blocks);
    }

    [HttpGet, Route("dialog-demo.html")]
    public IActionResult DialogDemo()
    {
        this.SetTitle("Dialog 测试演示");
        ViewBag.Menu = MenuItem.Home;
        return View();
    }

    [HttpGet, Route("robots.txt")]
    public async Task<IActionResult> Robots([FromServices] IAppOptionService service)
    {
        var content = await service.GetRobotsAsync();
        return Content(content, "text/plain; charset=utf-8");
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这段代码是一个 ASP.NET Core MVC 控制器(HomeController),属于名为 Dpz.Core 的项目。控制器主要负责处理网站首页及若干通用页面/接口的请求,代码如下:

整体结构

  • 命名空间:Dpz.Core.Web.Controllers
  • 继承自:Controller
  • 使用了 C# 12 主构造函数,直接注入以下依赖:
    • IBingWallpaper:必应壁纸服务
    • IConfiguration:配置对象
    • IFusionCache:缓存框架(FusionCache)
    • IHomeCacheService:首页缓存服务
  • 这些依赖可在控制器内直接使用,减少了冗余的字段声明。

主要 Action 方法

1. 首页 Index

  • 设置菜单为“首页”,页面标题为“首页”。
  • 调用 cacheService.GetCacheDataAsync() 获取首页缓存数据,并返回对应视图。

2. 新闻页 News

  • 路由:news.html
  • 可选参数 mode,默认空字符串。
  • 获取今日新闻缓存数据 GetTodayNewsAsync()。
  • 若 mode == "content",则只返回纯文本,将新闻列表中的标题按 1.xxx\n2.xxx 格式用换行拼接,用于某些外部调用。
  • 否则返回新闻视图。

3. 今日新闻局部视图 TodayNews

  • 获取今日新闻并返回局部视图 _TodayNews。

4. 系统通知 SystemNotifications

  • 通过 [FromServices] 注入 ISystemNotificationHistoryService,获取最近通知记录,返回局部视图 _SystemNotificationsPartial。

5. 日期时间页 Date

  • 路由:GET datetime
  • 异步返回视图,可能用于展示当前时间(模板中实现细节未给出)。

调试专用 Action(仅在 DEBUG 编译下存在)

被 #if DEBUG 包裹,用于开发/测试:

Demo

  • 接收可选参数 msg,返回 JSON(当前时间和传入的消息)。

Demo2

  • 直接抛出异常,用于测试全局异常处理。

Demo3

  • 注入 SignalR Hub 上下文 IHubContext<Notification>,构造一个 ProgressMessage 对象,通过 SendCoreAsync("hotTopicMessage", ...) 向所有客户端推送消息,然后返回视图。

LayDemo

  • 返回视图,可能是测试 Layui 或其他前端组件。

Demo4

  • 注入 IHttpClientFactory,通过名为 edge 的 HTTP 客户端请求 https://api.dpangzi.com/api/Article,将响应原文作为 application/json 返回。

身份认证与登录

Login

  • 路由:~/connect/oidc(注意路由写法 ~/ 表示根目录)
  • 参数 fromUrl 用于登录成功后跳转。
  • 若用户已认证,直接重定向到 fromUrl 或首页。
  • 否则使用 Challenge 方法发起认证质询,认证方案为 OpenIddictClientAspNetCoreDefaults.AuthenticationScheme(即 OpenIddict 客户端),用于第三方/OIDC 登录。

Markdown 指南页

Markdown

  • 路由:markdown-guide.html
  • 注入 IHttpClientFactory 来获取 HTTP 客户端。
  • 从配置 S3:CdnHost 读取 CDN 地址。
  • 使用 fusionCache.GetOrSetAsync 缓存 Markdown 内容(缓存键 "markdown-guide:content",缓存一天)。
  • 缓存 miss 时,从 {cdnHost}/markdown-guide.md 下载 Markdown 文本。
  • 最终将文本作为 model 渲染到视图。

动态页面

Activity

  • 路由:act/{id}
  • 注入 IDynamicPageService,根据 id 获取动态页面内容。
  • 首先尝试从 FusionCache 获取对应缓存(键为 CacheKey.DynamicPageKey(id)),若命中,则直接设置响应 ContentType 并返回 View。
  • 缓存 miss 时调用服务查找页面:
    • 未找到则返回 404。
    • 如果页面内容类型是 HTML:
      • 使用 AngleSharp 解析 HTML 字符串。
      • 为页面中追加 CSS 样式链接:遍历 page.Styles,每个样式项的链接指向 Url.Action("Activity", new { id = item.Name })(即同样通过动态页路由获取资源)。
      • 为页面追加脚本标签:遍历 page.Scripts,脚本地址同样指向动态页接口。
      • 通过 PrettyMarkupFormatter 格式化 HTML 并输出字符串。
    • 根据内容类型(HTML 或其它)获取描述,设置 Response.ContentType。
    • 将内容包裹成 ActivityModel 缓存,有效期 30 天。
    • 返回 View,model 为 HTML 内容。

这个方法实现了 动态 HTML 页面/样式/脚本 的统一管理和渲染。


弹幕相关

GroupBarrages

  • 注入 IBarrageService,参数 group,获取指定分组的弹幕列表,返回 JSON。

壁纸相关(必应壁纸)

Wallpaper

  • 参数 clear:若为 true 则先清除壁纸缓存。
  • 调用 bingWallpaper.GetRandomWallpaperAsync() 获取随机壁纸,返回 JSON。

TodayWallpaper

  • 同样支持 clear,调用 bingWallpaper.GetTodayWallpapersAsync() 获取今日壁纸列表,返回 JSON。

友情链接

Friends

  • 路由:friends.html
  • 注入 IAppOptionService
  • 设置标题“友情链接”和菜单项。
  • 调用 service.GetFriendsAsync() 获取列表并返回视图。

RSS 订阅

Rss

  • 路由:feed.rss
  • 注入 IArticleService
  • 从配置获取 CDN Host,创建 SyndicationFeed 对象(feed 标题、描述、链接、ID、时间、图片、版权、语言)。
  • 调用 articleService.GetLatestAsync(50) 获取最新 50 篇文章。
  • 每篇文章生成一个 SyndicationItem:
    • 标题、摘要内容、文章链接(通过 Url.Action 生成)。
    • 作者、更新时间。
  • 使用 Rss20FeedFormatter 和 XmlWriter 将 feed 写入内存流,最后以 text/xml; charset=utf-8 返回文件流,即生成 RSS 2.0 订阅源。

站点地图与机器人协议

Sitemap

  • 路由:sitemap,永久重定向到 /sitemap.xml。

Robots

  • 路由:robots.txt
  • 注入 IAppOptionService,调用 GetRobotsAsync() 获取 robots 内容,以纯文本返回。

黑名单页面

GetBlacklist

  • 路由:blacklist.html
  • 注入 IBlockService
  • 从配置读取 BlockAccessThreshold(默认 0),表示封禁访问次数的阈值。
  • 调用 blockService.GetBlocksAsync(threshold) 获取黑名单列表,返回视图。

对话框演示页

DialogDemo

  • 路由:dialog-demo.html
  • 设置标题和菜单,返回视图。

辅助扩展说明

  • SetTitle 与 ViewBag.Menu:用于设置视图的标题与高亮菜单。
  • this.SetTitle(...) 可能是一个扩展方法,根据控制器上下文设置页面标题。
  • 代码中使用了多个第三方/业务库:如 AngleSharp(HTML解析)、FusionCache(缓存)、SignalR(实时通信)、OpenIddict(OIDC客户端)等。
  • // ReSharper disable UnusedVariable 和 #pragma warning disable CS0162 用于抑制编译/分析警告,说明存在一些未使用的变量或不可达代码(如 Demo 中的 var s = User; 只赋值未使用)。

总结

HomeController 是一个多功能控制器,承担了:

  • 首页 / 新闻 / 动态页渲染
  • 必应壁纸、弹幕、系统通知等接口
  • RSS 生成、Robots、Sitemap 等 SEO 相关功能
  • 登录(OIDC)、黑名单管理、工具演示页面等

整个类大量依赖缓存(FusionCache+自定义缓存服务)提升性能,通过依赖注入解耦业务服务,并使用了 ASP.NET Core 的 MVC 特性(Razor 视图、PartialView、Json、Content、File 等返回类型)。

评论加载中...