
Node Functions 是 EdgeOne 的全面无服务器函数解决方案,为开发人员提供了无缝的动态后端能力。只需在项目的 node-functions 目录中创建 Node 函数,代码就会自动转换为基于 Node.js 的 API 端点。
这些端点可高效处理各种任务:
最重要的是,它完全消除了服务器管理的复杂性。Node Functions 开发者指南
git push 即可完成全栈应用的部署。EdgeOne Pages 不只是静态资源托管平台,它结合了 全球加速 + Node Functions+ Edge Functions,形成全栈一体化开发环境:

在本次实践中,我们使用 Node Functions + PostgreSQL 打造了实时日志存储系统。
ensureSchema 方法保证表结构与索引始终存在。npm install -g edgeone
edgeone -v # 查看版本
edgeone -h # 查看帮助edgeone login
edgeone whoami支持 Global(国际站) 与 China(中国站)。
edgeone pages init此命令会生成 edge-functions 或 node-functions 文件夹,以及示例函数。
// node-functions/_db.js
import pg from 'pg';
const { Pool } = pg;
const DATABASE_URL = process.env.DATABASE_URL;
if (!DATABASE_URL) {
throw new Error('DATABASE_URL 未配置');
}
export const pool = new Pool({
connectionString: DATABASE_URL,
});
let ready = false;
export async function ensureSchema() {
if (ready) return;
const ddl = `
CREATE TABLE IF NOT EXISTS edgeone_logs (
id SERIAL PRIMARY KEY,
received_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
request_id VARCHAR(64),
parent_request_id VARCHAR(64),
content_id VARCHAR(128),
request_time TIMESTAMP WITH TIME ZONE,
log_time TIMESTAMP WITH TIME ZONE,
edge_end_time TIMESTAMP WITH TIME ZONE,
client_ip INET,
client_port INTEGER,
client_region VARCHAR(32),
client_state VARCHAR(32),
client_device_type VARCHAR(16),
client_isp VARCHAR(128),
request_method VARCHAR(16),
request_scheme VARCHAR(16),
request_protocol VARCHAR(16),
request_ssl_protocol VARCHAR(16),
request_host VARCHAR(255),
request_url VARCHAR(2048),
request_url_query_string VARCHAR(2048),
request_referer VARCHAR(2048),
request_ua VARCHAR(1024),
edge_cache_status VARCHAR(32),
edge_server_id VARCHAR(255),
edge_server_ip INET,
edge_server_region VARCHAR(32),
edge_server_region_top_division VARCHAR(32),
edge_response_status_code INTEGER,
edge_response_time INTEGER,
edge_response_body_bytes INTEGER,
edge_response_bytes INTEGER,
request_bytes INTEGER,
origin_ip INET,
origin_response_status_code INTEGER,
raw JSONB NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_edgeone_logs_raw_gin ON edgeone_logs USING gin (raw);
CREATE INDEX IF NOT EXISTS idx_edgeone_logs_request_host_time ON edgeone_logs (request_host, request_time);
`;
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query(ddl);
await client.query('COMMIT');
ready = true;
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
}
export function json(body, status = 200, extraHeaders = {}) {
return new Response(JSON.stringify(body), {
status,
headers: { 'content-type': 'application/json; charset=utf-8', ...extraHeaders },
});
}// node-functions/accelerateLogs.js
import {pool, ensureSchema, json} from './_db.js';
function _noneIfDash(v) {
if (v === undefined || v === null) return null;
const s = String(v).trim();
return (s === '' || s === '-') ? null : v;
}
function _cleanMinusOne(v) {
if (v === undefined || v === null) return null;
const n = Number(v);
if (!Number.isFinite(n)) return null;
return n === -1 ? null : n;
}
function _safeInt(v) {
if (v === undefined || v === null) return null;
const n = Number(v);
return Number.isFinite(n) ? Math.trunc(n) : null;
}
function _safeIP(v) {
v = _noneIfDash(v);
if (v == null) return null;
try {
const s = String(v);
const isV6 = s.includes(':');
const isV4 = s.split('.').length === 4;
return (isV6 || isV4) ? s : null;
} catch {
return null;
}
}
function _parseTS(v) {
if (v === undefined || v === null) return null;
try {
const d = new Date(String(v));
return isNaN(d.getTime()) ? null : d;
} catch {
return null;
}
}
function mapEdgeLog(p) {
return {
request_id: p?.RequestID ?? null,
parent_request_id: p?.ParentRequestID ?? null,
content_id: p?.ContentID ?? null,
request_time: _parseTS(p?.RequestTime),
log_time: _parseTS(p?.LogTime),
edge_end_time: _parseTS(p?.EdgeEndTime),
client_ip: _safeIP(p?.ClientIP),
client_port: _safeInt(p?.ClientPort),
client_region: _noneIfDash(p?.ClientRegion),
client_state: _noneIfDash(p?.ClientState),
client_device_type: _noneIfDash(p?.ClientDeviceType),
client_isp: _noneIfDash(p?.ClientISP),
request_method: _noneIfDash(p?.RequestMethod),
request_scheme: _noneIfDash(p?.RequestScheme),
request_protocol: _noneIfDash(p?.RequestProtocol),
request_ssl_protocol: _noneIfDash(p?.RequestSSLProtocol),
request_host: _noneIfDash(p?.RequestHost),
request_url: p?.RequestUrl ?? null,
request_url_query_string: _noneIfDash(p?.RequestUrlQueryString),
request_referer: _noneIfDash(p?.RequestReferer),
request_ua: _noneIfDash(p?.RequestUA),
edge_cache_status: _noneIfDash(p?.EdgeCacheStatus),
edge_server_id: _noneIfDash(p?.EdgeServerID),
edge_server_ip: _safeIP(p?.EdgeServerIP),
edge_server_region: _noneIfDash(p?.EdgeSeverRegion),
edge_server_region_top_division: _noneIfDash(p?.EdgeServerRegionTopDivision),
edge_response_status_code: _safeInt(p?.EdgeResponseStatusCode),
edge_response_time: _cleanMinusOne(p?.EdgeResponseTime),
edge_response_body_bytes: _cleanMinusOne(p?.EdgeResponseBodyBytes),
edge_response_bytes: _cleanMinusOne(p?.EdgeResponseBytes),
request_bytes: _cleanMinusOne(p?.RequestBytes),
origin_ip: _safeIP(p?.OriginIP),
origin_response_status_code: _cleanMinusOne(p?.OriginResponseStatusCode),
};
}
const SQL = `
INSERT INTO edgeone_logs (request_id, parent_request_id, content_id,
request_time, log_time, edge_end_time,
client_ip, client_port, client_region, client_state, client_device_type, client_isp,
request_method, request_scheme, request_protocol, request_ssl_protocol, request_host,
request_url, request_url_query_string, request_referer, request_ua,
edge_cache_status, edge_server_id, edge_server_ip, edge_server_region,
edge_server_region_top_division,
edge_response_status_code, edge_response_time, edge_response_body_bytes,
edge_response_bytes, request_bytes,
origin_ip, origin_response_status_code,
raw)
VALUES ($1, $2, $3,
$4, $5, $6,
$7, $8, $9, $10, $11, $12,
$13, $14, $15, $16, $17, $18, $19, $20, $21,
$22, $23, $24, $25, $26,
$27, $28, $29, $30, $31,
$32, $33,
$34) RETURNING id;
`;
export async function onRequestPost(context) {
await ensureSchema();
let payload;
try {
payload = context.request.body;
} catch (error) {
console.log(error);
return json({error: 'Invalid JSON body'}, 400);
}
const c = mapEdgeLog(payload);
const params = [c.request_id, c.parent_request_id, c.content_id, c.request_time, c.log_time, c.edge_end_time, c.client_ip, c.client_port, c.client_region, c.client_state, c.client_device_type, c.client_isp, c.request_method, c.request_scheme, c.request_protocol, c.request_ssl_protocol, c.request_host, c.request_url, c.request_url_query_string, c.request_referer, c.request_ua, c.edge_cache_status, c.edge_server_id, c.edge_server_ip, c.edge_server_region, c.edge_server_region_top_division, c.edge_response_status_code, c.edge_response_time, c.edge_response_body_bytes, c.edge_response_bytes, c.request_bytes, c.origin_ip, c.origin_response_status_code, JSON.stringify(payload),];
try {
const {rows} = await pool.query(SQL, params);
return json({ok: true, id: rows?.[0]?.id ?? null}, 200);
} catch (e) {
console.error('edgeone_logs insert failed:', e);
return json({ok: false, error: 'db_insert_failed'}, 500);
}
}edgeone pages linkedgeone pages devhttp://localhost:8088/edgeone pages env 拉取配置文件到本地。(CLI会自动在项目根目录生成 .env 文件)
# 生产环境部署
edgeone pages deploy
# 预览环境部署
edgeone pages deploy -e preview构建成功后在 构建部署 中找到对应的部署记录,可以看到我们的 Node Functions 已经生效了。

然后需要到 项目设置 中配置对应的域名(Edgeone Pages可以生成免费SSL证书,同时如果您担心 CT(Certificate Transparency) 日志泄露,可能会导致暴露业务架构细节,可以在添加自定义域名时取消“自动生成免费证书”的勾选,之后再通过“配置证书”选择自定义证书。)



/accelerateLogs,点击推送即可部署对应的站点加速日志推送。

数据库中也可以看到对应的记录

通过 EdgeOne Pages + Node Functions + PostgreSQL,我们实现了:
这种架构充分体现了 “无服务器 + 边缘计算” 的优势,轻量、灵活、可扩展,适合现代应用场景。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。