博客 (72)

报道称 NGINX 被曝一组高危漏洞,已潜伏约 18 年,威胁全球约三分之一的网络服务器。

本次曝光的漏洞如下:

CVE-2026-42945 - (9.2 Critical)

CVE-2026-42946 - (8.3 High)

CVE-2026-40701 - (6.3 Medium)

CVE-2026-42934 - (6.3 Medium)

攻击者无需登录认证,只需发送一条特制 HTTP 请求,就能让 NGINX 工作进程崩溃;在合适条件下,还可能拿到服务器远程代码执行权限。


修复方案一

NGINX 升级到 1.31.0 或 1.30.1


修复方案二

若你使用的是 OpenResty 或宝塔面板,没有更新的 nginx 版本选择,那么手动修复此漏洞。

将 rewrite 规则中的未命名正则捕获改成命名捕获。

示例:

原来的写法:

rewrite ^(.*)$ /index.php?s=$1 last;

修改为:

rewrite ^(?<a>.*)$ /index.php?s=$a last;


参考文献:

  1. NGINX Rift: Achieving NGINX Remote Code Execution via an 18-Year-Old Vulnerability

  2. NGINX 曝 9.2 分高危漏洞:潜伏 18 年,威胁全球约 1/3 服务器

xoyozo 5 天前
217

以下是 Spectre.Console 的核心功能示例,涵盖最常用的输出和交互场景:


1. 基础富文本输出

image.png

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. 表格

image.png

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. 进度条 / 状态指示

image.pngimage.png

// 带进度条的循环任务
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. 交互式提示

image.png

// 确认
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. 树形结构

image.png

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. 布局 / 网格

image.png

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. 实时更新

image.png

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. 带样式的异常显示

image.png

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


9. 日历

image.png

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


10. 条形图 / 柱状图

image.png

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


11. 分解图

image.png

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


12. 规则线

image.png

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


13. 文本样式

image.png

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


14. 网格

image.png

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);






xoyozo 28 天前
161

今天发现在 .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 编码。编码不一致是导致乱码和签名失败最常见的原因之一 。

