This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
和"M404:"的战争 让老旧的东西再次发挥作用
Sunday, 23 November 2025
一. 导言导言 导言 导言 导言
我最近从<2004-2009 @( https{://}<www_. mostlylucid.Qnet}/blog@/}/}入口)>进口了我的旧文章,并发现了这一点。 一切一切 外部链接指向十年前消失的站点 , 旧的URL计划不再符合当前结构
与Semantic Search系列和功能一同即将到来,
问题分为三部分::
内部链接 @-_ 固定在进口过程中使用我的 归档导入工具@.我的旧文章使用旧的 URL 计划互相引用@,}所以我重写了那些作为移民的一部分的.
外部链接 @(}% autreating\ ) @M- 这是与外部资源的链接, 自此消失后#, @% 移动@,}或成为完全不同的站点 @.}一些来自<2006?> Gone.}的文件的链接
收到的请求 @- @People @(#和搜索引擎@)}仍然尝试访问旧的 URL,比如
/archive/2006/05/15/123.aspx语义搜索系统往往能找出他们到底在追逐什么 即使没有精确的鼻涕虫匹配
现在,#I 能够 @.}今天工作的这个网站下个月可能会消失@.。 通过定期在运行时使用“- checking_, ”来处理此系统自动抓取 未来 不仅在进口时间存在的内容@. @%
本篇文章涵盖我的方法 :
- 流出链接:
BrokenLinkArchiveMiddleware@-_@ org 快照取代已死外部链接 - 接入链接: 带有语义搜索的 @-处理器 即使从旧的 URL 方案中也能找到正确的内容
- 学习系统@: 网站如何随着时间推移从用户点击中变得更聪明?
- A. 背景处理: 检查链接而不阻截请求
旧内容问题
@' service_situation at a professional committee\ :QQI it'}您在#2006?} Gone中链接到的绝佳博客文章: 文档站点=?}重新构建了三次
graph TD
A[User requests old post] --> B{Link valid?}
B -->|Yes| C[Happy user]
B -->|No| D[404 Error]
D --> E[Frustrated user]
E --> F[User leaves]
style D stroke:#ef4444,stroke-width:3px
style F stroke:#ef4444,stroke-width:3px
style C stroke:#10b981,stroke-width:3px
幼稚的方法是修复手动连接@.}但是当你得到了上百个有数千个链接的邮箱时... ...,
建筑结构概览
该系统有两个主要组成部分,以协同配合的方式工作。
flowchart TB
subgraph Incoming["Incoming Requests"]
A[User Request] --> B{Page exists?}
B -->|Yes| C[Render Page]
B -->|No| D[404 Handler]
D --> E{Learned redirect?}
E -->|Yes| F[301 Permanent Redirect]
E -->|No| G{High-confidence match?}
G -->|Yes| H[302 Temporary Redirect]
G -->|No| I[Show suggestions]
I --> J[User clicks suggestion]
J --> K[Learn redirect]
end
subgraph Outgoing["Outgoing Links"]
C --> L[BrokenLinkArchiveMiddleware]
L --> M[Extract all links]
M --> N[Register for checking]
L --> O[Replace broken links]
O --> P[Archive.org URLs]
O --> Q[Semantic search results]
O --> R[Remove dead links]
end
style F stroke:#10b981,stroke-width:3px
style H stroke:#f59e0b,stroke-width:3px
style P stroke:#3b82f6,stroke-width:3px
style Q stroke:#8b5cf6,stroke-width:3px
处理流出链接
破断的连环工程中继器
这个中件拦截的 HTML 响应并做了三件事:
- 为背景检查提取所有链接
- 将已知断开的外部链接替换为归档@. @org 版本
- 使用语义搜索以寻找内部断开链接的替换符
关键的观点是 我们想要找到档案... .@org 在文章写作前后@._Q@2024}来自 @MS K1 文章的快照或许会引用完全不同的内容 @.}所以我们查一查博客上的文章:#'{s公布日期,
这里的' 核心结构@: @%
public partial class BrokenLinkArchiveMiddleware(
RequestDelegate next,
ILogger<BrokenLinkArchiveMiddleware> logger,
IServiceScopeFactory serviceScopeFactory)
{
public async Task InvokeAsync(
HttpContext context,
IBrokenLinkService? brokenLinkService,
ISemanticSearchService? semanticSearchService)
{
// Only process HTML responses for blog pages
if (!ShouldProcessRequest(context))
{
await next(context);
return;
}
// Capture the response so we can modify it
var originalBodyStream = context.Response.Body;
using var responseBody = new MemoryStream();
context.Response.Body = responseBody;
await next(context);
// Process the HTML response
if (IsSuccessfulHtmlResponse(context, responseBody))
{
var html = await ReadResponseAsync(responseBody);
html = await ProcessLinksAsync(html, context, brokenLinkService, semanticSearchService);
await WriteModifiedResponseAsync(originalBodyStream, html, context);
}
else
{
await CopyOriginalResponseAsync(responseBody, originalBodyStream);
}
}
}
提取链接
我们用生成的正gex 来拉出全部 href 属性@. @%I [GeneratedRegex] Q.NET 中的属性向我们提供编译@-time regex generation @,}这既要快,又要分拨 #-free:}
[GeneratedRegex(@"<a[^>]*\shref\s*=\s*[""']([^""']+)[""'][^>]*>",
RegexOptions.IgnoreCase | RegexOptions.Compiled)]
private static partial Regex HrefRegex();
private List<string> ExtractAllLinks(string html, HttpRequest request)
{
var links = new List<string>();
var matches = HrefRegex().Matches(html);
foreach (Match match in matches)
{
var href = match.Groups[1].Value;
// Skip special links (anchors, mailto, etc.)
if (SkipPatterns.Any(p => href.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
continue;
if (Uri.TryCreate(href, UriKind.Absolute, out var uri))
{
if (uri.Scheme == "http" || uri.Scheme == "https")
links.Add(href);
}
else if (href.StartsWith("/"))
{
// Convert relative URLs to absolute for tracking
var baseUri = new UriBuilder(request.Scheme, request.Host.Host,
request.Host.Port ?? (request.Scheme == "https" ? 443 : 80));
links.Add(new Uri(baseUri.Uri, href).ToString());
}
}
return links.Distinct().ToList();
}
现在你可以相当合理地使用 Html 灵活性包 / @https@://github.com}/AngleSharp/}安格勒沙爾普或类似的 HTML @(和這裡的其他{)采訪者 對於更複雜的假設,但為此目的超過於殺害@-我們只需要快速提取 hrefs_.
背景检查的注册链接
我们不想在检查链接时阻断响应@._% 取代#,}我们解除了背景任务 @:
var allLinks = ExtractAllLinks(html, context.Request);
var sourcePageUrl = context.Request.Path.Value;
if (allLinks.Count > 0)
{
// Fire and forget - don't block the response
_ = Task.Run(async () =>
{
using var scope = serviceScopeFactory.CreateScope();
var scopedService = scope.ServiceProvider.GetRequiredService<IBrokenLinkService>();
await scopedService.RegisterUrlsAsync(allLinks, sourcePageUrl);
});
}
注注: IServiceScopeFactory -我们需要一个新的范围, 因为原始请求@'s specasted services 在我们完成背景任务前将会被处理 @.}
重要: 这是 最终最终一致性 型号@. 任何 首次发现链接@,}它被排队作为验证 {-我们不知道它是否坏了吗? '*%s broken yet.}背景服务检查 它的<(>HEAD请求),} 如果它打破了='}#,}取回一个归档 *.}org replace.} 下一个 访问该页面的人会看到固定链接@. @% 对于一个定期流量的博客来说, 这通常意味着断开的连接在第一次发现后数小时内就被固定了 @.# 美丽的是我们不得不猜测哪些链接会被打破 #-}我们只是自动验证它们是否所有#.}
替换断开的链接
一旦我们知道一个链接被打破, 并有一个归档@.org 替换 @(。 从先前的背景检查),}我们用有用的工具提示来交换它们
foreach (var (originalUrl, archiveUrl) in archiveMappings)
{
if (html.Contains(originalUrl))
{
var tooltipText = $"Original link ({originalUrl}) is dead - archive.org version used";
var originalPattern = $"href=\"{originalUrl}\"";
var archivePattern = $"href=\"{archiveUrl}\" class=\"tooltip tooltip-warning\" " +
$"data-tip=\"{tooltipText}\" data-original-url=\"{originalUrl}\"";
html = html.Replace(originalPattern, archivePattern);
}
}
对于内部断开的链接@ ,, 我们首先尝试语义搜索@ I:
if (isInternal && semanticSearchService != null)
{
var replacement = await TryFindSemanticReplacementAsync(
brokenUrl, semanticSearchService, request, cancellationToken);
if (replacement != null)
{
html = ReplaceHref(html, brokenUrl, replacement);
continue;
}
}
// No replacement found - convert to plain text
html = RemoveHref(html, brokenUrl);
Part {2:} 背景链接检查
中队列通过数据库表格@,}和 a 发现背景处理链接 BrokenLinkCheckerBackgroundService 处理实际检查@. @%
服务按小时运行,做两件事情:
- 检查链接有效性 -HEAD 请求查看链接是否还活着
- 获取归档@. @org URL 找到最接近历史的快照
校对者使用自定义用户“-”表示身份并链接到网站@:}
request.Headers.UserAgent.ParseAdd(
"Mozilla/5.0 (compatible; MostlylucidBot/1.0; +https://www.mostlylucid.net)");
这让站点管理者看到什么 ' @%s击中他们的服务器@,}他们可以搜索我们,如果他们是'.}我们还请求在检查之间拨打+2秒 以避免敲击任何人_'}*s services *#.}
关键是 定期重新@- @ checked上周工作过的链接在今天可能已绝迹@. @The service picks up link that hasned '}在最后的24时间里被检查过,并再次验证了这些链接。 但是一旦我们找到了一个归档文件.},
sequenceDiagram
participant BG as Background Service
participant DB as Database
participant Web as External Sites
participant Archive as Archive.org CDX API
loop Every Hour
BG->>DB: Get links not checked in 24h (batch of 20)
loop For each link
BG->>Web: HEAD request
Web-->>BG: Status code
BG->>DB: Update link status + LastCheckedAt
end
BG->>DB: Get broken links needing archive lookup
loop For each broken link
BG->>DB: Look up source post publish date
BG->>Archive: CDX API query (filtered by date)
Archive-->>BG: Closest snapshot
BG->>DB: Store archive URL (permanent)
end
end
CDX API 存档@. @org
Achild.org 提供 CDX {(} Capture Inview_)} API, 它让我们查询快照@.} 智能位元在日期前过滤:}
private async Task<string?> GetArchiveUrlAsync(
string originalUrl,
DateTime? beforeDate,
CancellationToken cancellationToken)
{
var queryParams = new List<string>
{
$"url={Uri.EscapeDataString(originalUrl)}",
"output=json",
"fl=timestamp,original,statuscode",
"filter=statuscode:200", // Only successful responses
"limit=1"
};
// Find snapshot closest to the blog post's publish date
if (beforeDate.HasValue)
{
queryParams.Add($"to={beforeDate.Value:yyyyMMdd}");
queryParams.Add("sort=closest");
queryParams.Add($"closest={beforeDate.Value:yyyyMMdd}");
}
var apiUrl = $"https://web.archive.org/cdx/search/cdx?{string.Join("&", queryParams)}";
// ... fetch and parse response
return $"https://web.archive.org/web/{timestamp}/{original}";
}
这表示如果我在“2008”中写下一篇文章, 连结到一些资源@,}(I') 会收到来自「.」的存档快照。
情况追踪链接
我们追踪与一个合适的实体的链接 :
[Table("broken_links", Schema = "mostlylucid")]
public class BrokenLinkEntity
{
public int Id { get; set; }
public string OriginalUrl { get; set; } = string.Empty;
public string? ArchiveUrl { get; set; }
public bool IsBroken { get; set; } = false;
public int? LastStatusCode { get; set; }
public DateTimeOffset? LastCheckedAt { get; set; }
public int ConsecutiveFailures { get; set; } = 0;
public string? SourcePageUrl { get; set; } // For publish date lookup
}
周期性 Re- check
抓取未来断裂点的关键是重新@ - @ checking.}( 此服务查询) 。 最近已验证该链接的连接@ MPK2 @ t
public async Task<List<BrokenLinkEntity>> GetLinksToCheckAsync(int batchSize, CancellationToken cancellationToken)
{
var cutoff = DateTimeOffset.UtcNow.AddHours(-24);
return await _dbContext.BrokenLinks
.Where(x => x.LastCheckedAt == null || x.LastCheckedAt < cutoff)
.OrderBy(x => x.LastCheckedAt ?? DateTimeOffset.MinValue) // Oldest first
.Take(batchSize)
.ToListAsync(cancellationToken);
}
这意味着每个链接每天至少一次被重新% - @ valiated 至少一次@ MS K1} 如果一个先前的工作连接开始返回 {404} @s,} we'} ConsecutiveFailures 字段让我们略微原谅\ - {我们不做\ I' t标记链接在短暂错误后断裂@.}\
处理收到的请求
现在对硬币的另一面来说, “: ” 人们“(”和搜索引擎“MS K2 ”要求有URL的 '“t 存在” 。 这包括‘:’
- 旧的URL办法 @-我的古老博客使用路径,
/archive/2006/05/15/123.aspx@._% 其中一些仍然被索引化@,}书签{,}或从其他网站连接到#.} - Typos 泰波 - @ someone fat@-#finger the URL 或者复制错误的.}
- 部分子弹 搜索引擎有时会索引怪异的碎片
语义搜索系统在此特别有用 @ . @ 即使没有直接的子弹匹配@ MS K1} 我们也可以从所请求的 URL 中提取有意义的术语, 并找到与 . 字词相匹配的内容。 这个系统既了解弹体, 也嵌入了每个 post@,}的数据, 这样它就可以找到“ "Qclose ” 足够接近@ MPK5 匹配甚至完全不同的 網址结构@ I. {
为何不叫米德尔 ??
我的第一个本能是处理中间软件的重置 - 拦截请求\ , 检查已知的重定向\ , 和重新定位之前甚至踢前 能够 和这里的',它看起来像什么?
// DON'T DO THIS - runs on EVERY request
public class SlugRedirectMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context, ISlugSuggestionService? service)
{
if (context.Request.Path.StartsWithSegments("/blog"))
{
var targetSlug = await service.GetAutoRedirectSlugAsync(slug, language);
if (!string.IsNullOrWhiteSpace(targetSlug))
{
context.Response.Redirect($"/blog/{targetSlug}", permanent: true);
return;
}
}
await next(context);
}
}
问题?}这持续着 每项要求 至 /blog/*@.#' 在每个页面视图上都有一个数据库查询@,(即使对于完全有效的 URLs%.) 对于一个有体面交通的博客来说,你{'(无缘无故地在数据库中敲打_99%(时间为<.>)*}
如果 ASP=.NET 已决定页面是否已存在 '}{,}, 时当时 我们检查重定向@.}在有效页面上没有浪费的查询 @.
在ASP=.NET MVC的最初几天, 我们如何制作友好的 URLs 工作?.Scott Guthrie'S平面演示开始MVC使用这个方法。 ;勾钩进入IMS @404处理系统。
学习系统
当有人点击“ 404,” 时, 我们用模糊字符串匹配和“ 可选” @ MS K2 语义搜索@ . 如果单击一个建议\ , 我们记录在足够多的点击后 “ MSKO5 ” 充分自信地开始自动“ I- 重新定向” @.
stateDiagram-v2
[*] --> RequestReceived
RequestReceived --> RoutingCheck
RoutingCheck --> PageFound: Exists
RoutingCheck --> NotFound: 404
PageFound --> [*]
NotFound --> ErrorController
ErrorController --> CheckLearnedRedirect
CheckLearnedRedirect --> Redirect301: Has learned redirect
CheckLearnedRedirect --> CheckHighConfidence: No learned redirect
CheckHighConfidence --> Redirect302: Score >= 0.85 & gap >= 0.15
CheckHighConfidence --> ShowSuggestions: Lower confidence
ShowSuggestions --> UserClicks
UserClicks --> RecordClick
RecordClick --> UpdateWeight
UpdateWeight --> CheckThreshold
CheckThreshold --> EnableAutoRedirect: Weight >= 5 & confidence >= 70%
CheckThreshold --> [*]: Below threshold
EnableAutoRedirect --> [*]
“404”处理器
所有转方向逻辑 生命在 ErrorController@. @ASP#.NET@'%s UseStatusCodePagesWithReExecute 当发生“% 404” 事件时, 请通过我们的错误处理器执行请求@ MS K0 @ option: check
public class ErrorController(
BaseControllerService baseControllerService,
ILogger<ErrorController> logger,
ISlugSuggestionService? slugSuggestionService = null) : BaseController(baseControllerService, logger)
{
[Route("/error/{statusCode}")]
[HttpGet]
public async Task<IActionResult> HandleError(int statusCode, CancellationToken cancellationToken = default)
{
var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
switch (statusCode)
{
case 404:
// Check for auto-redirects before showing 404 page
var autoRedirectResult = await TryAutoRedirectAsync(statusCodeReExecuteFeature, cancellationToken);
if (autoRedirectResult != null)
return autoRedirectResult;
var model = await CreateNotFoundModel(statusCodeReExecuteFeature, cancellationToken);
return View("NotFound", model);
case 500:
return View("ServerError");
default:
return View("Error");
}
}
}
缩略 TryAutoRedirectAsync 方法既处理学习的重定向,又处理高端@- @sustrie first @-}时间匹配:
private async Task<IActionResult?> TryAutoRedirectAsync(
IStatusCodeReExecuteFeature? statusCodeReExecuteFeature,
CancellationToken cancellationToken)
{
if (slugSuggestionService == null || statusCodeReExecuteFeature == null)
return null;
var originalPath = statusCodeReExecuteFeature.OriginalPath ?? string.Empty;
var (slug, language) = ExtractSlugAndLanguage(originalPath);
// First: check for learned redirects (user previously clicked a suggestion)
// These get 301 Permanent Redirect - confirmed patterns
var learnedTargetSlug = await slugSuggestionService.GetAutoRedirectSlugAsync(
slug, language, cancellationToken);
if (!string.IsNullOrWhiteSpace(learnedTargetSlug))
{
var redirectUrl = BuildRedirectUrl(learnedTargetSlug, language);
logger.LogInformation("Learned auto-redirect (301): {Original} -> {Target}", originalPath, redirectUrl);
return RedirectPermanent(redirectUrl);
}
// Second: check for high-confidence first-time matches
// These get 302 Temporary Redirect until confirmed by user clicks
var firstTimeTargetSlug = await slugSuggestionService.GetFirstTimeAutoRedirectSlugAsync(
slug, language, cancellationToken);
if (!string.IsNullOrWhiteSpace(firstTimeTargetSlug))
{
var redirectUrl = BuildRedirectUrl(firstTimeTargetSlug, language);
logger.LogInformation("First-time auto-redirect (302): {Original} -> {Target}", originalPath, redirectUrl);
return Redirect(redirectUrl);
}
return null; // No redirect - show suggestions
}
相似位置分布
推荐服务使用 Levenshtein 距离@(edit 距离 @)}加上一些超自然现象 @MS K2}
private double CalculateSimilarity(string source, string target)
{
if (string.Equals(source, target, StringComparison.OrdinalIgnoreCase))
return 1.0;
source = source.ToLowerInvariant();
target = target.ToLowerInvariant();
// Levenshtein distance converted to similarity (0-1)
var distance = CalculateLevenshteinDistance(source, target);
var maxLength = Math.Max(source.Length, target.Length);
var levenshteinSimilarity = 1.0 - (double)distance / maxLength;
// Bonus if one string contains the other
var substringBonus = (source.Contains(target) || target.Contains(source)) ? 0.2 : 0.0;
// Bonus for common prefix (catches typos at the end)
var prefixLength = GetCommonPrefixLength(source, target);
var prefixBonus = (double)prefixLength / Math.Min(source.Length, target.Length) * 0.1;
return Math.Min(1.0, levenshteinSimilarity + substringBonus + prefixBonus);
}
从用户行为中学习
当用户点击一个建议时 @ ,我们记录并更新信任评分@ MS K1#
public async Task RecordSuggestionClickAsync(
string requestedSlug,
string clickedSlug,
string language,
int suggestionPosition,
double originalScore,
CancellationToken cancellationToken = default)
{
var redirect = await _context.SlugRedirects
.FirstOrDefaultAsync(r =>
r.FromSlug == normalizedRequestedSlug &&
r.ToSlug == normalizedClickedSlug &&
r.Language == language,
cancellationToken);
if (redirect == null)
{
redirect = new SlugRedirectEntity
{
FromSlug = normalizedRequestedSlug,
ToSlug = normalizedClickedSlug,
Language = language,
Weight = 1
};
_context.SlugRedirects.Add(redirect);
}
else
{
redirect.Weight++;
redirect.LastClickedAt = DateTimeOffset.UtcNow;
}
redirect.UpdateConfidenceScore();
await _context.SaveChangesAsync(cancellationToken);
}
信心评分计数既考虑点击又考虑印象 @:
public void UpdateConfidenceScore()
{
var total = Weight + ShownCount;
ConfidenceScore = total > 0 ? (double)Weight / total : 0.0;
// Enable auto-redirect after 5+ clicks with 70%+ confidence
if (Weight >= AutoRedirectWeightThreshold &&
ConfidenceScore >= AutoRedirectConfidenceThreshold)
{
AutoRedirect = true;
}
}
two - @ Tier 自动@ MS K1 @ redirect
我们有两层自动重定向 :
第一次- 高一 一次-信任如果相似的分数是 @>= @ @0.85, 并且有#' 与第二个“-}最佳匹配”的差幅那么大的话 我们马上用“NSK6”重新定位
**学习重定向@(301) @%**在“5+”点击「70%+」后,
public async Task<string?> GetFirstTimeAutoRedirectSlugAsync(
string requestedSlug,
string language,
CancellationToken cancellationToken = default)
{
var suggestions = await GetSuggestionsWithScoreAsync(requestedSlug, language, 2, cancellationToken);
if (suggestions.Count == 0) return null;
var topMatch = suggestions[0];
// Need high confidence
if (topMatch.Score < 0.85) return null;
// If there's only one suggestion with high score, redirect
if (suggestions.Count == 1) return topMatch.Post.Slug;
// Multiple suggestions - only redirect if there's a clear winner
var scoreGap = topMatch.Score - suggestions[1].Score;
if (scoreGap >= 0.15)
return topMatch.Post.Slug;
return null; // Too close to call - show suggestions instead
}
团结在一起
当Middroup 使感官(和当它 '"T"MSSK2"
用于 退出链接@,中间器械是正确选择#. BrokenLinkArchiveMiddleware 需要来拦截 HTML 响应 之后 已经完成,但 之前 发送到客户端@. @ there @'}没有其它明智的地方可以做这个-}我们需要修改响应流{,}和中间软件是专门设计为“.”的
用于 输入的重定向, 中间工具是错误的选择 @.We'd 正在运行每个数据库查询 /blog/* 此 URL 可能需要重定向 < . {404\ } <% MS K4\ > > 处理器方法只有在我们已经确定该页面是否存在{'}\ t } @-} 有效度更高时才运行此逻辑@.}
// In Program.cs
app.UseStatusCodePagesWithReExecute("/error/{0}"); // Handles 404s through ErrorController
app.UseStaticFiles();
app.UseRouting();
// ... other middleware
app.UseBrokenLinkArchive(); // Process outgoing links in responses (ONLY for middleware)
流体看起来像这个:
flowchart LR
subgraph Request["Request Processing"]
direction TB
A[Request] --> B[Static Files]
B --> C[Routing]
C --> D{Page exists?}
D -->|Yes| E[MVC/Endpoints]
D -->|No| F[ErrorController 404]
F --> G{Auto-redirect?}
G -->|Yes| H[Redirect]
G -->|No| I[Show suggestions]
end
subgraph Response["Response Processing"]
direction TB
E --> J[BrokenLinkArchiveMiddleware]
J --> K[Link Extraction]
K --> L[Link Replacement]
L --> M[Response]
end
subgraph Background["Background Processing"]
direction TB
N[BrokenLinkCheckerService] --> O[Check URLs]
O --> P[Fetch Archive.org]
P --> Q[Update Database]
end
K -.->|Register links| N
style F stroke:#ef4444,stroke-width:3px
style J stroke:#8b5cf6,stroke-width:3px
style N stroke:#f59e0b,stroke-width:3px
结论结论
这个系统已经运行了一段时间, 它相当满意地看着它学习 .。 数据库逐渐填满了重新定位的映射@,断开的链接 得到归档.org 替换{, 用户很少看到裸体的#404 #.#
关键原则#:
- Don't 区块请求 -在昂贵的操作中使用背景处理
- 向用户学习 他们的点击是有价值的信号
- 使用自动@- redirects 保持稳妥 @- 只有当您有信心时才会重新定向@ '
- Time - @avore 归档 @- 找到内容撰写时的归档@.org快照
- 优减降解 如果服务是'#t 可用 @,}请保持正常状态
语义搜索总是能找到合适的替代词 @.}但是它比留下成千上万个关于#.的断开链接更好看
即将到来#:语义搜索深海底
本周 @( @w /}{MSK 1}c MS K2}11月_2025)#'}将公布我的语义搜索序列。 该系列将会更详细地说明矢量@-}以矢量为基地的搜索如何在hun.}下工作。 这个系列将包括嵌入下一代=,QDrant矢量存储}♪,},以及它是如何与这个“404处理系统”连接的? ISemanticSearchService.SearchAsync @'}会真正找到您想要查看的“.” 位置上的 “'Qs ”
完整的源在存储器中, 如果您想为您自己的工程nick any of it@ . @