<返回目录     Powered by claud/xia兄

第15课: 实战项目

项目设计原则

项目架构设计原则:

良好的Lua项目应该遵循模块化、可测试、可维护的设计原则。使用模块系统组织代码,将不同功能分离到独立的模块中。采用面向对象或函数式编程风格,保持代码一致性。使用协程处理异步操作,避免回调地狱。建立完善的错误处理机制,确保程序健壮性。编写单元测试,保证代码质量。使用版本控制和文档,便于团队协作。

-- 项目结构建议
project/
├── src/           -- 源代码
│   ├── core/     -- 核心模块
│   ├── utils/    -- 工具函数
│   └── config/   -- 配置管理
├── tests/         -- 测试代码
├── docs/          -- 文档
├── scripts/       -- 构建脚本
└── main.lua       -- 入口文件

-- 模块设计原则
-- 1. 单一职责:每个模块只做一件事
-- 2. 开闭原则:对扩展开放,对修改关闭
-- 3. 依赖倒置:依赖抽象而非具体实现
-- 4. 接口隔离:客户端不应依赖不需要的接口
-- 5. 最少知识:模块间最小化依赖

项目1: 游戏脚本系统

游戏脚本系统原理:

游戏脚本系统是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()

项目2: 配置系统

配置系统原理:

配置系统是应用程序管理设置的核心组件。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())

项目3: 简单Web服务器

Web服务器原理:

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

项目4: 日志系统

日志系统原理:

日志系统是应用程序监控和调试的重要工具。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()

项目5: 数据库ORM

数据库ORM原理:

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

项目6: 命令行工具

命令行工具原理:

命令行工具是开发者日常工作的必备工具。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)
项目总结:
进阶项目:
  1. 实现一个完整的MVC Web框架
  2. 开发一个游戏引擎的脚本系统
  3. 创建一个自动化测试框架
  4. 构建一个分布式任务调度系统