xoyozo 5 个月前
1,041
  1. 临界区与 lock 关键字

    核心作用:

    通过将多线程访问串行化,保护共享资源或代码段。lock 关键字是 Monitor 类的语法糖,提供异常安全的临界区实现。

    实现示例:

    // 创建私有静态只读对象
    // private static readonly object _lockObj = new object();
    private static readonly System.Threading.Lock _locker = new(); // .NET 9+ 推荐使用 Lock 类型,避免传统 object 的性能损耗
    
    public void ThreadSafeMethod()
    {
        lock (_lockObj) 
        {
            // 临界区代码(每次仅一个线程可进入)
        }
    }

    超时机制:

    高并发场景可结合 Monitor.TryEnter 设置超时,避免无限等待:


    if (Monitor.TryEnter(lockObject, TimeSpan.FromSeconds(1)))
    {
        try { /* 操作 */ }
        finally { Monitor.Exit(lockObject); }
    }

    关键特性:

    用户态锁(无内核切换开销)

    自动调用Monitor.Enter和Monitor.Exit

    必须使用专用私有对象作为锁标识

    注意事项:

    ❌ 避免锁定this、Type实例或字符串(易引发死锁)

    ❌ 避免嵌套锁(需严格按顺序释放)

    ✅ 推荐readonly修饰锁对象

    ❌ lock 不适用于异步代码(async/await),需使用 SemaphoreSlim 实现异步锁

    lock 示例

     

  2. 互斥锁(Mutex)

    核心作用:

    系统级内核锁,支持跨进程同步,但性能开销较高(用户态/内核态切换)。

    实现示例:

    using var mutex = new Mutex(false, "Global\\MyAppMutex");
    
    try 
    {
        // 等待锁(最大等待时间500ms)
        if (mutex.WaitOne(500)) 
        {
            // 临界区代码
        }
    }
    finally 
    {
        if (mutex != null)
        {
            mutex.ReleaseMutex();
        }
    }

    关键特性:

    支持跨应用程序域同步

    线程终止时自动释放锁

    支持命名互斥体(系统全局可见)

    适用场景:

    单实例应用程序控制

    进程间共享文件访问

    硬件设备独占访问

    Mutex 示例

     

  3. 信号量(Semaphore)

    核心作用:

    通过许可计数器控制并发线程数,SemaphoreSlim为轻量级版本(用户态实现)。

    实现对比:

    类型
    跨进程
    性能
    最大许可数
    Semaphore
    ✔️

    系统限制
    SemaphoreSlim


    Int32.Max

    代码示例:

    // 创建初始3许可、最大5许可的信号量
    var semaphore = new SemaphoreSlim(3, 5);
    
    semaphore.Wait();  // 获取许可
    try 
    {
        // 资源访问代码
    }
    finally 
    {
        semaphore.Release();
    }

    异步编程

    private readonly SemaphoreSlim _asyncLock = new(1, 1);
    public async Task UpdateAsync()
    {
        await _asyncLock.WaitAsync();
        try { /* 异步操作 */ }
        finally { _asyncLock.Release(); }
    }

    典型应用:

    数据库连接池(限制最大连接数)

    API 请求限流

    批量任务并发控制

    Semaphore 示例

     

  4. 事件(Event)

    核心机制:

    通过信号机制实现线程间通知,分为两种类型:

    类型
    信号重置方式
    唤醒线程数
    AutoResetEvent
    自动
    单个
    ManualResetEvent
    手动
    所有

    使用示例:

    var autoEvent = new AutoResetEvent(false);
    
    // 等待线程
    Task.Run(() => 
    {
        autoEvent.WaitOne();
        // 收到信号后执行
    });
    
    // 信号发送线程
    autoEvent.Set();  // 唤醒一个等待线程

    高级用法:

    配合WaitHandle.WaitAll实现多事件等待

    使用ManualResetEventSlim提升性能

    AutoResetEvent 示例

     

  5. 读写锁(ReaderWriterLockSlim)

    核心优势:

    实现读写分离的并发策略,适合读多写少场景(如缓存系统)。

    锁模式对比:

    模式
    并发性
    升级支持
    读模式(EnterReadLock)
    多线程并发读

    写模式(EnterWriteLock)
    独占访问

    可升级模式
    单线程读→写
    ✔️

    代码示例:

    var rwLock = new ReaderWriterLockSlim();
    
    // 读操作
    rwLock.EnterReadLock();
    try 
    {
        // 只读访问
    }
    finally 
    {
        rwLock.ExitReadLock();
    }
    
    // 写操作
    rwLock.EnterWriteLock();
    try 
    {
        // 排他写入
    }
    finally 
    {
        rwLock.ExitWriteLock();
    }

    最佳实践:

    优先使用ReaderWriterLockSlim(旧版有死锁风险)

    避免长时间持有读锁(可能饿死写线程)

    ReaderWriterLockSlim 示例

  6. 原子操作(Interlocked)

    原理:

    通过CPU指令实现无锁线程安全操作。

    常用方法:

    int counter = 0;
    Interlocked.Increment(ref counter);      // 原子递增
    Interlocked.Decrement(ref counter);      // 原子递减
    Interlocked.CompareExchange(ref value, newVal, oldVal);  // CAS操作

    适用场景:

    简单计数器

    标志位状态切换

    无锁数据结构实现

     

  7. 自旋锁(SpinLock)

    核心特点:

    通过忙等待(busy-wait)避免上下文切换,适用极短临界区(<1微秒)。

    实现示例:

    private SpinLock _spinLock = new SpinLock();
    
    public void CriticalOperation()
    {
        bool lockTaken = false;
        try 
        {
            _spinLock.Enter(ref lockTaken);
            // 极短临界区代码
        }
        finally 
        {
            if (lockTaken) _spinLock.Exit();
        }
    }

    优化技巧:

    单核CPU需调用Thread.SpinWait或Thread.Yield

    配合SpinWait结构实现自适应等待

     

  8. 同步机制对比指南

    机制
    跨进程
    开销级别
    最佳适用场景
    lock


    通用临界区保护
    Mutex
    ✔️

    进程间资源独占
    Semaphore
    ✔️

    并发数限制(跨进程)
    SemaphoreSlim


    并发数限制(进程内)
    ReaderWriterLockSlim


    读多写少场景
    SpinLock

    极低
    纳秒级临界区
    Interlocked
    -无锁
    简单原子操作

