云函数
云函数是 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+ schemaCHECK (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
-- 服务端返回超时错误,不影响其他请求超时触发时:
- Lua 状态被强制中断
- 返回超时错误响应给 SDK
- 记录警告日志
- 不影响线程池中的其他执行
超时建议
| 操作类型 | 建议耗时 | 说明 |
|---|---|---|
| 简单计算 | < 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 的配置拉取机制下发到节点:
- 管理员通过 Admin API 创建/更新云函数
- CP 记录变更并发送
config.pendingSSE hint(Channel 2) - 节点通过
POST /cp/v2/nodes/:id/configs/pull拉取 cloud_function manifest - 节点通过
GET /cp/v2/objects/cloud-functions/:name?app_id=拉取具体脚本体 - 节点应用成功后 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
})开发建议
- 保持脚本简短:云函数应聚焦单一职责,复杂逻辑拆分为多个函数
- 使用 JSON 通讯:输入通过
ctx.payload传入 JSON,输出 return JSON 字符串 - 注意超时:避免大循环和复杂递归,字符串操作注意数据量
- 错误处理:使用
pcall包裹可能出错的逻辑,返回清晰的错误信息 - 测试先行:在开发环境充分测试后再部署到生产环境
- 幂等设计:同一请求多次调用应产生相同结果(网络重试场景)