Skip to content

云函数

云函数是 AuthNexus 提供的服务端可编程扩展能力。管理员通过 Admin API 部署 Lua 脚本,SDK 客户端在运行时调用,脚本在 server_app 侧的隔离沙箱中执行。

概述

什么是云函数

云函数是运行在 server_app 进程内的 Lua 5.4 脚本。它允许在不修改服务端二进制的前提下动态添加业务逻辑,典型场景包括:

  • 卡密验证与自定义校验规则
  • 用户登录后的附加业务逻辑
  • 动态计算配置值
  • 自定义数据查询与聚合
  • 活动规则与奖励计算

核心特性

特性说明
运行时Lua 5.4
隔离严格沙箱,禁用危险模块
唯一性每个 app_id + name 组合唯一
超时默认 1000ms
并发独立线程池 + 背压控制
部署通过 Admin API 热更新,无需重启

沙箱机制

禁用模块

为保证安全性,以下 Lua 标准模块/函数在沙箱中被完全移除:

禁用项原因
io文件系统访问
os操作系统命令执行
debug运行时内省,可绕过沙箱
package模块加载系统
require外部模块导入
dofile执行外部文件
loadfile加载外部文件

DANGER

这些限制不可配置,不可绕过。任何尝试调用上述函数/模块的脚本会立即报错。

可用标准库

沙箱内保留以下安全的 Lua 标准功能:

lua
-- 基础
print()           -- 输出(会记录到服务端日志)
type()            -- 类型检查
tostring()        -- 转字符串
tonumber()        -- 转数字
pairs()           -- 遍历表
ipairs()          -- 遍历数组
select()          -- 参数选择
unpack()          -- 表解包
pcall()           -- 保护调用
xpcall()          -- 带错误处理的保护调用
error()           -- 抛出错误
assert()          -- 断言

-- 字符串操作
string.byte()     string.char()     string.find()
string.format()   string.gmatch()   string.gsub()
string.len()      string.lower()    string.upper()
string.match()    string.rep()      string.reverse()
string.sub()

-- 表操作
table.concat()    table.insert()    table.remove()
table.sort()      table.move()      table.pack()
table.unpack()

-- 数学
math.abs()        math.ceil()       math.floor()
math.max()        math.min()        math.random()
math.randomseed() math.sqrt()       math.pi
-- 以及其他标准 math 函数

AuthNexus 扩展 API

沙箱内额外提供 AuthNexus 专用 API:

lua
-- JSON 操作
local obj = json.decode('{"key": "value"}')
local str = json.encode({key = "value"})

-- 获取当前请求上下文
local ctx = authnexus.get_context()
-- ctx.app_id     当前应用 ID
-- ctx.user_id    当前用户 ID(如已登录)
-- ctx.username   当前用户名
-- ctx.payload    调用时传入的负载字符串

函数管理

创建云函数

通过 Admin API 创建:

http
POST /admin/v1/cloud-functions
Content-Type: application/json

{
  "app_id": 1001,
  "name": "validate_license",
  "description": "验证许可证密钥格式",
  "script": "local ctx = authnexus.get_context()\nlocal data = json.decode(ctx.payload)\nif not data.key then\n  return json.encode({valid = false, reason = 'missing key'})\nend\nlocal pattern = '^%w%w%w%w%-%w%w%w%w%-%w%w%w%w$'\nif string.match(data.key, pattern) then\n  return json.encode({valid = true})\nelse\n  return json.encode({valid = false, reason = 'invalid format'})\nend"
}

更新云函数

http
PUT /admin/v1/cloud-functions/:id
Content-Type: application/json

{
  "script": "-- 更新后的脚本内容\n...",
  "description": "更新描述"
}

更新后通过配置推送机制自动下发到所有节点,无需重启。

删除云函数

http
DELETE /admin/v1/cloud-functions/:id

唯一性约束

每个 app_id + name 组合必须唯一:

  • 同一应用内不能有重名函数
  • 不同应用的同名函数互不影响
  • 业务实体禁止 app_id = 0(由 validate_business_app_id + schema CHECK (app_id > 0) 强制)

执行模型

独立线程池

云函数在专用的 cloud_function_threads 线程池中执行,与 IO / Logic / DB / Crypto 线程完全隔离:

SDK 请求 → IO 线程 → Logic 线程(解析) → CloudFunction 线程池(执行) → Logic 线程(响应) → IO 线程

这种隔离确保:

  • 云函数的 CPU 消耗不影响其他业务路径
  • 云函数超时不阻塞正常请求处理
  • 单个应用的云函数问题不影响整个节点

背压控制

通过 cloud_function_max_in_flight 参数控制并发执行数量:

  • 当正在执行的云函数数量达到上限时,新请求排队等待
  • 超过队列容量的请求返回过载错误
  • 配置可通过 server_runtime_settings 热更新

执行隔离