选择原则:

  1. 优先考虑用户态锁(lock/SpinLock/SemaphoreSlim)

  2. 跨进程需求必须使用内核对象(Mutex/Semaphore)

  3. 读写比例超过10:1时考虑读写锁

  4. 自旋锁仅用于高频短操作(如链表指针修改)

通过以上结构化的分类和对比,开发者可以更精准地选择适合特定场景的线程同步方案。建议在实际使用中配合性能分析工具(如BenchmarkDotNet)进行量化验证。

💡 ASP.NET 的异步编程(async/await)本质是单进程内的线程调度,不算“跨进程”。每个 IIS 应用程序池对应一个独立的工作进程(w3wp.exe),不同用户访问同一应用程序池下的 ASP.NET 网站,两者的请求均由同一个 w3wp.exe 进程处理。可能跨进程的场景有:Web Garden 配置、多应用程序池部署等。

在 C# 中,除了常规锁机制(如 lock、Mutex、Semaphore 等),还有一些内置类型通过内部锁或无锁设计实现线程安全。以下是常见的几类:

  1. 线程安全集合(System.Collections.Concurrent)这些集合通过细粒度锁或无锁算法(如 CAS)实现线程安全,适合高并发场景。

    • ConcurrentDictionary:分段锁机制,将数据分片存储,每个分片独立加锁,减少锁竞争。

    • ConcurrentQueue / ConcurrentStack基于原子操作(Interlocked)保证线程安全。

    • ConcurrentBag:每个线程维护本地存储,减少争用,适合频繁添加和移除的场景。

    • BlockingCollection:基于 ConcurrentQueue 和信号量(SemaphoreSlim)实现生产-消费者模式,支持阻塞和超时。

  2. 不可变集合(System.Collections.Immutable) 通过数据不可变性实现线程安全(无需锁),每次修改返回新对象。

  3. Lazy 的线程安全初始化(Lazy<T>) 通过锁或 Interlocked 确保延迟初始化的线程安全。

  4. 通道(System.Threading.Channels)用于异步生产-消费者模型,内部通过锁和信号量管理容量限制。

  5. 内存缓存(System.Runtime.Caching.MemoryCache)内部使用锁保护共享状态,确保线程安全。

  6. 原子操作类型(Interlocked 类、Volatile 关键字、Unsafe 类)通过 CPU 指令实现无锁线程安全。

  7. 其他同步工具(Barrier、CountdownEvent)虽然不是严格意义上的锁,但用于协调线程。

xoyozo 1 年前
3,807

