md文章批量上传到博客 二开修改

image-20240421002846688

上传文草 打包上传md文件演示图:

.NET WebApi接口限流实践.md
gpt.png
nuget包.png
接口限流截图.png
.NET WebApi接口限流实践zip
测试一下文章上传,文章分类默认是没有的,需要再这里自己填写。

特殊说明:

上传如果输入新的类目,导致上传后的文章类目为空 是因为输入后 类目没有加载,需要下拉再次选择输入的类目才可以正常获取类目

http://localhost:7031/Api/Blog/Upload 上传接口

D:\KnowledgeDocument\云计算\Cloud calc\docker\Docker容器云样卷.zip

期望实现 vue打包到netcore 前后端实现跳转

简易版在文章后台加个link跳转

注意事项1:

bolg分类页面 显示的是localhost地址无法跳转分类 http://localhost/Blog/List?categoryId=1
  return categories.Select(c => new CategoryNode
   {
       text = c.Name,
       href = _generator.GetUriByAction(
       _accessor.HttpContext!,
       nameof(BlogController.List),
       "Blog",
       new { categoryId = c.Id },"http"
       ),
       tags = new List<string> { c.Posts.Count.ToString() },
       nodes = GetNodes(c.Id)
   }).ToList();
   
  localhost 很可能是由 _generator.GetUriByAction 方法生成的默认主机名。在 ASP.NET Core 中,当你生成URL时,如果没有显式指定主机名,它会使用默认的主机名。通常情况下,如果你希望在生成的URL中使用特定的主机名或者IP地址,你需要在生成URL的方法中显式地指定主机名。

你可以尝试将 _generator.GetUriByAction 方法的调用修改为显式指定主机名的形式,如下所示:

   HostString host = new HostString("149.88.82.231:76");
href = _generator.GetUriByAction(
    _accessor.HttpContext!,
    nameof(BlogController.List),
    "Blog",
    new { categoryId = c.Id },
    "http",
   host
),
在这里,你需要将 yournewhostname.com 替换为你想要使用的实际主机名或IP地址。通过这种方式,你就可以确保生成的URL中不再包含默认的 localhost。
STATS页面同理
       @foreach (var ap in a.Posts)
       {
           string? url = _generator.GetUriByAction(
               _accessor.HttpContext!,
               "Post", "Blog",
               new { @ap.Id }, "http",

               );
           <li>
               <a href="@url" style="text-decoration: none;">@ap.CreationTime.ToString("MM/dd"):&nbsp;&nbsp;@ap.Title</a>
           </li>
       }
