OpenResty 中 Lua 脚本实战示例(分场景讲清 Lua 解决了 Nginx 原生做不到的事)

Nginx 原生配置只有静态指令,逻辑极弱;Lua 给 Nginx 赋予可编程能力,下面分 8 个高频场景,附完整可运行代码,讲清 Lua 发挥的作用。

前置基础

所有 Lua 代码写在 xxx_by_lua_block {} 里,运行在 Nginx 不同生命周期阶段。


示例 1:access_by_lua 实现接口鉴权(最常用)

Lua 作用

原生 Nginx 只能简单 IP 放行,无法读取 Header Token、校验 JWT、读取 Redis 判断登录状态,Lua 可以实现完整鉴权逻辑。

nginx

bash 复制代码
http {
    # 加载redis客户端库
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    server {
        listen 80;
        location /api {
            access_by_lua_block {
                -- 1. 获取请求头token
                local token = ngx.var.http_Authorization
                if not token then
                    ngx.print('{"code":401,"msg":"未携带token"}')
                    return ngx.exit(401)
                end

                -- 2. 连接Redis校验token是否有效
                local redis = require "resty.redis"
                local red = redis:new()
                red:set_timeout(1000)
                local ok, err = red:connect("127.0.0.1", 6379)
                if not ok then
                    ngx.exit(500)
                end
                local user_id, err = red:get(token)
                if not user_id or user_id == ngx.null then
                    ngx.print('{"code":401,"msg":"token失效"}')
                    return ngx.exit(401)
                end

                -- 3. 把用户ID传递给后端服务
                ngx.var.user_id = user_id
            }
            proxy_pass http://backend_server;
            proxy_set_header X-User-ID $user_id;
        }
    }
}

核心价值

动态鉴权、读取缓存、变量透传,纯 Nginx 配置无法实现。


示例 2:rewrite_by_lua 动态路由分发

Lua 作用

原生 rewrite 正则固定写死,Lua 可根据请求参数、Redis 配置、灰度规则动态转发不同后端。

nginx

bash 复制代码
location /gateway {
    rewrite_by_lua_block {
        local args = ngx.req.get_uri_args()
        local env = args.env or "prod"

        -- 根据参数动态修改转发地址
        if env == "test" then
            ngx.var.backend = "http://127.0.0.1:8081"
        else
            ngx.var.backend = "http://127.0.0.1:8080"
        end
    }
    proxy_pass $backend;
}

示例 3:content_by_lua 直接写接口,不用后端服务

Lua 作用

Nginx 原生只能返回静态文件,Lua 可独立编写 HTTP 接口,直接返回 JSON,适合简单验证码、健康检查、配置接口。

nginx

bash 复制代码
location /health {
    content_by_lua_block {
        local resp = {
            code = 0,
            msg = "服务正常",
            time = ngx.time()
        }
        -- 返回json
        ngx.header["Content-Type"] = "application/json"
        ngx.print(require("cjson").encode(resp))
    }
}

示例 4:ngx.shared.DICT + Lua 实现单机限流

Lua 作用

原生限流模块只能按固定速率,Lua 可自定义限流逻辑:按用户 ID、接口、IP 分别计数,灵活控制。

nginx

bash 复制代码
http {
    # 开辟共享内存,所有worker进程共享
    lua_shared_dict limit_store 10m;
    server {
        listen 80;
        location /api {
            access_by_lua_block {
                local limit_store = ngx.shared.limit_store
                local ip = ngx.var.remote_addr
                local key = "limit:" .. ip

                -- 计数器+1
                local count, err = limit_store:incr(key, 1, 0, 60) -- 60秒过期
                if count > 10 then -- 1分钟最多10次请求
                    ngx.print('{"code":429,"msg":"请求过于频繁"}')
                    return ngx.exit(429)
                end
            }
            proxy_pass http://backend;
        }
    }
}

示例 5:header_filter_by_lua /body_filter_by_lua 修改响应内容

Lua 作用

原生 Nginx 很难动态修改后端返回的响应头、页面内容,Lua 可以拦截响应并篡改。

nginx

bash 复制代码
location / {
    proxy_pass http://backend;
    -- 修改响应头
    header_filter_by_lua_block {
        -- 删除服务版本号,隐藏后端信息
        ngx.header["Server"] = "gateway"
        ngx.header["X-Powered-By"] = nil
    }
    -- 修改返回的body文本
    body_filter_by_lua_block {
        local data = ngx.arg[1]
        if data then
            -- 替换页面中的敏感文本
            data = ngx.re.gsub(data, "后端真实服务名称", "统一网关服务")
            ngx.arg[1] = data
        end
    }
}