在 .NET Framework 的缓存管理中,cacheMemoryLimitMegabytes 是一个关键配置属性,用于控制内存缓存(MemoryCache)实例的最大内存占用。以下是其具体用法及实现细节:

  1. 基本定义与作用

    • 功能:通过 cacheMemoryLimitMegabytes 可设置 MemoryCache 实例允许占用的最大内存(单位:MB)。若缓存数据超过此限制,系统会自动淘汰旧条目。

    • 默认值:默认值为 0,表示缓存基于计算机的物理内存自动管理(例如根据可用内存动态调整)。

  2. 配置方式

    • 通过配置文件(web.config)

      在 web.config 的 <system.runtime.caching> 节点下配置 namedCaches,示例:

      <configuration>
        <system.runtime.caching>
          <memoryCache>
            <namedCaches>
              <add 
                name="Default" 
                cacheMemoryLimitMegabytes="500" 
                physicalMemoryLimitPercentage="50" 
                pollingInterval="00:05:00" />
            </namedCaches>
          </memoryCache>
        </system.runtime.caching>
      </configuration>

      参数说明:

      • cacheMemoryLimitMegabytes:最大内存限制(例如 500 表示 500MB)。

      • physicalMemoryLimitPercentage:允许使用的物理内存百分比(可选)。

      • pollingInterval:缓存清理策略的轮询间隔(例如每5分钟检查一次)。

    • 通过代码动态配置

      在初始化 MemoryCache 时,通过 NameValueCollection 传递参数:

      var config = new NameValueCollection
      {
          { "cacheMemoryLimitMegabytes", "500" },
          { "physicalMemoryLimitPercentage", "50" },
          { "pollingInterval", "00:05:00" }
      };
      var cache = new MemoryCache("CustomCache", config);

      此方式适用于需要动态调整缓存策略的场景。

  3. 注意事项

    • 优先级规则:

      若同时配置了 cacheMemoryLimitMegabytes 和 physicalMemoryLimitPercentage,系统会选择两者中较小的值作为限制。

    • 分布式缓存兼容性:

      此属性仅适用于进程内缓存(如 MemoryCache),若使用 Redis 等分布式缓存需通过其独立配置管理内存。

    • 监控与调试:

      建议结合性能计数器(如 ASP.NET Applications 类别下的 Cache Total Entries)或日志记录模块(参考 web.config 的 <system.diagnostics> 配置)监控实际内存占用。

  4. 应用场景示例

    场景:一个电商网站需要缓存商品目录数据,限制最大内存为 1GB。

    配置实现:

    <add 
      name="ProductCatalogCache" 
      cacheMemoryLimitMegabytes="1024" 
      pollingInterval="00:10:00" />

    代码调用:

    var productCatalog = MemoryCache.Default["ProductCatalog"];
  5. 常见问题

    1. Q:设置为 0 时缓存会无限制增长吗?

      A:不会。此时缓存基于系统物理内存动态管理,通常上限为总内存的 70%-90%。

    2. Q:如何验证配置已生效?

      A:可通过 MemoryCache.GetCount() 统计条目数量,或使用性能监视器跟踪内存占用。

xoyozo 1 年前
1,844

打开 .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);


查看 .NET Framework 实现自动版本号的方法


xoyozo 1 年前
1,333
  1. 在解决方案资源管理器中找到 Properties/AssemblyInfo.cs 文件。该文件存放程序集版本信息。

  2. 修改版本号格式

    将以下代码片段中的 AssemblyVersion 改为使用星号通配符(建议保留主版本和次版本号):

    [assembly: AssemblyVersion("1.0.*")]  // 自动生成构建号和修订号
    // [assembly: AssemblyFileVersion("1.0.0.0")] // 注释或删除此行
  3. 关闭确定性构建

    用文本编辑器打开 .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+ 实现自动版本号的方法


xoyozo 1 年前
1,048

大模型文件一般都比较大,Ollama 默认是下载到 C 盘的,如何更改到 D 盘或其它盘符中?

第一步,退出 Ollama。

第二步,设置环境变量,设置方法参这篇文章

变量名:OLLAMA_MODELS
变量值:D:\.ollama\models(这是示例,填写自己的实际路径即可)

第三步,将默认路径下的 models 目录移至 D:\.ollama\

默认路径一般为 C:\Users\<用户>\.ollama\

