首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Edgeone Pages Node Functions 新体验

Edgeone Pages Node Functions 新体验

原创
作者头像
没猫饼
发布2025-09-26 13:26:40
发布2025-09-26 13:26:40
1570
举报

什么是 Node Functions?

Node FunctionsEdgeOne 的全面无服务器函数解决方案,为开发人员提供了无缝的动态后端能力。只需在项目的 node-functions 目录中创建 Node 函数,代码就会自动转换为基于 Node.js 的 API 端点

这些端点可高效处理各种任务:

  • 数据库查询
  • 第三方 API 集成
  • 表单提交
  • 数据清洗与处理

最重要的是,它完全消除了服务器管理的复杂性。Node Functions 开发者指南


集成 Node Functions 的关键优势

  1. 统一的前后端管理 前端静态资源与后端逻辑整合在同一 Git 仓库中,共享配置、环境变量与 CI/CD 流水线。一次 git push 即可完成全栈应用的部署。
  2. 极简运维成本 函数按需执行,自动伸缩,运维复杂度与服务器开销大幅降低。
  3. 完整 npm 生态支持 可直接使用数百万 npm 包(数据库驱动、工具库、SDK),开发者只需聚焦业务逻辑。

EdgeOne Pages 简介

EdgeOne Pages 不只是静态资源托管平台,它结合了 全球加速 + Node Functions+ Edge Functions,形成全栈一体化开发环境:

  • 🚀 前端:静态站点托管与全球加速分发
  • 后端:Node Functions / Edge Functions 运行环境,支持数据库访问 & API 调用
  • 🛠 工具链:统一的 edgeone CLI,涵盖开发、调试、部署
  • 🌐 边缘节点:依托 EdgeOne 全球网络,前后端接口都能低延迟触达
「EdgeOne Pages 全栈架构」
「EdgeOne Pages 全栈架构」

实践场景:实时日志入库

在本次实践中,我们使用 Node Functions + PostgreSQL 打造了实时日志存储系统。

主要流程

  1. 接收日志推送 EdgeOne 会以 HTTP POST 推送日志到 Node Functions。
  2. 数据清洗与转换 使用工具函数处理 IP、时间戳、状态码,避免脏数据。
  3. 自动建表与索引 ensureSchema 方法保证表结构与索引始终存在。
  4. 入库 PostgreSQL 将原始日志以 JSONB 保存,支持后续灵活查询。

一、准备工作

  1. 注册并登录腾讯云控制台。
  2. 开通 Pages 服务,创建 Pages 项目并克隆到本地。

二、安装 CLI

代码语言:bash
复制
npm install -g edgeone
edgeone -v    # 查看版本
edgeone -h    # 查看帮助

三、登录

代码语言:bash
复制
edgeone login
edgeone whoami

支持 Global(国际站)China(中国站)

四、初始化项目

代码语言:bash
复制
edgeone pages init

此命令会生成 edge-functionsnode-functions 文件夹,以及示例函数。

五、编写项目代码

代码语言:javascript
复制
// 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 },
    });
}
代码语言:javascript
复制
// 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);
    }
}

六、关联项目

  • 可同步控制台设置的环境变量,也能直接创建新项目。
代码语言:bash
复制
edgeone pages link

七、本地开发调试

代码语言:bash
复制
edgeone pages dev
  • 默认运行在 http://localhost:8088/
  • 本地开发调试的时候需要执行 edgeone pages env 拉取配置文件到本地。(CLI会自动在项目根目录生成 .env 文件)
「Pages 控制台环境变量配置」
「Pages 控制台环境变量配置」

八、部署上线

  • 推送到 Git 远端(自动触发构建)
  • 或手动部署:
代码语言:bash
复制
# 生产环境部署
edgeone pages deploy

# 预览环境部署
edgeone pages deploy -e preview

构建成功后在 构建部署 中找到对应的部署记录,可以看到我们的 Node Functions 已经生效了。

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

九、配置实时日志推送

  • 此处以 站点加速日志 为例,配置需要推送实时日志的域名,点击下一步
  • 勾选所有字段内容,然后点击下一步
  • 接口地址 中填入前面配置的 自定义域名 + 函数路径 /accelerateLogs,点击推送即可部署对应的站点加速日志推送。
  • 同时在日志分析中可以看到对应的函数运行日志(因为是第一次部署/请求,即为冷启动 Coldstart)

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


总结

通过 EdgeOne Pages + Node Functions + PostgreSQL,我们实现了:

  • 实时日志采集与入库
  • 低运维、全栈一体化开发体验
  • 本地调试 & CI/CD 自动化部署

这种架构充分体现了 “无服务器 + 边缘计算” 的优势,轻量、灵活、可扩展,适合现代应用场景。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是 Node Functions?
  • 集成 Node Functions 的关键优势
  • EdgeOne Pages 简介
  • 实践场景:实时日志入库
    • 主要流程
    • 一、准备工作
    • 二、安装 CLI
    • 三、登录
    • 四、初始化项目
    • 五、编写项目代码
    • 六、关联项目
    • 七、本地开发调试
    • 八、部署上线
    • 九、配置实时日志推送
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档