Lua 从入门到精通 / 18 - 实战案例 / Real-World Projects
实战案例 / Real-World Projects
理论学完了,看看 Lua 在真实场景中的用法。本章覆盖:脚本任务、OpenResty Web 开发、Redis 脚本、游戏引擎脚本、自定义 DSL。
Theory is learned — let’s see Lua in action. This chapter covers scripting tasks, OpenResty web dev, Redis scripting, game engine scripting, and custom DSLs.
🟢 基础 / Basics — 日常脚本任务
1. 批量文件处理 / Batch File Processing
#!/usr/bin/env lua
-- rename_files.lua — 批量重命名文件
-- Usage: lua rename_files.lua /path/to/dir .jpeg .jpg
local dir = arg[1] or "."
local from_ext = arg[2] or ".jpeg"
local to_ext = arg[3] or ".jpg"
local function scandir(directory)
local files = {}
local pipe = io.popen('ls -1 "' .. directory .. '" 2>/dev/null')
if pipe then
for file in pipe:lines() do
files[#files + 1] = file
end
pipe:close()
end
return files
end
local count = 0
for _, file in ipairs(scandir(dir)) do
if file:sub(-#from_ext) == from_ext then
local newname = file:sub(1, -#from_ext - 1) .. to_ext
local old = dir .. "/" .. file
local new = dir .. "/" .. newname
os.rename(old, new)
print(string.format(" %s → %s", file, newname))
count = count + 1
end
end
print(string.format("Renamed %d files", count))
2. JSON 处理 / JSON Processing
-- 使用 cjson 库(OpenResty 内置,或独立安装)
local cjson = require("cjson")
-- 编码
local data = {
name = "Alice",
age = 30,
scores = {95, 87, 92},
active = true,
}
local json_str = cjson.encode(data)
print(json_str)
-- {"name":"Alice","age":30,"scores":[95,87,92],"active":true}
-- 解码
local obj = cjson.decode(json_str)
print(obj.name) -- Alice
print(obj.scores[1]) -- 95
-- 处理 null
cjson.decode_null_as_null(true) -- 让 JSON null 返回 cjson.null
local result = cjson.decode('{"x": null}')
print(result.x == cjson.null) -- true
-- 错误处理
local ok, result = pcall(cjson.decode, "invalid json")
if not ok then
print("JSON parse error: " .. result)
end
3. 配置文件解析 / Configuration Parsing
-- config.lua — 用 Lua 作为配置文件
return {
server = {
host = os.getenv("APP_HOST") or "0.0.0.0",
port = tonumber(os.getenv("APP_PORT")) or 8080,
},
database = {
url = os.getenv("DATABASE_URL") or "sqlite://data.db",
pool_size = 10,
},
features = {
dark_mode = true,
beta_features = false,
},
}
-- main.lua — 加载配置
local config = require("config")
print(config.server.host)
print(config.database.url)
🟡 进阶 / Intermediate — OpenResty & Redis
1. OpenResty 请求处理 / OpenResty Request Handling
-- nginx.conf 中的 Lua 处理
-- location /api {
-- content_by_lua_block {
-- require("api_handler").handle()
-- }
-- }
-- api_handler.lua
local ngx = require("ngx")
local cjson = require("cjson.safe")
local _M = {}
function _M.handle()
local method = ngx.req.get_method()
local uri = ngx.var.uri
-- 路由分发
if method == "GET" and uri == "/api/health" then
return _M.health()
elseif method == "POST" and uri == "/api/users" then
return _M.createUser()
else
ngx.status = 404
ngx.say(cjson.encode({error = "Not Found"}))
end
end
function _M.health()
ngx.header.content_type = "application/json"
ngx.say(cjson.encode({
status = "ok",
timestamp = ngx.now(),
}))
end
function _M.createUser()
ngx.req.read_body()
local body = ngx.req.get_body_data()
local data, err = cjson.decode(body)
if not data or not data.name then
ngx.status = 400
return ngx.say(cjson.encode({error = "Invalid request"}))
end
-- 存储到 Redis
local redis = require("resty.redis")
local red = redis:new()
red:connect("127.0.0.1", 6379)
red:set("user:" .. data.name, cjson.encode(data))
red:close()
ngx.status = 201
ngx.header.content_type = "application/json"
ngx.say(cjson.encode({created = true, name = data.name}))
end
return _M
2. OpenResty 中间件 / OpenResty Middleware
-- rate_limiter.lua — 基于 Redis 的限流器
local ngx = require("ngx")
local redis = require("resty.redis")
local _M = {}
function _M.limit(key, max_requests, window_seconds)
local red = redis:new()
red:connect("127.0.0.1", 6379)
local current = red:incr(key)
if current == 1 then
red:expire(key, window_seconds)
end
red:close()
if current > max_requests then
ngx.status = 429
ngx.header["Retry-After"] = window_seconds
ngx.say('{"error": "Rate limit exceeded"}')
return ngx.exit(429)
end
end
return _M
-- nginx.conf 中使用:
-- access_by_lua_block {
-- local limiter = require("rate_limiter")
-- local key = "rate:" .. ngx.var.remote_addr
-- limiter.limit(key, 100, 60) -- 100 次/分钟
-- }
3. Redis Lua 脚本 / Redis Lua Scripting
-- Redis 使用 Lua 5.1 脚本(EVAL 命令)
-- 脚本在 Redis 服务器中原子执行
-- 示例一:原子递增并检查
local script = [[
local current = redis.call('GET', KEYS[1])
if not current then current = 0 end
current = tonumber(current) + tonumber(ARGV[1])
if current > tonumber(ARGV[2]) then
return redis.error_reply('Limit exceeded')
end
redis.call('SET', KEYS[1], current)
redis.call('EXPIRE', KEYS[1], ARGV[3])
return current
]]
-- Redis CLI 执行:
-- EVAL "script" 1 counter 5 100 3600
-- 参数:script, numkeys, key1, arg1, arg2, arg3
-- 示例二:分布式锁
local lock_script = [[
if redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) then
return 1
else
return 0
end
]]
-- 示例三:限流(令牌桶)
local rate_limit_script = [[
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call('GET', key)
if current and tonumber(current) >= limit then
return 0 -- 拒绝
end
current = redis.call('INCR', key)
if current == 1 then
redis.call('EXPIRE', key, window)
end
return 1 -- 允许
]]
🔴 高级 / Advanced — 游戏脚本与 DSL
1. 游戏引擎中的 Lua 脚本 / Game Engine Scripting
-- 游戏 AI 状态机
local StateMachine = {}
StateMachine.__index = StateMachine
function StateMachine.new(entity)
return setmetatable({
entity = entity,
states = {},
current = nil,
}, StateMachine)
end
function StateMachine:addState(name, state)
self.states[name] = state
state.machine = self
state.entity = self.entity
end
function StateMachine:switch(name, ...)
if self.current and self.states[self.current].exit then
self.states[self.current].exit(self.entity)
end
self.current = name
if self.states[name].enter then
self.states[name].enter(self.entity, ...)
end
end
function StateMachine:update(dt)
if self.current and self.states[self.current].update then
self.states[self.current].update(self.entity, dt)
end
end
-- NPC AI 状态
local idleState = {
enter = function(entity)
entity.animation = "idle"
end,
update = function(entity, dt)
local dist = entity:distanceTo(entity.target)
if dist < entity.aggroRange then
entity.machine:switch("chase")
end
end,
}
local chaseState = {
enter = function(entity)
entity.animation = "run"
entity.speed = entity.baseSpeed * 1.5
end,
update = function(entity, dt)
local dist = entity:distanceTo(entity.target)
if dist > entity.aggroRange * 2 then
entity.machine:switch("idle")
elseif dist < entity.attackRange then
entity.machine:switch("attack")
else
entity:moveToward(entity.target, dt)
end
end,
}
local attackState = {
enter = function(entity)
entity.animation = "attack"
entity.attackTimer = 0
end,
update = function(entity, dt)
entity.attackTimer = entity.attackTimer + dt
if entity.attackTimer >= entity.attackCooldown then
entity:performAttack()
entity.attackTimer = 0
end
local dist = entity:distanceTo(entity.target)
if dist > entity.attackRange then
entity.machine:switch("chase")
end
end,
}
-- 创建 NPC
local npc = createNPC({name = "Goblin", hp = 50})
npc.machine = StateMachine.new(npc)
npc.machine:addState("idle", idleState)
npc.machine:addState("chase", chaseState)
npc.machine:addState("attack", attackState)
npc.machine:switch("idle")
2. 自定义 DSL / Custom DSL
-- 用 Lua 的语法特性构建 DSL
-- 例如:任务描述 DSL
local Task = {}
Task.__index = Task
function Task.new(name)
return setmetatable({
name = name,
steps = {},
rewards = {},
requirements = {},
}, Task)
end
-- DSL 魔法:利用 Lua 的语法创建声明式 API
local function quest(name, body)
local q = Task.new(name)
local env = {
step = function(desc, fn)
q.steps[#q.steps + 1] = {desc = desc, action = fn}
end,
reward = function(item, count)
q.rewards[#q.rewards + 1] = {item = item, count = count or 1}
end,
require_level = function(level)
q.requirements[#q.requirements + 1] = {type = "level", value = level}
end,
require_item = function(item)
q.requirements[#q.requirements + 1] = {type = "item", value = item}
end,
}
setmetatable(env, {__index = _G})
setfenv(body, env) -- Lua 5.1
body()
return q
end
-- 使用 DSL 定义任务
local q = quest("Kill the Dragon", function()
require_level(10)
require_item("Sword of Light")
step("Travel to Dragon Mountain", function(player)
player:moveTo("dragon_mountain")
end)
step("Defeat the Dragon", function(player)
local dragon = spawnMob("Dragon", {hp = 1000})
player:combat(dragon)
end)
step("Collect Dragon Scale", function(player)
player:loot("Dragon Scale", 1)
end)
reward("Gold", 1000)
reward("Dragon Scale Armor", 1)
reward("Experience", 5000)
end)
-- 任务系统可以遍历 q.steps、q.rewards、q.requirements
3. 热更新系统 / Hot-Reload System
-- 游戏中的热更新:不重启游戏,只重新加载脚本
local HotReload = {}
local loaded = {} -- 已加载的模块
local callbacks = {} -- 热更新回调
function HotReload.watch(moduleName, callback)
callbacks[moduleName] = callback
end
function HotReload.reload(moduleName)
-- 清除缓存
package.loaded[moduleName] = nil
-- 重新加载
local ok, newModule = pcall(require, moduleName)
if not ok then
print("Reload failed: " .. tostring(newModule))
return false
end
-- 如果旧模块有状态,迁移状态
if loaded[moduleName] and callbacks[moduleName] then
callbacks[moduleName](loaded[moduleName], newModule)
end
loaded[moduleName] = newModule
print("Reloaded: " .. moduleName)
return true
end
-- 使用
HotReload.watch("game.player", function(old, new)
-- 迁移玩家状态
new.restoreState(old.getState())
end)
-- 文件系统监控触发重载(需要 inotify 或轮询)
local function checkForChanges()
-- 检查文件修改时间
local modules = {"game.player", "game.enemy", "game.items"}
for _, mod in ipairs(modules) do
local path = mod:gsub("%.", "/") .. ".lua"
local f = io.popen("stat -c %Y " .. path)
local mtime = tonumber(f:read("*a"))
f:close()
if mtime and mtime > (lastModified[mod] or 0) then
HotReload.reload(mod)
lastModified[mod] = mtime
end
end
end
小结 / Summary
| 层级 | 你需要知道的 / What You Need to Know |
|---|---|
| 🟢 基础 | 文件批处理、JSON 解析、Lua 作为配置文件 |
| 🟡 进阶 | OpenResty 请求处理/中间件、Redis Lua 脚本原子操作 |
| 🔴 高级 | 游戏状态机 AI、自定义 DSL、热更新系统 |
结语 / Conclusion
恭喜你完成了 Lua 从入门到精通的全部学习!回顾一下:
Congratulations on completing the entire Lua tutorial! Here’s a recap:
基础篇 / Fundamentals 进阶篇 / Intermediate 高级篇 / Advanced
├─ 语法基础 ├─ 类型系统深挖 ├─ 字节码与 VM
├─ 数据类型 ├─ 模式匹配 ├─ 元表内部机制
├─ 控制流程 ├─ 闭包与 Upvalue ├─ GC 算法
├─ 函数 ├─ OOP 模式 ├─ C API 栈操作
├─ 表 ├─ 模块系统 ├─ LuaJIT 与 FFI
└─ 字符串 ├─ 协程 └─ DSL 设计
├─ 错误处理
├─ I/O 操作
└─ 调试与性能
下一步建议 / Next Steps:
- 🎮 尝试用 Lua 写一个小游戏(推荐 LÖVE 2D)
- 🌐 用 OpenResty 搭建一个 API 服务
- 📝 给 Redis 写一个 Lua 脚本
- 🔧 阅读 Lua 源码,真正理解虚拟机
- 📖 阅读 Programming in Lua(Lua 之父的著作)
Happy coding!