xoyozo 1 年前
4,107
  1. 使 lua 支持 resty.http

  2. 在 access_by_lua_block 代码块中实现远程鉴权:

    #鉴权-START
    #resolver 223.5.5.5; # cat /etc/resolv.conf
    access_by_lua_block {
        local http = require("resty.http")
        local httpc = http.new()
        httpc:set_timeout(500)  -- 连接超时
        local res, err = httpc:request_uri("https://鉴权地址/", {
            method = "GET",
            headers = {
                ["X-Real-IP"] = ngx.var.remote_addr,
                ["User-Agent"] = ngx.var.http_user_agent,
                ["X-Forwarded-Host"] = ngx.var.host,
                ["X-Forwarded-Uri"] = ngx.var.request_uri,
            },
            ssl_verify = false, -- 禁用 SSL 验证
            timeout = 500,     -- 读取超时
        })
    
        if not res then
            ngx.log(ngx.ERR, "Failed to request: " .. err)
        end
            
        if res and res.status == 403 then
            ngx.exit(ngx.HTTP_FORBIDDEN)
            -- return ngx.redirect("https://一个显示403友好信息的页面.html")
        end
    }
    #鉴权-END

    注意更改接口地址和友情显示 403 页面地址。

    本示例仅捕获 403 状态码,500、408 等其它异常情况视为允许访问,请根据业务需求自行添加状态码的判断。

    若超时也会进入 if not res then 代码块。

    建议将此代码部署在 nginx 主配置文件的 http 代码块中(宝塔面板中的路径:/www/server/nginx/nginx/conf/nginx.conf),如果你只想为单个网站鉴权,也可以放在网站配置文件的 server 块中。

  3. 若鉴权接口在私网中,建议将鉴权接口域名和私网 IP 添加到 hosts 文件中。


  1. 直接输出字符串

    ngx.header.content_type = "text/plain";
    ngx.say("hello world!")
  2. 输出到日志

    ngx.log(ngx.ERR, "Response status: " .. res.status)

    日志在网站的 站名.error.log 中查看。

    宝塔面板查看方式:日志 - 网站日志 - 异常

  3. 若想获取服务器 CPU 使用率等信息并传递给远程鉴权接口,请参考此文

  4. 常见问题

    no resolver defined to resolve

    原因:没有定义 DNS 解析器

    解决方法:在 http 块或 server 块中添加 resolver 8.8.8.8 valid=30s;,推荐使用接入商自己的公共 DNS,如果走内网,推荐使用本机 DNS

    unable to get local issuer certificate

    原因:没有配置 SSL 证书信息

    解决方法:添加 request_uri 参数:

    ssl_verify = true,  -- 启用 SSL 验证
    ssl_trusted_certificate = "证书路径",  -- 指定 CA 证书路径

    ssl_verify = false,  -- 禁用 SSL 验证
  5. 若您不想用 lua,可以用 nginx 原生自带的  auth_request 模块来实现

  6. 若 WAF 返回拒绝访问时需要 302 跳转(即 return ngx.redirect(...)),那么最好加上禁止缓存的标识,防止 CDN 污染:

    if res and res.status == 403 then
        -- ===== 禁止 CDN 缓存此响应 =====
        ngx.header["Cache-Control"] = "no-store, no-cache, must-revalidate, proxy-revalidate"
        ngx.header["Pragma"] = "no-cache"
        ngx.header["Expires"] = "0"
        -- 可选:添加标记头,方便调试
        ngx.header["X-WAF-Action"] = "blocked"
        return ngx.redirect("https://...")
    end
xoyozo 1 年前
3,023

微信网页开发 JS-SDK 官方文档介绍的获取地理位置接口示例是这样的:

wx.getLocation({
  type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
  success: function (res) {
    var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
    var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
    var speed = res.speed; // 速度,以米/每秒计
    var accuracy = res.accuracy; // 位置精度
  }});

实际上是这样的:

wx.getLocation({
  type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
  success: function (res) {
    // 网页位置信息授权点击“确定”按钮,以及允许后继续获取定位信息
  },
  fail: function (err) {
    // 获取定位信息失败(譬如:网页位置信息已授权,但操作系统定位已关闭)
  },
  cancel: function (err) {
    // 网页位置信息授权点击“拒绝”按钮
  }
});

顺便提一下如何关闭网页授权:

进入网页使用 JS-SDK 的公众号(可以在授权弹框中看到服务号名称),点击右上角人形图标,继续点击右上角三个点图标,选择设置,关闭“位置信息”。

授权框.png公众号设置.png

xoyozo 2 年前
1,854