问题复现
使用 .NET Framework 开发的网站项目,用 QQ 浏览器访问无法登录成功,用其它浏览器(如 Edge)没有问题。
只有访问 https 地址时出现问题。
原因
在 HTTPS 协议下,现代浏览器(特别是 QQ 浏览器)会强制执行安全策略。根据规范,当 Cookie 设置了 SameSite=None 时,必须同时设置 Secure 属性,否则浏览器会静默拒绝(丢弃)该 Cookie。
解决方案
方法一:如果您是通过 web.config 配置的,请确保 <system.web> 节点下的 <httpCookies> 设置正确,并且您的 .NET Framework 版本支持这些属性(4.7.2+)。
<system.web>
<httpCookies sameSite="None" requireSSL="true" />
<sessionState ... />
</system.web>方法二:在 Global.asax 文件的 Application_PostAuthenticateRequest 或 Application_EndRequest 事件中,强制为 Cookie 添加 Secure 属性。
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
// 仅在 HTTPS 环境下处理
if (Request.IsSecureConnection)
{
HttpCookie sessionCookie = Response.Cookies["ASP.NET_SessionId"];
if (sessionCookie != null)
{
// 强制设置 SameSite=None 和 Secure
sessionCookie.SameSite = SameSiteMode.None;
sessionCookie.Secure = true;
Response.Cookies.Set(sessionCookie);
}
}
}检验
确认 Set-Cookie 的值变为:
ASP.NET_SessionId=...; path=/; HttpOnly; SameSite=None; Secure以下是 Spectre.Console 的核心功能示例,涵盖最常用的输出和交互场景:
1. 基础富文本输出

using Spectre.Console;
// 使用 Markup 语法(类似 BBCode)
AnsiConsole.Markup("[bold green]成功![/] 文件已保存。\n");
AnsiConsole.Markup("[red]错误:[/] 无法连接到服务器。\n");
// 混合样式
AnsiConsole.Markup("[underline blue]https://example.com[/]\n");
// 自动换行写
AnsiConsole.Write(new Panel("[yellow]警告[/] 磁盘空间不足")
.Header("系统通知")
.Border(BoxBorder.Rounded));2. 表格

var table = new Table();
table.AddColumn("[u]ID[/]");
table.AddColumn(new TableColumn("[u]名称[/]").Centered());
table.AddColumn("[u]状态[/]");
table.AddRow("1", "订单服务", "[green]运行中[/]");
table.AddRow("2", "支付网关", "[red]离线[/]");
table.AddRow("3", "消息队列", "[yellow]警告[/]");
AnsiConsole.Write(table);3. 进度条 / 状态指示


// 带进度条的循环任务
await AnsiConsole.Progress()
.StartAsync(async ctx =>
{
var task1 = ctx.AddTask("[green]下载文件[/]", maxValue: 100);
var task2 = ctx.AddTask("[green]处理数据[/]", maxValue: 100);
while (!ctx.IsFinished)
{
task1.Increment(1.5);
task2.Increment(0.8);
await Task.Delay(50);
}
});
// 不确定时长的旋转状态
await AnsiConsole.Status()
.StartAsync("正在连接...", async ctx =>
{
await Task.Delay(3000); // 模拟工作
AnsiConsole.MarkupLine("[green]连接成功![/]");
});4. 交互式提示

// 确认
if (AnsiConsole.Confirm("是否继续安装?"))
{
// 执行安装
}
// 文本输入(带验证)
var name = AnsiConsole.Prompt(
new TextPrompt<string>("请输入用户名:")
.ValidationErrorMessage("[red]用户名不能为空[/]")
.Validate(input => !string.IsNullOrWhiteSpace(input)));
// 选择列表
var fruit = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("请选择最喜欢的水果")
.AddChoices(new[] { "苹果", "香蕉", "橙子", "葡萄" }));
AnsiConsole.MarkupLine($"你选择了:[green]{fruit}[/]");
// 多选
var colors = AnsiConsole.Prompt(
new MultiSelectionPrompt<string>()
.Title("请选择颜色")
.AddChoices(new[] { "红色", "绿色", "蓝色", "黄色" }));5. 树形结构