示例 6:init_worker_by_lua 后台定时任务

Lua 作用

Nginx 本身没有定时任务能力,Lua 结合 ngx.timer.at 实现异步后台任务,不阻塞请求。

nginx

bash 复制代码
http {
    lua_shared_dict task_cache 5m;
    init_worker_by_lua_block {
        local function loop_task(premature)
            if premature then return end
            -- 每30秒执行一次:上报监控、刷新本地缓存
            local redis = require "resty.redis"
            -- 刷新配置缓存逻辑省略
            -- 循环创建定时器
            local ok, err = ngx.timer.at(30, loop_task)
        end
        -- 启动定时器
        ngx.timer.at(0, loop_task)
    }
}

示例 7:ngx.location.capture 子请求,接口聚合

Lua 作用

一个前端请求需要调用多个后端接口时,原生 Nginx 无法并发调用并合并结果;Lua 子请求串行 / 并发调用多个接口,组装统一 JSON 返回。

nginx

bash 复制代码
location /agg {
    content_by_lua_block {
        local cjson = require "cjson"
        -- 并发调用两个后端接口
        local res1 = ngx.location.capture("/user/info")
        local res2 = ngx.location.capture("/user/order")

        -- 组装数据统一返回
        local result = {
            user = cjson.decode(res1.body),
            order = cjson.decode(res2.body)
        }
        ngx.header["Content-Type"] = "application/json"
        ngx.print(cjson.encode(result))
    }
}
location /user/info { proxy_pass http://user-service; }
location /user/order { proxy_pass http://order-service; }

示例 8:log_by_lua 自定义日志、数据上报

Lua 作用

原生 access.log 格式固定,Lua 可在请求结束后读取完整请求 / 响应信息,格式化 JSON 日志,异步上报到 ELK、监控平台。

nginx

bash 复制代码
location /api {
    proxy_pass http://backend;
    log_by_lua_block {
        local log_data = {
            ip = ngx.var.remote_addr,
            uri = ngx.var.request_uri,
            status = ngx.var.status,
            cost = ngx.var.request_time,
            token = ngx.var.http_Authorization
        }
        -- 异步上报日志(不会阻塞请求返回)
        ngx.timer.at(0, function()
            local http = require "resty.http"
            local httpc = http.new()
            httpc:request_uri("http://log-collector/log", {
                method = "POST",
                body = require("cjson").encode(log_data)
            })
        end)
    }
}

总结:Lua 在 OpenResty 的核心能力汇总

  1. 可编程鉴权:读取 Redis/JWT 实现登录校验、权限控制
  2. 动态路由网关:灰度发布、多环境转发、流量染色
  3. 独立接口开发:无需 Java/Go 后端,直接用 Lua 写 HTTP 服务
  4. 灵活限流防护:按 IP / 用户自定义限流规则,防刷接口
  5. 响应动态篡改:修改返回头、替换页面、脱敏返回数据
  6. 后台异步任务:定时刷新缓存、上报监控,不阻塞业务请求
  7. 接口聚合:一次请求调用多个后端,合并结果返回前端
  8. 自定义日志监控:结构化日志、异步上报链路追踪数据

简单一句话:Nginx 负责高性能网络收发,Lua 负责所有复杂业务逻辑

相关推荐
触底反弹7 小时前
🔥 从「为什么」到「怎么用」:TypeScript 类型约束与泛型完全指南
后端·面试·typescript
李剑一7 小时前
有点干,前端架构基础之:Web Worker到底是什么?它和Java中的线程是一个道理吗?
前端·面试·架构
leo_yu_yty8 小时前
Mysql 面试准备
数据库·mysql·面试
六边形6668 小时前
独立开发不知道做什么?使用 TRAE Work 抓取差评痛点,快速跑通产品立项流
前端·后端·面试
一只叫煤球的猫10 小时前
Spring AI 2.0 源码解析(三):ChatClient 的 Fluent API 如何构建请求?
java·后端·面试
禁止摆烂_才浅11 小时前
JavaScript 类型判断:instanceof 与 constructor 原理深度解析
前端·javascript·面试
胡萝卜术12 小时前
从"氛围编程"到规范驱动:两次创造如何让 AI 协作从碰运气变成工程流水线
前端·面试·github
叫我Paul就好12 小时前
当你用过 Spring,你可能就更能理解 DSH 的核心 Cordis
后端·面试
Rain的Java大神之路13 小时前
如何避免订单重复提交
java·redis·后端·面试·架构·rabbitmq·rocketmq
程序员爱钓鱼14 小时前
Rust 泛型 Generics详解:编写可复用且类型安全的代码
后端·面试·rust