每次云函数调用创建独立的 Lua 状态(state),调用完成后销毁:

  • 不同调用之间无状态共享
  • 无全局变量污染风险
  • 内存随状态销毁自动释放

超时处理

默认超时

云函数默认执行超时为 1000ms(1 秒)。

超时行为

lua
-- 此脚本会被超时中断
while true do
  -- 无限循环
end
-- 服务端返回超时错误,不影响其他请求

超时触发时:

  1. Lua 状态被强制中断
  2. 返回超时错误响应给 SDK
  3. 记录警告日志
  4. 不影响线程池中的其他执行

超时建议

操作类型建议耗时说明
简单计算< 10ms字符串处理、数值计算
JSON 解析 + 逻辑< 50ms典型业务场景
复杂逻辑< 200ms多层嵌套判断、大量字符串操作
接近超时500ms+应考虑优化脚本或拆分逻辑

SDK 端调用

基本调用

cpp
authnexus::CloudFunctionRequest req;
req.function_name = "validate_license";
req.payload = R"({"key": "ABCD-EFGH-IJKL"})";

auto result = sdk.invoke_cloud_function(req);
if (result.success) {
    // result.response 是 Lua 脚本的 return 值(字符串)
    auto data = json::parse(result.response);
    bool valid = data["valid"].get<bool>();
} else {
    // result.error_message 包含错误信息
    // 可能是超时、脚本错误、函数不存在等
}

错误类型

错误说明处理建议
函数不存在指定名称的函数未部署检查函数名和 app_id
脚本错误Lua 运行时错误检查脚本语法和逻辑
超时执行超过 1000ms优化脚本或拆分逻辑
过载并发执行数达到上限稍后重试,或调大 max_in_flight
沙箱违规调用了禁用的函数/模块修改脚本,使用允许的 API

配置下发

下发流程

云函数脚本通过 Channel 1 的配置拉取机制下发到节点:

  1. 管理员通过 Admin API 创建/更新云函数
  2. CP 记录变更并发送 config.pending SSE hint(Channel 2)
  3. 节点通过 POST /cp/v2/nodes/:id/configs/pull 拉取 cloud_function manifest
  4. 节点通过 GET /cp/v2/objects/cloud-functions/:name?app_id= 拉取具体脚本体
  5. 节点应用成功后 ACK,失败则上报错误

版本一致性

所有节点最终会收到相同版本的云函数。在滚动更新过程中,短暂的版本不一致是正常的(最终一致性)。

典型用例

卡密格式验证

lua
local ctx = authnexus.get_context()
local data = json.decode(ctx.payload)

local key = data.card_key or ""

-- 校验格式:4组4位字母数字,连字符分隔
local pattern = "^%w%w%w%w%-%w%w%w%w%-%w%w%w%w%-%w%w%w%w$"
if not string.match(key, pattern) then
    return json.encode({
        valid = false,
        reason = "卡密格式不正确,应为 XXXX-XXXX-XXXX-XXXX"
    })
end

return json.encode({valid = true})

登录次数限制判断

lua
local ctx = authnexus.get_context()
local data = json.decode(ctx.payload)

local attempt_count = data.attempt_count or 0
local max_attempts = 5
local lockout_minutes = 30

if attempt_count >= max_attempts then
    return json.encode({
        allowed = false,
        lockout_remaining = lockout_minutes,
        message = string.format("登录失败次数过多,请 %d 分钟后重试", lockout_minutes)
    })
end

return json.encode({
    allowed = true,
    remaining_attempts = max_attempts - attempt_count
})

动态定价计算

lua
local ctx = authnexus.get_context()
local data = json.decode(ctx.payload)

local base_price = data.base_price or 0
local quantity = data.quantity or 1
local user_level = data.user_level or "normal"

-- 会员折扣
local discount = 1.0
if user_level == "vip" then
    discount = 0.9
elseif user_level == "svip" then
    discount = 0.8
end

-- 批量折扣
if quantity >= 100 then
    discount = discount * 0.95
elseif quantity >= 50 then
    discount = discount * 0.97
end

local total = math.floor(base_price * quantity * discount * 100) / 100

return json.encode({
    unit_price = math.floor(base_price * discount * 100) / 100,
    total_price = total,
    discount_rate = discount,
    quantity = quantity
})

开发建议

  1. 保持脚本简短:云函数应聚焦单一职责,复杂逻辑拆分为多个函数
  2. 使用 JSON 通讯:输入通过 ctx.payload 传入 JSON,输出 return JSON 字符串
  3. 注意超时:避免大循环和复杂递归,字符串操作注意数据量
  4. 错误处理:使用 pcall 包裹可能出错的逻辑,返回清晰的错误信息
  5. 测试先行:在开发环境充分测试后再部署到生产环境
  6. 幂等设计:同一请求多次调用应产生相同结果(网络重试场景)