var root = new Tree("[yellow]项目结构[/]");
var src = root.AddNode("src");
src.AddNode("Program.cs");
src.AddNode("Services");
src.AddNode("Models");
var tests = root.AddNode("tests");
tests.AddNode("UnitTests");
AnsiConsole.Write(root);6. 布局 / 网格

var layout = new Layout("Root")
.SplitColumns(
new Layout("Left").Size(30),
new Layout("Right")
.SplitRows(
new Layout("Top"),
new Layout("Bottom")));
layout["Left"].Update(new Panel("导航栏"));
layout["Top"].Update(new Panel("主内容区"));
layout["Bottom"].Update(new Panel("日志输出"));
AnsiConsole.Write(layout);7. 实时更新

var table = new Table().AddColumn("Time").AddColumn("Message");
table.AddRow("10:00", "系统启动");
await AnsiConsole.Live(table)
.StartAsync(async ctx =>
{
for (int i = 1; i <= 5; i++)
{
await Task.Delay(1000);
table.AddRow($"10:0{i}", $"事件 {i}");
ctx.Refresh(); // 刷新显示
}
});8. 带样式的异常显示

try
{
throw new InvalidOperationException("操作失败");
}
catch (Exception ex)
{
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenPaths);
}9. 日历

var calendar = new Calendar(2026, 4);
calendar.AddCalendarEvent(2026, 4, 22); // 标记日期
calendar.HighlightStyle(Style.Parse("yellow bold"));
AnsiConsole.Write(calendar);10. 条形图 / 柱状图

AnsiConsole.Write(new BarChart()
.Width(60)
.Label("[green bold]项目进度[/]")
.CenterLabel()
.AddItem("后端", 80, Color.Green)
.AddItem("前端", 65, Color.Blue)
.AddItem("测试", 40, Color.Red));11. 分解图

AnsiConsole.Write(new BreakdownChart()
.Width(60)
.AddItem("CPU", 45, Color.Red)
.AddItem("内存", 30, Color.Blue)
.AddItem("磁盘", 25, Color.Green));12. 规则线

AnsiConsole.Write(new Rule("[red]警告区域[/]").RuleStyle("red").LeftJustified());
AnsiConsole.WriteLine("内容");
AnsiConsole.Write(new Rule().RuleStyle("green"));13. 文本样式

// 大字标题
AnsiConsole.Write(new FigletText("Hello").Color(Color.Green));
// Emoji
AnsiConsole.Markup(":check_mark_button: 成功 :cross_mark: 失败");14. 网格

