良好的Lua项目应该遵循模块化、可测试、可维护的设计原则。使用模块系统组织代码,将不同功能分离到独立的模块中。采用面向对象或函数式编程风格,保持代码一致性。使用协程处理异步操作,避免回调地狱。建立完善的错误处理机制,确保程序健壮性。编写单元测试,保证代码质量。使用版本控制和文档,便于团队协作。
-- 项目结构建议
project/
├── src/ -- 源代码
│ ├── core/ -- 核心模块
│ ├── utils/ -- 工具函数
│ └── config/ -- 配置管理
├── tests/ -- 测试代码
├── docs/ -- 文档
├── scripts/ -- 构建脚本
└── main.lua -- 入口文件
-- 模块设计原则
-- 1. 单一职责:每个模块只做一件事
-- 2. 开闭原则:对扩展开放,对修改关闭
-- 3. 依赖倒置:依赖抽象而非具体实现
-- 4. 接口隔离:客户端不应依赖不需要的接口
-- 5. 最少知识:模块间最小化依赖
游戏脚本系统是Lua最经典的应用场景之一。Lua的轻量级、高性能和易嵌入特性使其成为游戏引擎的首选脚本语言。核心原理包括:使用面向对象编程实现游戏实体(玩家、敌人、物品等),使用表存储游戏状态,使用协程处理异步事件(如动画、延时),使用元表实现类型系统和行为定制。游戏循环通常在宿主语言中运行,Lua脚本负责游戏逻辑、AI行为、事件响应等。
-- game.lua - 完整RPG游戏脚本系统
-- 游戏状态管理器
GameState = {
currentScene = "town",
player = nil,
enemies = {},
inventory = {},
quests = {},
flags = {}
}
function GameState:reset()
self.currentScene = "town"
self.enemies = {}
self.inventory = {}
self.quests = {}
self.flags = {}
end
function GameState:setFlag(key, value)
self.flags[key] = value
end
function GameState:getFlag(key, default)
return self.flags[key] or default
end
-- 玩家类
Player = {
name = "",
level = 1,
hp = 100,
maxHp = 100,
mp = 50,
maxMp = 50,
exp = 0,
gold = 0,
stats = {
strength = 10,
agility = 10,
intelligence = 10
},
skills = {},
equipment = {
weapon = nil,
armor = nil,
accessory = nil
}
}
function Player:new(name)
local obj = {
name = name,
level = 1,
hp = 100,
maxHp = 100,
mp = 50,
maxMp = 50,
exp = 0,
gold = 100,
stats = {
strength = 10,
agility = 10,
intelligence = 10
},
skills = {},
equipment = {
weapon = nil,
armor = nil,
accessory = nil
}
}
setmetatable(obj, self)
self.__index = self
return obj
end
function Player:getTotalStats()
local total = {
strength = self.stats.strength,
agility = self.stats.agility,
intelligence = self.stats.intelligence
}
if self.equipment.weapon then
total.strength = total.strength + (self.equipment.weapon.attack or 0)
end
if self.equipment.armor then
total.agility = total.agility + (self.equipment.armor.defense or 0)
end
return total
end
function Player:attack(enemy)
local stats = self:getTotalStats()
local baseDamage = math.random(10, 20) + stats.strength
local critChance = stats.agility / 100
local isCrit = math.random() < critChance
local damage = baseDamage * self.level
if isCrit then
damage = damage * 2
print(self.name .. " 暴击!")
end
enemy.hp = enemy.hp - damage
print(string.format("%s 攻击 %s 造成 %d 点伤害", self.name, enemy.name, damage))
return damage
end
function Player:useSkill(skillName, target)
local skill = self.skills[skillName]
if not skill then
print("技能不存在: " .. skillName)
return false
end
if self.mp < skill.cost then
print("魔法值不足")
return false
end
self.mp = self.mp - skill.cost
print(string.format("%s 使用技能 %s!", self.name, skill.name))
skill.effect(self, target)
return true
end
function Player:gainExp(amount)
self.exp = self.exp + amount
print("获得 " .. amount .. " 点经验")
local levelUpExp = self.level * 100
while self.exp >= levelUpExp do
self.exp = self.exp - levelUpExp
self:levelUp()
levelUpExp = self.level * 100
end
end
function Player:levelUp()
self.level = self.level + 1
self.maxHp = self.maxHp + 20
self.maxMp = self.maxMp + 10
self.stats.strength = self.stats.strength + 2
self.stats.agility = self.stats.agility + 2
self.stats.intelligence = self.stats.intelligence + 2
self.hp = self.maxHp
self.mp = self.maxMp
print(string.format("升级了!当前等级: %d", self.level))
print(string.format("属性提升 - 力量:%d 敏捷:%d 智力:%d",
self.stats.strength, self.stats.agility, self.stats.intelligence))
end
function Player:heal(amount)
self.hp = math.min(self.hp + amount, self.maxHp)
print(string.format("%s 恢复了 %d 点生命值", self.name, amount))
end
-- 技能系统
Skill = {
name = "",
cost = 0,
damage = 0,
effect = nil
}
function Skill:new(name, cost, effect)
local obj = {
name = name,
cost = cost,
effect = effect
}
setmetatable(obj, self)
self.__index = self
return obj
end
-- 技能效果示例
local fireballEffect = function(player, target)
local damage = 50 + player.stats.intelligence * 2
target.hp = target.hp - damage
print(string.format("火球术造成 %d 点伤害", damage))
end
local healEffect = function(player, target)
local healAmount = 30 + player.stats.intelligence
player:heal(healAmount)
end
-- 敌人类
Enemy = {
name = "",
hp = 50,
maxHp = 50,
level = 1,
aiType = "aggressive",
skills = {}
}
function Enemy:new(name, level, aiType)
local obj = {
name = name,
hp = 50 * level,
maxHp = 50 * level,
level = level,
aiType = aiType or "aggressive",
skills = {}
}
setmetatable(obj, self)
self.__index = self
return obj
end
function Enemy:addSkill(skill)
table.insert(self.skills, skill)
end
function Enemy:aiAction(player)
if self.aiType == "aggressive" then
self:attack(player)
elseif self.aiType == "defensive" then
if self.hp < self.maxHp * 0.3 then
self:defend()
else
self:attack(player)
end
elseif self.aiType == "smart" then
if #self.skills > 0 and math.random() < 0.3 then
local skill = self.skills[math.random(1, #self.skills)]
skill.effect(self, player)
else
self:attack(player)
end
end
end
function Enemy:attack(target)
local damage = math.random(5, 15) * self.level
target.hp = target.hp - damage
print(string.format("%s 攻击 %s 造成 %d 点伤害", self.name, target.name, damage))
return damage
end
function Enemy:defend()
local healAmount = math.floor(self.maxHp * 0.1)
self.hp = math.min(self.hp + healAmount, self.maxHp)
print(string.format("%s 防御并恢复了 %d 点生命值", self.name, healAmount))
end
-- 物品系统
Item = {
name = "",
type = "consumable",
effect = nil,
description = ""
}
function Item:new(name, itemType, effect, description)
local obj = {
name = name,
type = itemType,
effect = effect,
description = description
}
setmetatable(obj, self)
self.__index = self
return obj
end
-- 战斗系统
BattleSystem = {
player = nil,
enemy = nil,
turn = 0
}
function BattleSystem:new(player, enemy)
local obj = {
player = player,
enemy = enemy,
turn = 0
}
setmetatable(obj, self)
self.__index = self
return obj
end
function BattleSystem:start()
print("\n=== 战斗开始 ===")
print(string.format("%s VS %s", self.player.name, self.enemy.name))
print(string.format("玩家 HP: %d/%d MP: %d/%d",
self.player.hp, self.player.maxHp,
self.player.mp, self.player.maxMp))
print(string.format("敌人 HP: %d/%d", self.enemy.hp, self.enemy.maxHp))
print("=== 战斗开始 ===\n")
end
function BattleSystem:nextTurn()
self.turn = self.turn + 1
print(string.format("\n--- 第 %d 回合 ---", self.turn))
end
function BattleSystem:playerAction(action, target)
if action == "attack" then
self.player:attack(target)
elseif action == "skill" then
local skillName = target
self.player:useSkill(skillName, self.enemy)
elseif action == "item" then
local itemName = target
self.player:useItem(itemName)
end
end
function BattleSystem:enemyAction()
self.enemy:aiAction(self.player)
end
function BattleSystem:checkEnd()
if self.enemy.hp <= 0 then
print(string.format("%s 被击败了!", self.enemy.name))
local expGain = self.enemy.level * 50
local goldGain = self.enemy.level * 10
self.player:gainExp(expGain)
self.player.gold = self.player.gold + goldGain
print(string.format("获得 %d 金币", goldGain))
return "victory"
elseif self.player.hp <= 0 then
print(string.format("%s 被击败了...", self.player.name))
return "defeat"
end
return nil
end
function BattleSystem:run()
self:start()
while true do
self:nextTurn()
self:playerAction("attack", self.enemy)
local result = self:checkEnd()
if result then
print("=== 战斗结束 ===\n")
return result
end
self:enemyAction()
result = self:checkEnd()
if result then
print("=== 战斗结束 ===\n")
return result
end
end
end
-- 任务系统
Quest = {
id = "",
name = "",
description = "",
objectives = {},
rewards = {},
status = "available"
}
function Quest:new(id, name, description)
local obj = {
id = id,
name = name,
description = description,
objectives = {},
rewards = {},
status = "available"
}
setmetatable(obj, self)
self.__index = self
return obj
end
function Quest:addObjective(description, target, count)
table.insert(self.objectives, {
description = description,
target = target,
count = count,
current = 0
})
end
function Quest:addReward(type, value)
table.insert(self.rewards, {type = type, value = value})
end
function Quest:updateObjective(target)
for _, obj in ipairs(self.objectives) do
if obj.target == target and obj.current < obj.count then
obj.current = obj.current + 1
print(string.format("任务进度: %s (%d/%d)",
obj.description, obj.current, obj.count))
end
end
self:checkCompletion()
end
function Quest:checkCompletion()
local allComplete = true
for _, obj in ipairs(self.objectives) do
if obj.current < obj.count then
allComplete = false
break
end
end
if allComplete and self.status ~= "completed" then
self.status = "completed"
print(string.format("任务完成: %s", self.name))
return true
end
return false
end
-- 游戏主循环
function startGame()
print("========================================")
print(" 欢迎来到 Lua RPG!")
print("========================================")
io.write("请输入角色名: ")
local name = io.read()
local player = Player:new(name)
-- 添加技能
player.skills["火球术"] = Skill:new("火球术", 10, fireballEffect)
player.skills["治疗术"] = Skill:new("治疗术", 15, healEffect)
print(string.format("\n欢迎, %s!", name))
print("你的冒险开始了...\n")
-- 创建任务
local mainQuest = Quest:new("quest_001", "击败史莱姆", "消灭3只史莱姆")
mainQuest:addObjective("击败史莱姆", "slime", 3)
mainQuest:addReward("gold", 100)
mainQuest:addReward("exp", 200)
GameState.player = player
GameState.quests["quest_001"] = mainQuest
-- 战斗循环
for i = 1, 3 do
local enemy = Enemy:new("史莱姆", i, "aggressive")
local battle = BattleSystem:new(player, enemy)
local result = battle:run()
if result == "defeat" then
print("游戏结束")
return
end
-- 更新任务
mainQuest:updateObjective("slime")
-- 恢复
player.hp = player.maxHp
player.mp = player.maxMp
end
print("\n========================================")
print(" 恭喜通关!")
print("========================================")
print(string.format("最终等级: %d", player.level))
print(string.format("金币: %d", player.gold))
print(string.format("力量: %d 敏捷: %d 智力: %d",
player.stats.strength, player.stats.agility, player.stats.intelligence))
end
-- startGame()
配置系统是应用程序管理设置的核心组件。Lua的表结构非常适合存储层次化配置,字符串处理能力可以解析多种配置格式(INI、JSON、YAML等)。核心原理包括:使用表存储配置数据,支持嵌套结构和类型转换;实现热重载机制,无需重启即可更新配置;提供默认值和验证机制,确保配置有效性;支持环境变量覆盖,适应不同部署环境;实现配置继承和合并,简化复杂配置管理。
-- config.lua - 完整配置管理系统
Config = {
data = {},
file = "config.ini",
watchers = {},
validators = {}
}
function Config:new(filename)
local obj = {
data = {},
file = filename or "config.ini",
watchers = {},
validators = {}
}
setmetatable(obj, self)
self.__index = self
return obj
end
-- 加载INI格式配置文件
function Config:load(filename)
self.file = filename or self.file
local file = io.open(self.file, "r")
if not file then
return false, "无法打开文件: " .. self.file
end
local section = "default"
local lineNum = 0
local errors = {}
for line in file:lines() do
lineNum = lineNum + 1
line = line:match("^%s*(.-)%s*$") -- 去除首尾空格
if line ~= "" and not line:match("^;") and not line:match("^#") then
local sec = line:match("^%[(.+)%]$")
if sec then
section = sec
self.data[section] = self.data[section] or {}
else
local key, value = line:match("^([^=]+)=(.+)$")
if key and value then
key = key:match("^%s*(.-)%s*$")
value = value:match("^%s*(.-)%s*$")
-- 类型转换
value = self:convertValue(value)
self.data[section] = self.data[section] or {}
self.data[section][key] = value
else
table.insert(errors, string.format("第%d行: 格式错误", lineNum))
end
end
end
end
file:close()
if #errors > 0 then
return false, table.concat(errors, "\n")
end
-- 验证配置
local validationErrors = self:validate()
if #validationErrors > 0 then
return false, table.concat(validationErrors, "\n")
end
return true
end
-- 值类型转换
function Config:convertValue(value)
-- 布尔值
if value:lower() == "true" then
return true
elseif value:lower() == "false" then
return false
end
-- 数字
local num = tonumber(value)
if num then
return num
end
-- 数组(逗号分隔)
if value:match(",") then
local arr = {}
for item in value:gmatch("[^,]+") do
table.insert(arr, self:convertValue(item:match("^%s*(.-)%s*$")))
end
return arr
end
-- 字符串
return value
end
-- 保存配置文件
function Config:save(filename)
filename = filename or self.file
local file = io.open(filename, "w")
if not file then
return false, "无法创建文件: " .. filename
end
for section, values in pairs(self.data) do
file:write("[" .. section .. "]\n")
for key, value in pairs(values) do
local valueStr = self:serializeValue(value)
file:write(key .. "=" .. valueStr .. "\n")
end
file:write("\n")
end
file:close()
return true
end
-- 值序列化
function Config:serializeValue(value)
local valueType = type(value)
if valueType == "boolean" then
return value and "true" or "false"
elseif valueType == "table" then
local arr = {}
for _, v in ipairs(value) do
table.insert(arr, self:serializeValue(v))
end
return table.concat(arr, ",")
else
return tostring(value)
end
end
-- 获取配置值
function Config:get(section, key, default)
if self.data[section] and self.data[section][key] ~= nil then
return self.data[section][key]
end
-- 检查环境变量
local envKey = string.upper(string.format("%s_%s", section, key))
local envValue = os.getenv(envKey)
if envValue then
return self:convertValue(envValue)
end
return default
end
-- 设置配置值
function Config:set(section, key, value)
self.data[section] = self.data[section] or {}
self.data[section][key] = value
-- 通知观察者
self:notify(section, key, value)
end
-- 批量设置
function Config:setMany(section, values)
self.data[section] = self.data[section] or {}
for key, value in pairs(values) do
self.data[section][key] = value
end
end
-- 获取整个section
function Config:getSection(section)
return self.data[section] or {}
end
-- 添加验证器
function Config:addValidator(section, key, validator)
self.validators[section] = self.validators[section] or {}
self.validators[section][key] = validator
end
-- 验证配置
function Config:validate()
local errors = {}
for section, validators in pairs(self.validators) do
for key, validator in pairs(validators) do
local value = self:get(section, key)
local ok, err = validator(value)
if not ok then
table.insert(errors, string.format("[%s]%s: %s", section, key, err))
end
end
end
return errors
end
-- 添加观察者
function Config:watch(section, key, callback)
local watchKey = section .. "." .. key
self.watchers[watchKey] = self.watchers[watchKey] or {}
table.insert(self.watchers[watchKey], callback)
end
-- 通知观察者
function Config:notify(section, key, value)
local watchKey = section .. "." .. key
if self.watchers[watchKey] then
for _, callback in ipairs(self.watchers[watchKey]) do
callback(section, key, value)
end
end
end
-- 合并配置
function Config:merge(otherConfig)
for section, values in pairs(otherConfig.data) do
self.data[section] = self.data[section] or {}
for key, value in pairs(values) do
self.data[section][key] = value
end
end
end
-- 导出为JSON格式
function Config:toJSON()
local json = require("dkjson")
return json.encode(self.data, {indent = true})
end
-- 从JSON导入
function Config:fromJSON(jsonStr)
local json = require("dkjson")
local data, pos, err = json.decode(jsonStr, 1, nil)
if err then
return false, err
end
self.data = data
return true
end
-- 热重载
function Config:reload()
local oldData = self.data
local ok, err = self:load(self.file)
if ok then
return true
else
self.data = oldData
return false, err
end
end
-- 创建配置模板
function Config:createTemplate(filename)
local template = {
database = {
host = "localhost",
port = 3306,
username = "root",
password = "",
database = "myapp"
},
server = {
host = "0.0.0.0",
port = 8080,
workers = 4,
timeout = 30
},
logging = {
level = "info",
file = "app.log",
maxSize = "100M",
maxFiles = 10
}
}
local file = io.open(filename or "config.ini.template", "w")
if not file then
return false
end
file:write("; 配置文件模板\n")
file:write("; 复制此文件为 config.ini 并修改配置\n\n")
for section, values in pairs(template) do
file:write("[" .. section .. "]\n")
for key, value in pairs(values) do
file:write(key .. "=" .. tostring(value) .. "\n")
end
file:write("\n")
end
file:close()
return true
end
-- 使用示例
local config = Config:new("app.ini")
-- 添加验证器
config:addValidator("database", "port", function(value)
if type(value) ~= "number" or value < 1 or value > 65535 then
return false, "端口必须在1-65535之间"
end
return true
end)
config:addValidator("server", "workers", function(value)
if type(value) ~= "number" or value < 1 then
return false, "worker数量必须大于0"
end
return true
end)
-- 添加观察者
config:watch("server", "port", function(section, key, value)
print(string.format("配置变更: %s.%s = %d", section, key, value))
-- 这里可以添加重启服务等逻辑
end)
-- 加载配置
local ok, err = config:load()
if not ok then
print("加载配置失败: " .. err)
config:createTemplate()
print("已创建配置模板,请修改后重试")
return
end
-- 获取配置
local dbHost = config:get("database", "host", "localhost")
local dbPort = config:get("database", "port", 3306)
local serverPort = config:get("server", "port", 8080)
local logLevel = config:get("logging", "level", "info")
print(string.format("数据库: %s:%d", dbHost, dbPort))
print(string.format("服务器端口: %d", serverPort))
print(string.format("日志级别: %s", logLevel))
-- 修改配置
config:set("server", "port", 9000)
config:save()
-- 批量设置
config:setMany("database", {
host = "192.168.1.100",
port = 3307
})
config:save()
-- 获取整个section
local dbConfig = config:getSection("database")
print("数据库配置:", dbConfig.host, dbConfig.port)
-- 导出为JSON
print(config:toJSON())
Web服务器是网络编程的重要应用。Lua通过LuaSocket库实现网络通信,可以构建轻量级的HTTP服务器。核心原理包括:使用TCP套接字监听端口,接受客户端连接;解析HTTP请求报文,提取方法、路径、头部等信息;根据路由规则分发请求到对应的处理函数;生成HTTP响应报文,包含状态码、头部和内容;支持静态文件服务和动态内容生成;实现中间件机制,处理日志、认证、CORS等通用功能;支持WebSocket等实时通信协议。
-- server.lua - 完整HTTP Web服务器
local socket = require("socket")
Server = {
host = "127.0.0.1",
port = 8080,
routes = {},
middlewares = {},
staticDir = "public",
notFoundHandler = nil,
errorHandler = nil
}
function Server:new(host, port)
local obj = {
host = host or "127.0.0.1",
port = port or 8080,
routes = {},
middlewares = {},
staticDir = "public",
notFoundHandler = nil,
errorHandler = nil
}
setmetatable(obj, self)
self.__index = self
return obj
end
-- 添加路由
function Server:route(method, path, handler)
local key = method:upper() .. ":" .. path
self.routes[key] = handler
end
-- 简化路由方法
function Server:get(path, handler)
self:route("GET", path, handler)
end
function Server:post(path, handler)
self:route("POST", path, handler)
end
function Server:put(path, handler)
self:route("PUT", path, handler)
end
function Server:delete(path, handler)
self:route("DELETE", path, handler)
end
-- 添加中间件
function Server:use(middleware)
table.insert(self.middlewares, middleware)
end
-- 解析HTTP请求
function Server:parseRequest(request)
local lines = {}
for line in request:gmatch("[^\r\n]+") do
table.insert(lines, line)
end
-- 解析请求行
local requestLine = lines[1]
local method, path, version = requestLine:match("^(%w+)%s+([^%s]+)%s+HTTP/(%d%.%d)$")
-- 解析头部
local headers = {}
local bodyStart = 2
for i = 2, #lines do
if lines[i] == "" then
bodyStart = i + 1
break
end
local key, value = lines[i]:match("^([^:]+):%s*(.+)$")
if key then
headers[key:lower()] = value
end
end
-- 解析请求体
local body = ""
if bodyStart <= #lines then
body = table.concat(lines, "\r\n", bodyStart)
end
-- 解析查询参数
local queryParams = {}
local queryString = path:match("^(.-)%?(.+)$")
if queryString then
path = path:match("^(.-)%?")
for param in queryString:gmatch("([^&]+)") do
local key, value = param:match("^([^=]+)=(.+)$")
if key then
queryParams[key] = self:urlDecode(value)
end
end
end
return {
method = method,
path = path,
version = version,
headers = headers,
body = body,
queryParams = queryParams
}
end
-- URL解码
function Server:urlDecode(str)
return str:gsub("%%(%x%x)", function(hex)
return string.char(tonumber(hex, 16))
end)
end
-- URL编码
function Server:urlEncode(str)
return str:gsub("[^%w%-_%.~]", function(c)
return string.format("%%%02X", string.byte(c))
end)
end
-- 创建响应
function Server:response(status, body, headers)
headers = headers or {}
headers["Content-Type"] = headers["Content-Type"] or "text/html; charset=utf-8"
headers["Content-Length"] = #body
local statusText = {
[200] = "OK",
[201] = "Created",
[204] = "No Content",
[301] = "Moved Permanently",
[302] = "Found",
[400] = "Bad Request",
[401] = "Unauthorized",
[403] = "Forbidden",
[404] = "Not Found",
[405] = "Method Not Allowed",
[500] = "Internal Server Error",
[502] = "Bad Gateway",
[503] = "Service Unavailable"
}
local responseLines = {
string.format("HTTP/1.1 %d %s", status, statusText[status] or "Unknown")
}
for key, value in pairs(headers) do
table.insert(responseLines, string.format("%s: %s", key, value))
end
table.insert(responseLines, "")
table.insert(responseLines, body)
return table.concat(responseLines, "\r\n")
end
-- JSON响应
function Server:jsonResponse(data, status)
local json = require("dkjson")
local body = json.encode(data, {indent = true})
return self:response(status or 200, body, {
["Content-Type"] = "application/json; charset=utf-8"
})
end
-- 重定向
function Server:redirect(url, status)
status = status or 302
return self:response(status, "", {
["Location"] = url
})
end
-- 静态文件服务
function Server:serveStatic(filePath)
local fullPath = self.staticDir .. filePath
local file = io.open(fullPath, "rb")
if not file then
return nil, "File not found"
end
local content = file:read("*all")
file:close()
-- 根据扩展名设置Content-Type
local ext = filePath:match("%.([^.]+)$")
local mimeTypes = {
html = "text/html",
css = "text/css",
js = "application/javascript",
json = "application/json",
png = "image/png",
jpg = "image/jpeg",
jpeg = "image/jpeg",
gif = "image/gif",
svg = "image/svg+xml",
ico = "image/x-icon",
txt = "text/plain",
pdf = "application/pdf"
}
local contentType = mimeTypes[ext] or "application/octet-stream"
return self:response(200, content, {
["Content-Type"] = contentType
})
end
-- 处理请求
function Server:handleRequest(client)
local request, err = client:receive("*l")
if not request then
client:close()
return
end
-- 读取完整的请求
local headers = {}
local contentLength = 0
while true do
local line, err = client:receive("*l")
if not line or line == "" then
break
end
local key, value = line:match("^([^:]+):%s*(.+)$")
if key then
headers[key:lower()] = value
if key:lower() == "content-length" then
contentLength = tonumber(value) or 0
end
end
end
-- 读取请求体
local body = ""
if contentLength > 0 then
body = client:receive(contentLength)
end
-- 构建完整请求
local fullRequest = request .. "\r\n"
for k, v in pairs(headers) do
fullRequest = fullRequest .. k .. ": " .. v .. "\r\n"
end
fullRequest = fullRequest .. "\r\n" .. body
-- 解析请求
local req = self:parseRequest(fullRequest)
-- 执行中间件
local middlewareResult = nil
for _, middleware in ipairs(self.middlewares) do
local result = middleware(req)
if result then
middlewareResult = result
break
end
end
local response
if middlewareResult then
response = middlewareResult
else
-- 查找路由
local routeKey = req.method .. ":" .. req.path
local handler = self.routes[routeKey]
if handler then
local ok, result = pcall(handler, req)
if ok then
if type(result) == "string" then
response = self:response(200, result)
else
response = result
end
else
if self.errorHandler then
response = self.errorHandler(req, result)
else
response = self:response(500, "Internal Server Error: " .. result)
end
end
else
-- 尝试静态文件
local staticResponse, err = self:serveStatic(req.path)
if staticResponse then
response = staticResponse
else
if self.notFoundHandler then
response = self.notFoundHandler(req)
else
response = self:response(404, "Not Found")
end
end
end
end
client:send(response)
client:close()
end
-- 启动服务器
function Server:start()
local server, err = socket.bind(self.host, self.port)
if not server then
print("无法启动服务器: " .. err)
return false
end
server:settimeout(0)
print(string.format("服务器启动: http://%s:%d", self.host, self.port))
print("按 Ctrl+C 停止服务器\n")
local clients = {}
while true do
-- 接受新连接
local client = server:accept()
if client then
client:settimeout(10)
table.insert(clients, client)
end
-- 处理客户端请求
local activeClients = {}
for _, client in ipairs(clients) do
local ok, err = pcall(function()
self:handleRequest(client)
end)
if not ok then
print("处理请求错误: " .. err)
end
end
clients = activeClients
socket.sleep(0.01)
end
end
-- 使用示例
local app = Server:new("127.0.0.1", 8080)
-- 日志中间件
app:use(function(req)
print(string.format("[%s] %s %s", os.date("%Y-%m-%d %H:%M:%S"), req.method, req.path))
end)
-- CORS中间件
app:use(function(req)
if req.method == "OPTIONS" then
return app:response(200, "", {
["Access-Control-Allow-Origin"] = "*",
["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS",
["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
})
end
end)
-- 首页
app:get("/", function(req)
return [[
Lua Web服务器
欢迎使用Lua Web服务器
这是一个使用LuaSocket构建的简单HTTP服务器
]]
end)
-- API: 获取当前时间
app:get("/api/time", function(req)
return app:jsonResponse({
timestamp = os.time(),
datetime = os.date("%Y-%m-%d %H:%M:%S"),
timezone = os.date("%z")
})
end)
-- API: 服务器信息
app:get("/api/info", function(req)
return app:jsonResponse({
server = "Lua Web Server",
version = "1.0.0",
lua_version = _VERSION,
uptime = os.difftime(os.time(), os.time())
})
end)
-- API: 用户列表
app:get("/api/users", function(req)
local users = {
{id = 1, name = "张三", email = "zhangsan@example.com"},
{id = 2, name = "李四", email = "lisi@example.com"},
{id = 3, name = "王五", email = "wangwu@example.com"}
}
return app:jsonResponse(users)
end)
-- API: 创建用户
app:post("/api/users", function(req)
local json = require("dkjson")
local data, pos, err = json.decode(req.body, 1, nil)
if err then
return app:jsonResponse({
error = "Invalid JSON",
message = err
}, 400)
end
-- 这里应该保存到数据库
data.id = math.random(1000, 9999)
return app:jsonResponse(data, 201)
end)
-- API: 获取用户详情
app:get("/api/users/:id", function(req)
local id = req.path:match("/api/users/(%d+)")
if not id then
return app:jsonResponse({error = "Invalid user ID"}, 400)
end
-- 这里应该从数据库查询
local user = {
id = tonumber(id),
name = "用户" .. id,
email = "user" .. id .. "@example.com"
}
return app:jsonResponse(user)
end)
-- 404处理
app.notFoundHandler = function(req)
return app:jsonResponse({
error = "Not Found",
path = req.path,
method = req.method
}, 404)
end
-- 错误处理
app.errorHandler = function(req, err)
return app:jsonResponse({
error = "Internal Server Error",
message = err
}, 500)
end
-- app:start()
日志系统是应用程序监控和调试的重要工具。Lua的文件I/O和字符串格式化能力使其能够构建高效的日志系统。核心原理包括:使用日志级别(DEBUG、INFO、WARN、ERROR、FATAL)过滤不同重要性的日志;支持多种输出目标(文件、控制台、网络、数据库);实现日志轮转,防止单个日志文件过大;支持结构化日志(JSON格式),便于日志分析;提供上下文信息(时间戳、线程ID、调用栈等);支持异步日志,避免阻塞主线程;实现日志格式化和颜色输出,提高可读性。
-- logger.lua - 完整日志系统
Logger = {
level = {
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4,
FATAL = 5
},
currentLevel = 2,
file = nil,
filename = "app.log",
maxSize = 10 * 1024 * 1024, -- 10MB
maxFiles = 5,
format = "text", -- text 或 json
colorOutput = true,
context = {},
handlers = {}
}
function Logger:new(filename, level)
local obj = {
currentLevel = level or 2,
filename = filename or "app.log",
file = nil,
maxSize = 10 * 1024 * 1024,
maxFiles = 5,
format = "text",
colorOutput = true,
context = {},
handlers = {}
}
setmetatable(obj, self)
self.__index = self
return obj
end
-- 打开日志文件
function Logger:open()
self.file = io.open(self.filename, "a")
if self.file then
self:checkRotation()
end
return self.file ~= nil
end
-- 关闭日志文件
function Logger:close()
if self.file then
self.file:close()
self.file = nil
end
end
-- 检查日志轮转
function Logger:checkRotation()
if not self.file then
return
end
local file = io.open(self.filename, "r")
if not file then
return
end
local size = file:seek("end")
file:close()
if size >= self.maxSize then
self:rotate()
end
end
-- 日志轮转
function Logger:rotate()
if self.file then
self.file:close()
end
-- 删除最旧的日志文件
local oldestFile = self.filename .. "." .. self.maxFiles
os.remove(oldestFile)
-- 重命名现有日志文件
for i = self.maxFiles - 1, 1, -1 do
local oldFile = self.filename .. "." .. i
local newFile = self.filename .. "." .. (i + 1)
os.rename(oldFile, newFile)
end
-- 重命名当前日志文件
os.rename(self.filename, self.filename .. ".1")
-- 重新打开日志文件
self.file = io.open(self.filename, "a")
end
-- 添加上下文
function Logger:addContext(key, value)
self.context[key] = value
end
-- 移除上下文
function Logger:removeContext(key)
self.context[key] = nil
end
-- 清除所有上下文
function Logger:clearContext()
self.context = {}
end
-- 添加处理器
function Logger:addHandler(handler)
table.insert(self.handlers, handler)
end
-- 获取颜色代码
function Logger:getColor(level)
if not self.colorOutput then
return ""
end
local colors = {
[1] = "\27[36m", -- DEBUG - 青色
[2] = "\27[32m", -- INFO - 绿色
[3] = "\27[33m", -- WARN - 黄色
[4] = "\27[31m", -- ERROR - 红色
[5] = "\27[35m" -- FATAL - 紫色
}
return colors[level] or ""
end
-- 重置颜色
function Logger:resetColor()
if not self.colorOutput then
return ""
end
return "\27[0m"
end
-- 格式化日志(文本格式)
function Logger:formatText(level, message, context)
local levelNames = {"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
local color = self:getColor(level)
local reset = self:resetColor()
local contextStr = ""
if next(context) then
local contextParts = {}
for k, v in pairs(context) do
table.insert(contextParts, string.format("%s=%s", k, v))
end
contextStr = " [" .. table.concat(contextParts, ", ") .. "]"
end
local logLine = string.format("%s[%s] [%s] %s%s",
color, timestamp, levelNames[level], message, contextStr)
if self.colorOutput then
logLine = logLine .. reset
end
return logLine .. "\n"
end
-- 格式化日志(JSON格式)
function Logger:formatJSON(level, message, context)
local levelNames = {"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
local logData = {
timestamp = timestamp,
level = levelNames[level],
level_value = level,
message = message,
context = context
}
local json = require("dkjson")
return json.encode(logData) .. "\n"
end
-- 记录日志
function Logger:log(level, message, extraContext)
if level < self.currentLevel then
return
end
-- 合并上下文
local context = {}
for k, v in pairs(self.context) do
context[k] = v
end
if extraContext then
for k, v in pairs(extraContext) do
context[k] = v
end
end
-- 格式化日志
local logLine
if self.format == "json" then
logLine = self:formatJSON(level, message, context)
else
logLine = self:formatText(level, message, context)
end
-- 输出到控制台
io.write(logLine)
io.flush()
-- 输出到文件
if self.file then
self.file:write(logLine)
self.file:flush()
self:checkRotation()
end
-- 调用处理器
for _, handler in ipairs(self.handlers) do
handler(level, message, context)
end
end
-- 获取调用栈
function Logger:getStackTrace(level)
level = level or 2
local trace = debug.traceback("", level)
return trace
end
-- 记录带调用栈的日志
function Logger:logWithTrace(level, message)
self:log(level, message)
self:log(level, self:getStackTrace(3))
end
-- 日志级别方法
function Logger:debug(message, context)
self:log(self.level.DEBUG, message, context)
end
function Logger:info(message, context)
self:log(self.level.INFO, message, context)
end
function Logger:warn(message, context)
self:log(self.level.WARN, message, context)
end
function Logger:error(message, context)
self:log(self.level.ERROR, message, context)
end
function Logger:fatal(message, context)
self:log(self.level.FATAL, message, context)
end
-- 性能日志
function Logger:timer(name)
local startTime = os.clock()
return {
stop = function()
local elapsed = os.clock() - startTime
self:debug(string.format("Timer[%s]: %.3fms", name, elapsed * 1000))
end
}
end
-- 异常捕获
function Logger:protect(fn)
return function(...)
local ok, err = pcall(fn, ...)
if not ok then
self:error("Protected function failed: " .. tostring(err))
self:error(self:getStackTrace(3))
return nil, err
end
return err
end
end
-- 设置日志级别
function Logger:setLevel(level)
if type(level) == "string" then
level = self.level[level:upper()]
end
if level and level >= 1 and level <= 5 then
self.currentLevel = level
end
end
-- 使用示例
local logger = Logger:new("app.log", Logger.level.DEBUG)
logger:open()
-- 添加上下文
logger:addContext("app", "myapp")
logger:addContext("version", "1.0.0")
-- 添加处理器(例如发送到远程日志服务)
logger:addHandler(function(level, message, context)
-- 这里可以添加发送到远程服务的逻辑
-- 例如:发送到ELK、Sentry等
end)
-- 基本日志
logger:debug("调试信息")
logger:info("应用启动")
logger:warn("警告信息")
logger:error("错误信息")
logger:fatal("致命错误")
-- 带上下文的日志
logger:info("用户登录", {
user_id = 123,
username = "zhangsan",
ip = "192.168.1.100"
})
-- 性能计时
local timer = logger:timer("database_query")
-- 执行数据库查询
timer:stop()
-- 异常保护
local safeFunction = logger:protect(function()
-- 可能出错的代码
error("Something went wrong!")
end)
safeFunction()
-- 记录调用栈
logger:logWithTrace(Logger.level.ERROR, "发生错误")
-- 切换到JSON格式
logger.format = "json"
logger:info("JSON格式日志", {key = "value"})
logger:close()
ORM(对象关系映射)是连接应用程序和数据库的重要桥梁。Lua的表和元表机制非常适合实现ORM。核心原理包括:使用表映射数据库表结构,通过元表实现查询构建器;支持CRUD操作(创建、读取、更新、删除);实现关联关系(一对一、一对多、多对多);提供查询接口,支持条件、排序、分页等;实现事务管理,确保数据一致性;支持连接池,提高数据库连接效率;提供数据验证和类型转换;支持软删除和审计字段。
-- orm.lua - 完整数据库ORM
local sqlite3 = require("lsqlite3")
-- 查询构建器
QueryBuilder = {
table = "",
select = {"*"},
where = {},
joins = {},
orderBy = {},
limit = nil,
offset = nil,
params = {}
}
function QueryBuilder:new(table)
local obj = {
table = table,
select = {"*"},
where = {},
joins = {},
orderBy = {},
limit = nil,
offset = nil,
params = {}
}
setmetatable(obj, self)
self.__index = self
return obj
end
function QueryBuilder:select(...)
self.select = {...}
return self
end
function QueryBuilder:where(condition, ...)
table.insert(self.where, condition)
for _, v in ipairs({...}) do
table.insert(self.params, v)
end
return self
end
function QueryBuilder:join(table, condition, ...)
table.insert(self.joins, {type = "JOIN", table = table, condition = condition})
for _, v in ipairs({...}) do
table.insert(self.params, v)
end
return self
end
function QueryBuilder:leftJoin(table, condition, ...)
table.insert(self.joins, {type = "LEFT JOIN", table = table, condition = condition})
for _, v in ipairs({...}) do
table.insert(self.params, v)
end
return self
end
function QueryBuilder:orderBy(column, direction)
direction = direction or "ASC"
table.insert(self.orderBy, {column = column, direction = direction})
return self
end
function QueryBuilder:take(limit)
self.limit = limit
return self
end
function QueryBuilder:skip(offset)
self.offset = offset
return self
end
function QueryBuilder:toSQL()
local sql = "SELECT " .. table.concat(self.select, ", ")
sql = sql .. " FROM " .. self.table
for _, join in ipairs(self.joins) do
sql = sql .. " " .. join.type .. " " .. join.table .. " ON " .. join.condition
end
if #self.where > 0 then
sql = sql .. " WHERE " .. table.concat(self.where, " AND ")
end
if #self.orderBy > 0 then
local orderParts = {}
for _, order in ipairs(self.orderBy) do
table.insert(orderParts, order.column .. " " .. order.direction)
end
sql = sql .. " ORDER BY " .. table.concat(orderParts, ", ")
end
if self.limit then
sql = sql .. " LIMIT " .. self.limit
end
if self.offset then
sql = sql .. " OFFSET " .. self.offset
end
return sql
end
-- 模型基类
Model = {
tableName = "",
db = nil,
primaryKey = "id",
timestamps = true,
softDelete = false
}
function Model:new(tableName, db)
local obj = {
tableName = tableName,
db = db,
primaryKey = "id",
timestamps = true,
softDelete = false
}
setmetatable(obj, self)
self.__index = self
return obj
end
-- 连接数据库
function Model:connect(dbFile)
self.db = sqlite3.open(dbFile)
return self.db ~= nil
end
-- 关闭数据库
function Model:close()
if self.db then
self.db:close()
end
end
-- 开始事务
function Model:beginTransaction()
if self.db then
self.db:execute("BEGIN TRANSACTION")
end
end
-- 提交事务
function Model:commit()
if self.db then
self.db:execute("COMMIT")
end
end
-- 回滚事务
function Model:rollback()
if self.db then
self.db:execute("ROLLBACK")
end
end
-- 创建记录
function Model:create(data)
local keys = {}
local values = {}
local placeholders = {}
for k, v in pairs(data) do
table.insert(keys, k)
table.insert(values, v)
table.insert(placeholders, "?")
end
if self.timestamps then
table.insert(keys, "created_at")
table.insert(keys, "updated_at")
local now = os.date("%Y-%m-%d %H:%M:%S")
table.insert(values, now)
table.insert(values, now)
table.insert(placeholders, "?")
table.insert(placeholders, "?")
end
local sql = string.format(
"INSERT INTO %s (%s) VALUES (%s)",
self.tableName,
table.concat(keys, ", "),
table.concat(placeholders, ", ")
)
local stmt = self.db:prepare(sql)
for i, v in ipairs(values) do
stmt:bind(i, v)
end
stmt:step()
stmt:finalize()
return self.db:last_insert_rowid()
end
-- 批量创建
function Model:createMany(dataList)
self:beginTransaction()
local ids = {}
for _, data in ipairs(dataList) do
local id = self:create(data)
table.insert(ids, id)
end
self:commit()
return ids
end
-- 查找记录
function Model:find(id)
local sql = string.format("SELECT * FROM %s WHERE %s = ?", self.tableName, self.primaryKey)
local stmt = self.db:prepare(sql)
stmt:bind(1, id)
if stmt:step() == sqlite3.ROW then
local result = {}
for i = 0, stmt:columns() - 1 do
result[stmt:get_name(i)] = stmt:get_value(i)
end
stmt:finalize()
return result
end
stmt:finalize()
return nil
end
-- 查找或创建
function Model:findOrCreate(conditions, data)
local query = QueryBuilder:new(self.tableName)
for k, v in pairs(conditions) do
query:where(k .. " = ?", v)
end
local sql = query:toSQL()
local stmt = self.db:prepare(sql)
for i, v in ipairs(query.params) do
stmt:bind(i, v)
end
if stmt:step() == sqlite3.ROW then
local result = {}
for i = 0, stmt:columns() - 1 do
result[stmt:get_name(i)] = stmt:get_value(i)
end
stmt:finalize()
return result, false
end
stmt:finalize()
local newData = {}
for k, v in pairs(conditions) do
newData[k] = v
end
for k, v in pairs(data) do
newData[k] = v
end
local id = self:create(newData)
return self:find(id), true
end
-- 获取所有记录
function Model:all()
local sql = string.format("SELECT * FROM %s", self.tableName)
local results = {}
for row in self.db:nrows(sql) do
table.insert(results, row)
end
return results
end
-- 查询构建器
function Model:query()
return QueryBuilder:new(self.tableName)
end
-- 执行查询
function Model:execute(query)
local sql = query:toSQL()
local stmt = self.db:prepare(sql)
for i, v in ipairs(query.params) do
stmt:bind(i, v)
end
local results = {}
while stmt:step() == sqlite3.ROW do
local result = {}
for i = 0, stmt:columns() - 1 do
result[stmt:get_name(i)] = stmt:get_value(i)
end
table.insert(results, result)
end
stmt:finalize()
return results
end
-- 更新记录
function Model:update(id, data)
local sets = {}
local values = {}
for k, v in pairs(data) do
table.insert(sets, k .. " = ?")
table.insert(values, v)
end
if self.timestamps then
local now = os.date("%Y-%m-%d %H:%M:%S")
table.insert(sets, "updated_at = ?")
table.insert(values, now)
end
table.insert(values, id)
local sql = string.format(
"UPDATE %s SET %s WHERE %s = ?",
self.tableName,
table.concat(sets, ", "),
self.primaryKey
)
local stmt = self.db:prepare(sql)
for i, v in ipairs(values) do
stmt:bind(i, v)
end
stmt:step()
stmt:finalize()
end
-- 批量更新
function Model:updateMany(conditions, data)
local sets = {}
local values = {}
local whereConditions = {}
for k, v in pairs(data) do
table.insert(sets, k .. " = ?")
table.insert(values, v)
end
if self.timestamps then
local now = os.date("%Y-%m-%d %H:%M:%S")
table.insert(sets, "updated_at = ?")
table.insert(values, now)
end
for k, v in pairs(conditions) do
table.insert(whereConditions, k .. " = ?")
table.insert(values, v)
end
local sql = string.format(
"UPDATE %s SET %s WHERE %s",
self.tableName,
table.concat(sets, ", "),
table.concat(whereConditions, " AND ")
)
local stmt = self.db:prepare(sql)
for i, v in ipairs(values) do
stmt:bind(i, v)
end
stmt:step()
stmt:finalize()
end
-- 删除记录
function Model:delete(id)
local sql = string.format("DELETE FROM %s WHERE %s = ?", self.tableName, self.primaryKey)
local stmt = self.db:prepare(sql)
stmt:bind(1, id)
stmt:step()
stmt:finalize()
end
-- 批量删除
function Model:deleteMany(conditions)
local whereConditions = {}
local values = {}
for k, v in pairs(conditions) do
table.insert(whereConditions, k .. " = ?")
table.insert(values, v)
end
local sql = string.format(
"DELETE FROM %s WHERE %s",
self.tableName,
table.concat(whereConditions, " AND ")
)
local stmt = self.db:prepare(sql)
for i, v in ipairs(values) do
stmt:bind(i, v)
end
stmt:step()
stmt:finalize()
end
-- 统计记录数
function Model:count(conditions)
local sql = string.format("SELECT COUNT(*) as count FROM %s", self.tableName)
if conditions then
local whereConditions = {}
local values = {}
for k, v in pairs(conditions) do
table.insert(whereConditions, k .. " = ?")
table.insert(values, v)
end
sql = sql .. " WHERE " .. table.concat(whereConditions, " AND ")
local stmt = self.db:prepare(sql)
for i, v in ipairs(values) do
stmt:bind(i, v)
end
if stmt:step() == sqlite3.ROW then
local count = stmt:get_value(0)
stmt:finalize()
return count
end
stmt:finalize()
else
local stmt = self.db:prepare(sql)
if stmt:step() == sqlite3.ROW then
local count = stmt:get_value(0)
stmt:finalize()
return count
end
stmt:finalize()
end
return 0
end
-- 分页查询
function Model:paginate(page, perPage)
page = page or 1
perPage = perPage or 10
local offset = (page - 1) * perPage
local query = QueryBuilder:new(self.tableName)
query:take(perPage):skip(offset)
local sql = query:toSQL()
local stmt = self.db:prepare(sql)
local results = {}
while stmt:step() == sqlite3.ROW do
local result = {}
for i = 0, stmt:columns() - 1 do
result[stmt:get_name(i)] = stmt:get_value(i)
end
table.insert(results, result)
end
stmt:finalize()
local total = self:count()
local totalPages = math.ceil(total / perPage)
return {
data = results,
current_page = page,
per_page = perPage,
total = total,
total_pages = totalPages
}
end
-- 使用示例
local User = Model:new("users")
User:connect("test.db")
-- 创建表
User.db:execute([[
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
age INTEGER,
created_at TEXT,
updated_at TEXT
)
]])
-- 创建单个记录
local id = User:create({
name = "张三",
email = "zhangsan@example.com",
age = 25
})
print("创建用户ID: " .. id)
-- 批量创建
local ids = User:createMany({
{name = "李四", email = "lisi@example.com", age = 30},
{name = "王五", email = "wangwu@example.com", age = 28},
{name = "赵六", email = "zhaoliu@example.com", age = 35}
})
print("批量创建用户IDs: " .. table.concat(ids, ", "))
-- 查找记录
local user = User:find(id)
if user then
print(string.format("找到用户: %s, %s, %d岁", user.name, user.email, user.age))
end
-- 查找或创建
local user, created = User:findOrCreate(
{email = "newuser@example.com"},
{name = "新用户", age = 20}
)
print(string.format("用户 %s, 新创建: %s", user.name, created))
-- 获取所有记录
local allUsers = User:all()
print("总用户数: " .. #allUsers)
-- 使用查询构建器
local query = User:query()
:select("name", "email")
:where("age > ?", 25)
:orderBy("age", "DESC")
:take(2)
local results = User:execute(query)
for _, u in ipairs(results) do
print(string.format("查询结果: %s, %s", u.name, u.email))
end
-- 更新记录
User:update(id, {age = 26})
print("更新用户年龄为26")
-- 批量更新
User:updateMany({age = 25}, {age = 26})
print("将所有25岁用户更新为26岁")
-- 统计记录数
local count = User:count({age = 26})
print("26岁用户数: " .. count)
-- 分页查询
local page = User:paginate(1, 2)
print(string.format("第%d页,每页%d条,共%d条记录",
page.current_page, page.per_page, page.total))
-- 删除记录
User:delete(id)
print("删除用户ID: " .. id)
-- 批量删除
User:deleteMany({age = 20})
print("删除所有20岁用户")
User:close()
命令行工具是开发者日常工作的必备工具。Lua的字符串处理和表操作能力使其能够构建强大的CLI工具。核心原理包括:解析命令行参数,支持短选项(-h)和长选项(--help);实现子命令系统,支持多级命令结构;提供自动帮助生成,根据命令定义生成帮助信息;支持参数验证和类型转换;实现交互式提示,提高用户体验;支持配置文件和环境变量;提供彩色输出和进度显示;支持命令补全和别名。
-- cli.lua - 完整命令行工具
CLI = {
name = "app",
version = "1.0.0",
description = "命令行应用",
commands = {},
options = {},
globalOptions = {},
middlewares = {}
}
function CLI:new(name, version, description)
local obj = {
name = name or "app",
version = version or "1.0.0",
description = description or "命令行应用",
commands = {},
options = {},
globalOptions = {},
middlewares = {}
}
setmetatable(obj, self)
self.__index = self
return obj
end
-- 添加命令
function CLI:command(name, description, handler)
self.commands[name] = {
name = name,
description = description,
handler = handler,
options = {},
arguments = {}
}
return self.commands[name]
end
-- 添加全局选项
function CLI:option(name, short, description, default)
self.globalOptions[name] = {
short = short,
description = description,
default = default
}
end
-- 添加中间件
function CLI:use(middleware)
table.insert(self.middlewares, middleware)
end
-- 解析参数
function CLI:parse(args)
local command = nil
local options = {}
local arguments = {}
local globalOptions = {}
local i = 1
-- 解析全局选项
while i <= #args do
local arg = args[i]
if arg:match("^%-%-") then
local key, value = arg:match("^%-%-([^=]+)=?(.*)$")
if value == "" then
value = true
end
globalOptions[key] = value
i = i + 1
elseif arg:match("^%-") then
local key = arg:match("^%-(.+)$")
if #key > 1 then
for j = 1, #key do
globalOptions[key:sub(j, j)] = true
end
else
globalOptions[key] = true
end
i = i + 1
else
break
end
end
-- 获取命令
if i <= #args then
command = args[i]
i = i + 1
end
-- 解析命令选项和参数
while i <= #args do
local arg = args[i]
if arg:match("^%-%-") then
local key, value = arg:match("^%-%-([^=]+)=?(.*)$")
if value == "" then
value = true
end
options[key] = value
i = i + 1
elseif arg:match("^%-") then
local key = arg:match("^%-(.+)$")
if #key > 1 then
for j = 1, #key do
options[key:sub(j, j)] = true
end
else
options[key] = true
end
i = i + 1
else
table.insert(arguments, arg)
i = i + 1
end
end
return command, globalOptions, options, arguments
end
-- 显示版本
function CLI:showVersion()
print(string.format("%s v%s", self.name, self.version))
end
-- 显示帮助
function CLI:showHelp()
print(string.format("%s v%s - %s", self.name, self.version, self.description))
print("\n用法:")
print(string.format(" %s [全局选项] <命令> [命令选项] [参数]", self.name))
print("\n全局选项:")
if next(self.globalOptions) then
for name, opt in pairs(self.globalOptions) do
local short = opt.short and string.format("-%s, ", opt.short) or " "
print(string.format(" %s--%-20s %s", short, name, opt.description))
end
else
print(" (无)")
end
print("\n可用命令:")
if next(self.commands) then
for name, cmd in pairs(self.commands) do
print(string.format(" %-20s %s", name, cmd.description))
end
else
print(" (无)")
end
print("\n使用 '<命令> --help' 查看命令详细帮助")
end
-- 显示命令帮助
function CLI:showCommandHelp(commandName)
local cmd = self.commands[commandName]
if not cmd then
print("未知命令: " .. commandName)
return
end
print(string.format("命令: %s", cmd.name))
print(string.format("描述: %s", cmd.description))
print("\n用法:")
print(string.format(" %s %s [选项] [参数]", self.name, cmd.name))
if next(cmd.options) then
print("\n选项:")
for name, opt in pairs(cmd.options) do
local short = opt.short and string.format("-%s, ", opt.short) or " "
print(string.format(" %s--%-20s %s", short, name, opt.description))
end
end
if next(cmd.arguments) then
print("\n参数:")
for _, arg in ipairs(cmd.arguments) do
print(string.format(" %-20s %s", arg.name, arg.description))
end
end
end
-- 执行命令
function CLI:run(args)
local command, globalOpts, cmdOpts, arguments = self:parse(args)
-- 处理全局选项
if globalOpts.version or globalOpts.v then
self:showVersion()
return
end
if globalOpts.help or globalOpts.h or not command then
self:showHelp()
return
end
-- 查找命令
local cmd = self.commands[command]
if not cmd then
print("未知命令: " .. command)
self:showHelp()
return
end
-- 处理命令帮助
if cmdOpts.help or cmdOpts.h then
self:showCommandHelp(command)
return
end
-- 合并选项
local allOptions = {}
for k, v in pairs(globalOpts) do
allOptions[k] = v
end
for k, v in pairs(cmdOpts) do
allOptions[k] = v
end
-- 执行中间件
for _, middleware in ipairs(self.middlewares) do
local result = middleware(command, allOptions, arguments)
if result == false then
return
end
end
-- 执行命令
local ok, err = pcall(cmd.handler, allOptions, arguments)
if not ok then
print("错误: " .. tostring(err))
os.exit(1)
end
end
-- 交互式提示
function CLI:prompt(message, default)
if default then
io.write(string.format("%s [%s]: ", message, default))
else
io.write(message .. ": ")
end
local input = io.read()
if input == "" then
return default
end
return input
end
-- 确认提示
function CLI:confirm(message)
io.write(message .. " [y/N]: ")
local input = io.read():lower()
return input == "y" or input == "yes"
end
-- 选择提示
function CLI:select(message, options)
print(message)
for i, opt in ipairs(options) do
print(string.format(" %d. %s", i, opt))
end
io.write("选择: ")
local input = tonumber(io.read())
if input and input >= 1 and input <= #options then
return input, options[input]
end
return nil
end
-- 进度条
function CLI:progress(current, total, width)
width = width or 50
local percent = current / total
local filled = math.floor(percent * width)
local empty = width - filled
io.write("\r[")
io.write(string.rep("=", filled))
io.write(string.rep(" ", empty))
io.write(string.format("] %d%%", math.floor(percent * 100)))
io.flush()
if current >= total then
print()
end
end
-- 彩色输出
function CLI:color(text, color)
local colors = {
black = "\27[30m",
red = "\27[31m",
green = "\27[32m",
yellow = "\27[33m",
blue = "\27[34m",
magenta = "\27[35m",
cyan = "\27[36m",
white = "\27[37m",
reset = "\27[0m"
}
local colorCode = colors[color] or ""
return colorCode .. text .. colors.reset
end
-- 成功消息
function CLI:success(message)
print(self:color("✓ " .. message, "green"))
end
-- 错误消息
function CLI:error(message)
print(self:color("✗ " .. message, "red"))
end
-- 警告消息
function CLI:warning(message)
print(self:color("⚠ " .. message, "yellow"))
end
-- 信息消息
function CLI:info(message)
print(self:color("ℹ " .. message, "blue"))
end
-- 使用示例
local app = CLI:new("myapp", "1.0.0", "我的命令行应用")
-- 添加全局选项
app:option("verbose", "v", "详细输出", false)
app:option("config", "c", "配置文件路径", nil)
-- 添加中间件
app:use(function(command, options, arguments)
if options.verbose then
print(string.format("执行命令: %s", command))
print(string.format("选项: %s", table.concat(arguments, ", ")))
end
end)
-- 添加命令
local startCmd = app:command("start", "启动服务器", function(opts, args)
local port = opts.port or 8080
local host = opts.host or "127.0.0.1"
app:success(string.format("服务器启动在 %s:%d", host, port))
app:info("按 Ctrl+C 停止服务器")
-- 这里添加实际的服务器启动逻辑
end)
startCmd.options = {
port = {short = "p", description = "服务器端口", default = 8080},
host = {short = "H", description = "服务器地址", default = "127.0.0.1"}
}
local stopCmd = app:command("stop", "停止服务器", function(opts, args)
app:success("服务器已停止")
end)
local buildCmd = app:command("build", "构建项目", function(opts, args)
local target = args[1] or "production"
app:info(string.format("开始构建: %s", target))
-- 模拟构建过程
for i = 1, 10 do
app:progress(i, 10)
os.execute("sleep 0.1")
end
app:success("构建完成")
end)
buildCmd.arguments = {
{name = "target", description = "构建目标 (development/production)"}
}
local userCmd = app:command("user", "用户管理", function(opts, args)
local action = args[1]
if action == "add" then
local name = app:prompt("用户名")
local email = app:prompt("邮箱")
local password = app:prompt("密码")
if app:confirm(string.format("创建用户 %s?", name)) then
app:success(string.format("用户 %s 创建成功", name))
else
app:info("取消创建用户")
end
elseif action == "list" then
app:info("用户列表:")
print(" 1. 张三 (zhangsan@example.com)")
print(" 2. 李四 (lisi@example.com)")
print(" 3. 王五 (wangwu@example.com)")
else
app:error("未知操作: " .. action)
end
end)
userCmd.arguments = {
{name = "action", description = "操作 (add/list/delete)"}
}
local initCmd = app:command("init", "初始化项目", function(opts, args)
local projectName = args[1] or app:prompt("项目名称")
app:info(string.format("初始化项目: %s", projectName))
local choices = {"空项目", "Web应用", "CLI工具", "库"}
local choiceIndex, choice = app:select("选择项目类型:", choices)
if choice then
app:success(string.format("创建 %s: %s", choice, projectName))
end
end)
-- 运行应用
app:run(arg)