分类统计
var ctx = document.getElementById("myChart");
$.getJSON('http://149.88.82.231:76/Api/Category/WordCloud', function (res) {
    var labels = [];
上传到linux 需要在wwwroot文件夹下面创建temp文件夹 上传用

post/edit/内上传的图片 地址指向是localhost:7031

摄影地址指向错误http://149.88.82.231:6001/photo/undefined/media/photofraphy/9224d48debb85531.jpg

http://149.88.82.231:76/media/yasuo2/6dab0a440eb59b6f.jpg
http://149.88.82.231:76/media/photofraphy/6dab0a440eb59b6f.jpg
http://149.88.82.231:7031/media/yasuo2/6dab0a440eb59b6f.jpg

原因是需要 在config items里面 配置当前服务器地址

文章末尾连接
    public async Task<PostViewModel> GetPostViewModel(Post post)
    {
        
        var model = new PostViewModel
        {
            Id = post.Id,
            Title = post.Title,
            Summary = post.Summary ?? "(没有介绍)",
            Content = post.Content ?? "(没有内容)",
            Path = post.Path ?? string.Empty,
            Url = _generator.GetUriByAction(
    _accessor.HttpContext!,
    "Post", "Blog",
    new { post.Id }, "https"
),
可能需要修改的有 创建邮件 EmailTemplate 留言板 千牛cdn

找http://149.88.82.231:76

163的email

  "Email": {
    "Address": "123@163.com",
    "Password": "WRKjWKRj"
  }

取消邮箱发送

取消申请链接 的邮箱发送 SendEmailOnAdd 方法

通过申请的 await SendEmailOnAccept(item);

重点1 实现上传 但是在备份和下载上 不需要完善

因为如果id对应文件夹 那么备份和下载 只需要打包对应文件夹即可

1,编辑md的时候上传图片 如何重复利用 public string Host => _conf["host"];

2.文章是否可以直接下载包含图片 吗

3.怎么实现迁移,处理db数据库,对应文件夹(对应postid) 怎么处理 需要理清楚 添加文章的逻辑

下载文章 md

替换图片路径 下载图片 (图片链接要改成 同级目录链接)

打包md和图片文件

using System;
using System.IO;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

public class PostService
{
    private readonly MyDbContext _myDbContext;

    public PostService(MyDbContext myDbContext)
    {
        _myDbContext = myDbContext;
    }

    public async Task<string> DownloadImagesAndReplaceAsync(Post post)
    {
        // 下载文章中的外部图片并替换成相对路径
        var content = post.Content;
        var regex = new Regex(@"!\[.*?\]\((.*?)\)");
        var matches = regex.Matches(content);

        foreach (Match match in matches)
        {
            var imageUrl = match.Groups[1].Value;
            // 下载图片并获取相对路径
            var relativePath = await DownloadAndReplaceImage(post, imageUrl);
            // 替换文章内容中的图片链接
            content = content.Replace(imageUrl, relativePath);
        }

        return content;
    }

    private async Task<string> DownloadAndReplaceImage(Post post, string imageUrl)
    {
        // 下载图片并保存到本地
        using var httpClient = new HttpClient();
        var response = await httpClient.GetAsync(imageUrl);

        if (!response.IsSuccessStatusCode)
        {
            // 图片下载失败,返回原始链接
            return imageUrl;
        }

        // 生成图片保存路径
        var fileName = Path.GetFileName(imageUrl);
        var directory = Path.Combine("media", "blog", post.Id);
        Directory.CreateDirectory(Path.Combine(_environment.WebRootPath, directory));
        var imagePath = Path.Combine(directory, fileName);

        // 保存图片到本地
        var absolutePath = Path.Combine(_environment.WebRootPath, imagePath);
        using var fileStream = new FileStream(absolutePath, FileMode.Create);
        await response.Content.CopyToAsync(fileStream);

        // 返回相对路径
        return imagePath;
    }
}

修改1上传问题 已解决

支持直接上传md 支持上传单个md直接拖入上传 以及多个md放入压缩包内批量上传md文章

修改2 全站文章检索 只检索标题 需要文章内容 已解决 加content即可

全站文章检索 只检索标题 需要文章内容

修改3 vue后台类目搜索问题 已解决 后台文章标题搜索

placeholder="请输入关键字" @input="handleInputChange" >

之前的可能需要输入后失去焦点 才能搜索

        public IActionResult Blog(string keyword, int categoryId = 0, int page = 1, int pageSize = 5)
        {
            var posts = _context.posts
                .Where(a => a.Title!.Contains(keyword) || a.Content.Contains(keyword))
                .Include(a => a.Categories)
                .ToList();
            return View("Result", new SearchResultViewModel
            {
                Keyword = keyword,
                Posts = posts
            });
        }

.Contains(keyword)默认是区分大小写的所以 .Where(a => a.Title.ToLower().Contains(keyword.ToLower()) || a.Content.ToLower().Contains(keyword.ToLower())) 通过全部转换大小写实现 让搜索不区分大小写

如果分类被隐藏,就无法查看文章,理论上搜索的时候,文章就不能包含被隐藏的分类文章

    var visibleCategories = _context.categories.Where(c => c.Visible).Select(c => c.Id).ToList();
    var posts = _context.posts
        .Where(a => a.Title.ToLower().Contains(keyword.ToLower()) || a.Content.ToLower().Contains(keyword.ToLower()))
        .Where(a => visibleCategories.Contains(a.CategoryId)) // Filter by visible categories
        .Include(a => a.Categories)
        .ToList();

如果登陆的话就开放全部查询

           // 检查文章的分类是否都可见
           bool boolCategoriesVisible = post.Categories.Visible;
           // 如果有任何一个分类不可见,则不显示文章
           var UserName = HttpContext.Session.GetString("UserName");
           // 检查字符串是否为空
           bool isUserNameEmpty = string.IsNullOrEmpty(UserName);
           //// 如果用户未登录, 隐藏分类的文章
           if (isUserNameEmpty)
           {
               if (!boolCategoriesVisible)
               {
                   // 可以返回一个提示信息,或者直接重定向到其他页面
                   return Content("该文章所属分类已经隐藏分类,无法查看。");
               }
           }

修改4 vue图片不显示 以及快速删除

修改5 vue文章删除按钮启用和多选删除 问题 已解决

const deleteSelectedItems = () => {
  ElMessageBox.confirm("此操作将永久删除所选文章, 是否继续?", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    selectedPosts.value.forEach(post => {
      onItemDeleteClick2(post);
    });
    selectedPosts.value = []; // 清空选中的项
    hasSelection.value = false; // 将 hasSelection 设置为 false,表示没有选中项了
  }).catch(() => {
    // 取消操作
  });
}

// 多选删除
const deleteSelectedItems = (isMultiDelete) => {
  if (isMultiDelete) {
    selectedPosts.value.forEach(post => {
      onItemDeleteClick(post);
    });
    selectedPosts.value = []; // 清空选中的项
    hasSelection.value = false; // 将 hasSelection 设置为 false,表示没有选中项了
  } else {
    const post = // 根据需要获取要删除的帖子
    deleteItem(post.id)
      .then(res => ElMessage.success(`操作成功。${res.message}`))
      .catch(res => ElMessage.error(`操作失败。${res.message}`))
      .finally(() => loadBlogPosts())
  }
}

// 删除
const onItemDeleteClick = (post) => {
  ElMessageBox.confirm("此操作将永久删除该文章, 是否继续?", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    deleteItem(post.id)
      .then(res => ElMessage.success(`操作成功。${res.message}`))
      .catch(res => ElMessage.error(`操作失败。${res.message}`))
      .finally(() => loadBlogPosts())
  }).catch(() => ElMessage.warning('操作取消'))
}

上传 特殊zip出错 暂不处理

{
    "statusCode": 500,
    "successful": false,
    "message": "解压文件出错:Could not find a part of the path 'C:\\Users\\24243\\AppData\\Local\\Temp\\msohtmlclip1\\01\\clip_image002.gif'.",
    "data": null
}

博客修改6 解决图片随机获取的问题 已解决

DataAcqController 随机一言获取

WordCloud /js/bar.js

random-image f

1.拿不到七牛云key

2.改从拿文章第一张图片作为图片

@using Personalblog.Model.Entitys
@model List<Post>
@inject IConfiguration Configuration
    @foreach (var item in Model.Select((value, index) => new { Value = value, Index = index }))
    {
        <div class="carousel-item @if (item.Index == 0) { <text>active</text> }">   
            <div class="row rounded mb-4 shadow-sm box border" style="margin-left:3px;margin-right:3px">
                <div class="col-md-6">
                    <div class="p-4 p-md-5 mb-4 text-black">
                        <div class="display-6 fst-italic">@item.Value.Title</div>
                        <p class="lead my-3">@item.Value.Summary</p>
                        <p class="lead mb-0">
                            <a class="text-black fw-bold" asp-controller="Blog" asp-action="Post" asp-route-id="@item.Value.Id">
                                Continue reading...
                            </a>
                        </p>
                    </div>
                </div>想在后面显示一个图片,如果文章里面有图片,就显示文章里面的第一张图片,如果没有就显示默认图片   <img class="bd-placeholder-img img-fluid no-padding rounded" style="object-fit: cover; object-position: center;width:100%;height:500px"
       src="默认.jpg" alt="" />怎么实现
要在你的 Razor 视图中显示文章的图片,你需要做一些逻辑处理来检查文章是否有图片,并根据情况显示文章中的第一张图片或者默认图片。以下是你可以采取的一种方法:
@foreach (var item in Model.Select((value, index) => new { Value = value, Index = index }))
{
    <div class="carousel-item @if (item.Index == 0) { <text>active</text> }">   
        <div class="row rounded mb-4 shadow-sm box border" style="margin-left:3px;margin-right:3px">
            <div class="col-md-6">
                <div class="p-4 p-md-5 mb-4 text-black">
                    <div class="display-6 fst-italic">@item.Value.Title</div>
                    <p class="lead my-3">@item.Value.Summary</p>
                    <p class="lead mb-0">
                        <a class="text-black fw-bold" asp-controller="Blog" asp-action="Post" asp-route-id="@item.Value.Id">
                            Continue reading...
                        </a>
                    </p>
                </div>
            </div>

            <div class="col-md-6">
                @{
                    var firstImage = item.Value.Content.FirstOrDefault(c => c.Type == ContentType.Image);
                    string imageUrl = firstImage != null ? firstImage.Url : "/path/to/default.jpg";
                }
                <img class="bd-placeholder-img img-fluid no-padding rounded" style="object-fit: cover; object-position: center;width:100%;height:500px"
                    src="@imageUrl" alt="" />
            </div>
        </div>
    </div>
}
这段代码做了以下几件事情:

在遍历文章列表时,检查每篇文章的内容是否包含图片。
如果文章内容中包含图片,则使用第一张图片的 URL;否则,使用默认图片的 URL。
将图片显示在文章标题和摘要下面的部分。
你需要确保 Post 实体类中有一个属性来表示文章的内容(如 Content),其中包含了每篇文章的详细信息,包括图片等。根据你的实际情况,可能需要做一些调整来适应你的数据模型和业务逻辑。
错误(活动) CS1061 “char”未包含“Type”的定义,并且找不到可接受第一个“char”类型参数的可访问扩展方法“Type”(是否缺少 using 指令或程序集引用?)item.Value.Content.FirstOrDefault(c => c.Type == ContentType.Image);
对不起,这是我疏忽了。根据你的错误信息,似乎 Content 属性的类型是 char,而不是期望的类型。请确保 Content 属性的类型是一个包含文章内容的对象,该对象具有一些属性或方法来检查内容中是否包含图片,并获取第一张图片的 URL。

例如,如果你的 Content 类型是一个字符串数组,你可以这样处理:

csharp
var firstImage = item.Value.Content.FirstOrDefault(c => c.Contains("<img"));
string imageUrl = firstImage != null ? GetImageUrlFromHtml(firstImage) : "/path/to/default.jpg";
其中 GetImageUrlFromHtml 是一个方法,它从 HTML 标记中提取图片的 URL。

如果你的 Content 类型是另一个自定义类型,你需要确保该类型具有一个属性或方法来检查是否包含图片,并获取第一张图片的 URL。

请根据你的实际数据结构和业务逻辑做出相应调整。
严重性	代码	说明	项目	文件	行	禁止显示状态
错误(活动)	CS0103	当前上下文中不存在名称“GetImageUrlFromHtml” 用正则

如果你想使用正则表达式来提取 HTML 内容中的图片 URL,你可以尝试以下方法:

@functions {
    string GetImageUrlFromHtml(string htmlContent)
    {
        // 定义图片 URL 提取的正则表达式
        string pattern = "<img.+?src=[\"'](.+?)[\"'].*?>";
        
        // 使用正则表达式匹配 HTML 内容中的图片 URL
        Match match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
        
        // 如果找到匹配项,则返回第一个匹配项的图片 URL
        if (match.Success)
        {
            return match.Groups[1].Value;
        }
        else
        {
            // 如果没有匹配项,则返回默认图片的 URL
            return "/path/to/default.jpg";
        }
    }
}

@foreach (var item in Model.Select((value, index) => new { Value = value, Index = index }))
{
    <div class="carousel-item @if (item.Index == 0) { <text>active</text> }">   
        <div class="row rounded mb-4 shadow-sm box border" style="margin-left:3px;margin-right:3px">
            <div class="col-md-6">
                <div class="p-4 p-md-5 mb-4 text-black">
                    <div class="display-6 fst-italic">@item.Value.Title</div>
                    <p class="lead my-3">@item.Value.Summary</p>
                    <p class="lead mb-0">
                        <a class="text-black fw-bold" asp-controller="Blog" asp-action="Post" asp-route-id="@item.Value.Id">
                            Continue reading...
                        </a>
                    </p>
                </div>
            </div>

            <div class="col-md-6">
                string imageUrl = GetImageUrlFromHtml(item.Value.Content);
                <img class="bd-placeholder-img img-fluid no-padding rounded" style="object-fit: cover; object-position: center;width:100%;height:500px"
                    src="@imageUrl" alt="" />
            </div>
        </div>
    </div>
}

if (!string.IsNullOrEmpty(item.Value.Content 判断有没有图片 如果有显示图片,没有显示默认的jpg ))Content 文字内容格式是markdown格式,交给前端去解析
  {
      <img class="bd-placeholder-img img-fluid no-padding rounded" style="object-fit: cover; object-position: center; width:100%; height:500px" src="@item.Value.ImageUrl" alt="Article Image" />
  }
  else
  {
      <img class="bd-placeholder-img img-fluid no-padding rounded" style="object-fit: cover; object-position: center; width:100%; height:500px" src="默认.jpg" alt="Default Image" />
  }
  理解了,你想在 Razor 视图中根据 Markdown 格式的内容来判断是否显示图片,并根据情况显示不同的图片。下面是你可以使用的修改后的代码:
  @functions {
    string GetImageUrlFromMarkdown(string markdownContent)
    {
        // 正则表达式匹配 Markdown 中的图片链接
        string pattern = @"!\[.*?\]\((.*?)\)";
        
        // 使用正则表达式匹配 Markdown 内容中的图片链接
        Match match = Regex.Match(markdownContent, pattern, RegexOptions.IgnoreCase);
        
        // 如果找到匹配项,则返回第一个匹配项的图片链接
        if (match.Success)
        {
            return match.Groups[1].Value;
        }
        else
        {
            // 如果没有匹配项,则返回空字符串
            return string.Empty;
        }
    }
}

@foreach (var item in Model.Select((value, index) => new { Value = value, Index = index }))
{
    // 判断 Markdown 内容中是否包含图片链接
    bool hasImage = !string.IsNullOrEmpty(GetImageUrlFromMarkdown(item.Value.Content));

    <div class="carousel-item @if (item.Index == 0) { <text>active</text> }">   
        <div class="row rounded mb-4 shadow-sm box border" style="margin-left:3px;margin-right:3px">
            <div class="col-md-6">
                <div class="p-4 p-md-5 mb-4 text-black">
                    <div class="display-6 fst-italic">@item.Value.Title</div>
                    <p class="lead my-3">@item.Value.Summary</p>
                    <p class="lead mb-0">
                        <a class="text-black fw-bold" asp-controller="Blog" asp-action="Post" asp-route-id="@item.Value.Id">
                            Continue reading...
                        </a>
                    </p>
                </div>
            </div>

            <div class="col-md-6">
                @if (hasImage)
                {
                    <img class="bd-placeholder-img img-fluid no-padding rounded" style="object-fit: cover; object-position: center;width:100%;height:500px"
                        src="@GetImageUrlFromMarkdown(item.Value.Content)" alt="Article Image" />
                }
                else
                {
                    <img class="bd-placeholder-img img-fluid no-padding rounded" style="object-fit: cover; object-position: center;width:100%;height:500px"
                        src="默认.jpg" alt="Default Image" />
                }
            </div>
        </div>
    </div>
}
这个修改后的代码首先定义了一个 GetImageUrlFromMarkdown 方法,用于从 Markdown 内容中提取图片链接。然后在循环中,根据 Markdown 内容是否包含图片链接来判断是否显示图片,并选择合适的图片路径。
严重性	代码	说明	项目	文件	行	禁止显示状态 错误(活动)	CS0103	当前上下文中不存在名称“Regex”	Personalblog	F:\Merchant\merchant202403\Blogsys\personalblog-master\personalblog-master\Personalblog\Views\Home\Widegets\HomeTopPostCard.cshtml	12	

这个错误是因为在 Razor 视图中没有直接访问到 Regex 类,因此无法直接使用它。解决这个问题的方法是在 Razor 视图中导入 System.Text.RegularExpressions 命名空间,这样就可以直接使用 Regex 类了。下面是修复后的代码:

@using System.Text.RegularExpressions

@functions {
    string GetImageUrlFromMarkdown(string markdownContent)
    {
        // 正则表达式匹配 Markdown 中的图片链接
        string pattern = @"!\[.*?\]\((.*?)\)";
        
        // 使用正则表达式匹配 Markdown 内容中的图片链接
        Match match = Regex.Match(markdownContent, pattern, RegexOptions.IgnoreCase);
        
        // 如果找到匹配项,则返回第
[Something went wrong, please try again later.]

修改7添加登录功能实现文章隐藏 但是管理员可查看

前端vue接收 返回值

{
    "statusCode": 200,
    "successful": true,
    "message": "Ok",
    "data": {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Inp5IiwibmFtZSI6IjEiLCJqdGkiOiIzYjczY2U4Mi1lNjNmLTRhZjUtOTYwMC0yOTg0Njk5NWExZWEiLCJleHAiOjE3MTQ3MjM1ODAsImlzcyI6InN0YXJibG9nIiwiYXVkIjoic3RhcmJsb2ctYWRtaW4tdWkifQ.72wJONPtbvczH7uV-9pMRnaNPDiv0FrBbkBizhjgsso",
        "expiration": "2024-05-03T16:06:20+08:00"
    },
    "errorData": null
}

修改8 删除文章顺带删除了后台文件夹 完善单个文章导入 文章内部链接替换问题

单个文章 文件夹压缩 不走链接替换,走本地 图片替换

小计: 原文所有链接替换后 有弊端 可以下载,但是原文不能替换, 如果服务器宕机 只有db备份的话 怎么处理

单个上传 会自动下载文章内链接 进行替换 zip打包上传不会替换

修改9 login登陆后可以直接下载备份数据库

问题1 当前数据库 正在使用,无法直接返回下载 解决办法 copy一份返回

onclick="window.location.href='@Url.Action("Downloaddb", "Home")'"
  public IActionResult Downloaddb()
  {
      // 获取文件路径
      // 获取数据库文件路径
      string filePath = "app.db"; // 这里使用了一个静态路径,你也可以根据需要传入文件路径
      // 检查文件是否存在
      if (!System.IO.File.Exists(filePath))
      {
          return NotFound(); // 如果文件不存在,返回404 Not Found
      }
      // 设置文件的Content-Type
      string contentType = "application/db";

      // 设置文件下载的名称
      string fileName = "app.db";
      // 构造新的文件名
      string newFileName = "bak_" + fileName;
      // 删除之前  bak数据库文件
      if (System.IO.File.Exists(newFileName))
      {
          System.IO.File.Delete(newFileName);
      }
      //然后copy当前的app.db 为新 的newFileName然后下载 newFileName文件 
      // 复制 app.db 为新的 newFileName
      System.IO.File.Copy(filePath, newFileName); 
      // 返回新文件的下载链接
      return File(System.IO.File.ReadAllBytes(newFileName), contentType, newFileName);
  }

修改首页 显示最新的lasttime 最近的文章

blog分类目录页面 文章显示 改成 降序时间 优先显示最近一段时间的文章

舍弃推荐文章显示 更改 优先最近一段时间的文章 根据搜索判断是否登陆显示隐藏分类文章

python 的目录为啥没有生成

公告改短

修改10在每篇文章最后做一个同类型的相关文章推荐

var doc = Markdig.Markdown.Parse(post.Content); doc是"MarkdownDocument" Count = 42 
doc.ExtractToc();
public static List<TocNode>? ExtractToc(this MarkdownDocument document) {
    var headings = new List<Heading>();

    foreach (var heading in document.Descendants<HeadingBlock>()) {
        var item = new Heading {Level = heading.Level, Text = heading.Inline?.FirstChild?.ToString()};
        headings.Add(item);
    }

    var chineseTitleCount = 0;
    var slugMap = new Dictionary<string, int>();
    for (var i = 0; i < headings.Count; i++) {
        var item = headings[i];
        item.Id = i;
        if(i==7){}
        var text = item.Text ?? "";
        // 包含中文且不包含英文的转换为 section-1 格式
        if (Regex.IsMatch(text, "^((?![a-zA-Z]).)*[\u4e00-\u9fbb]((?![a-zA-Z]).)*$")) {
            item.Slug = chineseTitleCount == 0 ? "section" : $"section-{chineseTitleCount}";
            chineseTitleCount++;
        }
        // 其他情况处理为只包含英文数字格式
        else {
            item.Slug = Regex.Replace(text, @"[^a-zA-Z0-9\s]+", "")
                .Trim().Replace(" ", "-").ToLower();
            if (Char.IsDigit(item.Slug[0])) //第一个字符为数字则删除 因为id不支持数字开头
            {
                item.Slug = item.Slug.Substring(1);
            }
            if (slugMap.ContainsKey(item.Slug)) {
                item.Slug = $"{item.Slug}-{slugMap[item.Slug]++}";
            }
            else {
                slugMap[item.Slug] = 1;
            }
        }

        for (var j = i; j >= 0; j--) {
            var preItem = headings[j];
            if (item.Level == preItem.Level + 1) {
                item.Pid = j;
                break;
            }
        }
    }

    List<TocNode>? GetNodes(int pid = -1) {
        var nodes = headings.Where(a => a.Pid == pid).ToList();
        return nodes.Count == 0
            ? null
            : nodes.Select(a => new TocNode {Text = a.Text, Href = $"#{a.Slug}", Nodes = GetNodes(a.Id)}).ToList();
    }

    return GetNodes();}
在这报错了      if (Char.IsDigit(item.Slug[0])) //第一个字符为数字则删除 因为id不支持数字开头
根据你提供的代码,报错信息是因为在判断第一个字符是否为数字时,可能会出现 IndexOutOfRangeException,这是因为在某些情况下 item.Slug 可能是空字符串或者长度为 0 的字符串导致的。

为了解决这个问题,你可以在判断之前先确保 item.Slug 不为空并且长度大于 0,可以使用类似以下的条件判断:

csharp
if (!string.IsNullOrEmpty(item.Slug) && Char.IsDigit(item.Slug[0])) {
    // 如果第一个字符是数字,则进行相应处理
    item.Slug = item.Slug.Substring(1);
}
通过这样的条件判断,你可以避免出现 IndexOutOfRangeException,确保在操作字符串之前先进行必要的检查。

修改11

带有链接的图片可以直接显示吗

不改变原文章 下载图片,显示返回的是带图片,这样可以 数据都不需要带着走 那本身就是本地图片的怎么办呢

上传的文章和图片 是在同一级目录好
还是在文件名的目录下面好

做不到源文件始终不能动 很难随时迁移不需要文件夹,上传的时候 在访问的时候进行图片下载链接重替换 需要重构上传思路

单个上传 会自动下载文章内链接 进行替换 zip打包上传不会替换 那本地图片应该用zip

优化, 如果不是防盗链 应该不下载更换content {}
 // 删除检查文章中的外部图片和下载并进行替换
 单个上传不需要更新, 单个插入和更新也不需要更新替换 原本

文章打开报错只返回id的情况

文章打开报错只返回id的情况 {"Index was outside the bounds of the array."}

if (!string.IsNullOrEmpty(item.Slug)&&Char.IsDigit(item.Slug[0])) //第一个字符为数字则删除 因为id不支持数字开头

修改12 202411.18

1.所有引用改到本地

2.博客前端复制 修改 不带入language

lib/Blog/BlogPost.js

原来是data-clipboard-target="#code-block-${t}"  包含了language前端标签
修改到下一级代码标签即可 data-clipboard-target="#code-block-${t} code">

end

标题不能带花括号,不然前端无法获取标题,如文章打开报错只返回id的情况 {"Index was outside the bounds of the array."}