var grid = new Grid();
grid.AddColumn(new GridColumn().NoWrap().PadRight(4));
grid.AddColumn();
grid.AddRow("[b]名称[/]", "Spectre.Console");
grid.AddRow("[b]版本[/]", "0.49.1");
grid.AddRow("[b]许可[/]", "MIT");
AnsiConsole.Write(grid);安装虚拟声卡 VB-Cable 搞定。
若重启后未生效,在服务中启动 Windows Audio、Windows Audio Endpoint Builder 和 Multimedia Class Scheduler(如果有),并设置为自动启动。
如何希望使用 OBS 录制系统声音,在 OBS 菜单 - 文件 - 设置 - 音频 - 桌面音频 中选择正确的音源(如“远程音频”)。
今天发现在 .NET Framework 和 .NET 9 中使用 System.Uri.EscapeDataString() 方法对字符串进行编码,会产生不同的结果。
譬如“(”符号,前者视其为非保留字符,不进行转义,后者视为保留字符,转义为“%28”。
原因是 .NET Framework 4.8 主要遵循 RFC 2396,而 .NET 9 遵循 RFC 3986。
在跨平台签名验证场景中,对 URL 编码的一致性要求极高,任何细微差别都会导致签名校验失败。
以下是以 RFC 3986 标准为核心、优先使用各平台内置的高一致性方案。
对于 .NET 9,直接使用 Uri.EscapeDataString()。
string encodedData = System.Uri.EscapeDataString(dataToEncode);对于 .NET Framework,以下是一个遵循 RFC 3986 严格标准的自定义编码方法示例。
static string Rfc3986EscapeDataString(string input)
{
// 定义 RFC 3986 中明确的未保留字符集(不编码)
var unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
var result = new StringBuilder();
byte[] data = Encoding.UTF8.GetBytes(input); // 统一转换为 UTF-8 字节
foreach (byte b in data)
{
char currentChar = (char)b;
// 如果是未保留字符,直接输出
if (unreservedChars.IndexOf(currentChar) != -1)
{
result.Append(currentChar);
}
else
{
// 否则,进行百分号编码(%XX,大写)
result.Append('%').Append(b.ToString("X2"));
}
}
return result.ToString();
}对于 PHP,直接使用内置的 rawurlencode() 函数。这个函数的设计初衷就是严格遵循 RFC 3986 标准。
$encoded_data = rawurlencode($data_to_encode);对于 JavaScript,encodeURIComponent 函数严格遵循 RFC 3986 标准。
let encodedData = encodeURIComponent(dataToEncode);重要提示:无论使用哪种语言,务必在编码前明确指定字符串使用 UTF-8 编码。编码不一致是导致乱码和签名失败最常见的原因之一 。
在 Linux 上运行 .NET 网站,通过
HttpContext.Connection.RemoteIpAddress获取客户端的 IP 地址,结果是
::ffff:127.0.0.1解决方法:
打开 Program.cs 文件,在 var app = builder.Build(); 之前(尽量往前)添加以下代码:
if (OperatingSystem.IsLinux())
{
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor
| ForwardedHeaders.XForwardedProto
| ForwardedHeaders.XForwardedHost;
// 清除 KnownNetworks 和 KnownProxies,表示信任来自本机的代理(如 Nginx)
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
Console.WriteLine("ForwardedHeaders enabled (Running on Linux)");
}然后在 app.UseRouting(); 之前添加以下代码:
if (OperatingSystem.IsLinux())
{
app.UseForwardedHeaders();
Console.WriteLine("UseForwardedHeaders() applied.");
}其中,OperatingSystem.IsLinux() 用于判断只在 Linux 环境中生效,你可以视自身情况作判断。
IIS 中使用 HttpPlatformHandler 模块部署 python web 项目时遇到 502.3 网关错误:
HTTP 错误 502.3 - Bad Gateway
There was a connection error while trying to route the request.
最可能的原因:
CGI 应用程序没有返回一组有效的 HTTP 错误。
由于父网关中出现错误,充当代理或网关的服务器无法处理该请求。
可尝试的操作:
使用 DebugDiag 排查 CGI 应用程序。
确定此错误是否由代理或网关引起。
详细错误信息:
模块 httpPlatformHandler
通知 ExecuteRequestHandler
处理程序 httpplatformhandler
错误代码 见下方表格
详细信息:
当 CGI 应用程序未返回一组有效的 HTTP 头,或者代理或网关无法将请求发送至父网关时,便会出现此错误。您可能需要获取一个网络跟踪,或者与代理服务器管理员联系(如果不是 CGI 的问题)。
| 错误代码 | 可能的错误原因 |
|---|---|
| 0x8007053d | 未安装相关组件库,建议执行命令:pip install -r requirements.txt |
| 0x80070005 | 若显示配置错误“由于权限不足而无法读取配置文件”则是应用代码目录未配置相应用户的读取权限; 访问受限,请正确配置 IIS 中的网站凭据、应用程序池标识、项目目录“读取”、解释器目录“读取和执行”权限等 |
| 0x80072ee2 | 执行超时,一般发生在批量读写的时候 |
| 0x8007042b | |
| 任何错误 | 查看日志文件。 可能的错误原因:
|
AsNoTracking 设置未追踪查询
var customers = dbContext.Customers.AsNoTracking().ToList();这对于只读查询非常有用,因为它可以减少内存使用并提高性能,因为它不需要维护实体的更改跟踪信息。
ExecuteDelete 和 ExecuteUpdate 批量操作
context.Logs.Where(c => c.Time < new DateTime(2000, 1, 1)).ExecuteDelete();从 EF Core 7 开始,ExecuteDelete 和 ExecuteUpdate 是官方原生支持的批量操作方法。直接操作数据库,不需要调用 SaveChanges():不加载实体到内存,减少内存消耗和网络往返。若一次删除数据量过大可以使用 Take() 来限制,同样不会加载到内存。这两种方法(ExecuteUpdate 和 ExecuteDelete)都返回受影响的行数。
若需要分页删除和大批量插入,或在高频、大规模场景,推荐使用 Zack.EFCore.Batch:
context.Logs.Where(c => c.Time < new DateTime(2000, 1, 1)).DeleteRangeAsync(batchSize: 1000);考虑用 Union 代替 OR
// Where 后行数多时(如分页前)用 OR
var q = db.dt_crm__contract.AsNoTracking();
q = q.Where(c => c.dt_crm__customer.SalesmanId == uid || myIns.Contains(c.IndustryId));
// 用于合并的 q1、q2 的行数少时用 Union
var q1 = db.dt_crm__contract.AsNoTracking().Where(c => c.dt_crm__customer.SalesmanId == uid);
var q2 = db.dt_crm__contract.AsNoTracking().Where(c => myIns.Contains(c.IndustryId));
var q = q1.Union(q2);“ToDictionary + Count”之前先 Select
// 不推荐
var dic = q.GroupBy(c => c.Date)
.ToDictionary(k => k.Key, v => v.Count());
// 推荐
var dic = q.GroupBy(c => c.Date)
.Select(g => new { g.Key, Count = g.Count() })
.ToDictionary(k => k.Key, v => v.Count);打开 .csproj 项目文件,在 <PropertyGroup> 标签内添加:
<Version>
1.0.$([System.Math]::Floor($([System.DateTime]::Now.Subtract($([System.DateTime]::Parse('2000-01-01T00:00:00Z'))).TotalDays))).$([MSBuild]::Divide($([System.Math]::Floor($([System.DateTime]::Now.TimeOfDay.TotalSeconds))), 2))
</Version>最终生成的版本号示例: 1.0.9238.28518
其中,Major 与 Minor 是固定的,Build 是2000年1月1日至今的天数,Revision 是今天的秒数 / 2 所得的值。(为了防止数值超过 65535)
程序中获取版本号:
var version = Assembly.GetExecutingAssembly().GetName().Version!; // 当前类库
var version = Assembly.GetEntryAssembly()?.GetName().Version!; // 入口项目从版本号获取发布时间:
DateTime versionTime = new DateTime(2000, 1, 1).AddDays(version.Build).AddSeconds(version.Revision * 2);在解决方案资源管理器中找到 Properties/AssemblyInfo.cs 文件。该文件存放程序集版本信息。
修改版本号格式
将以下代码片段中的 AssemblyVersion 改为使用星号通配符(建议保留主版本和次版本号):
[assembly: AssemblyVersion("1.0.*")] // 自动生成构建号和修订号 // [assembly: AssemblyFileVersion("1.0.0.0")] // 注释或删除此行关闭确定性构建
用文本编辑器打开 .csproj 项目文件,在 <PropertyGroup> 标签内添加:
<Deterministic>false</Deterministic>此设置允许 MSBuild 生成动态版本号。
最终生成的版本号示例: 1.0.9238.28518
其中,Major 与 Minor 是固定的,Build 是2000年1月1日至今的天数,Revision 是今天的秒数 / 2 所得的值。(为了防止数值超过 65535)
程序中获取版本号:
var version = Assembly.GetExecutingAssembly().GetName().Version;从版本号获取发布时间:
DateTime versionTime = new DateTime(2000, 1, 1).AddDays(version.Build).AddSeconds(version.Revision * 2);查看 .NET Core / .NET 5+ 实现自动版本号的方法
