# 腾讯云开发 CloudBase - AI 原生后端一体化平台 > CloudBase 是面向 AI Coding 的 AI 原生后端一体化平台,内置数据库、存储、鉴权、云函数与云托管等服务,让你专注产品与业务逻辑,并提供 CloudBase MCP、CloudBase CLI、CloudBase Skills 等 AI 友好的开发工具。同时支持快速构建完整的小程序/小游戏、H5、Web、移动 App 等应用。 This file contains all documentation content in a single document following the llmtxt.org standard. # database Documentation > 云开发数据库服务,提供NoSQL文档数据库,支持CRUD操作、聚合查询、实时数据库、事务处理等功能 # 文档型数据库/未命名文档 > 当前文档链接: https://docs.cloudbase.net/database/introduce --- sidebar_label: 概述 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import AIDevelopmentPrompt from '../ai/cloudbase-ai-toolkit/components/AIDevelopmentPrompt'; # 文档型数据库 **CloudBase 文档型数据库** 支持灵活的 JSON 文档存储,无需预定义数据结构、支持任意结构的 JSON 数据、支持多文档事务操作,并提供 SDK 进行数据操作。 :::tip 其他数据库类型 CloudBase 还提供 [MySQL 数据库](../database/configuration/db/tdsql/initialization) 和 [PostgreSQL 数据库](../database/configuration/db/postgresql/initialization),可根据业务需求选择合适的数据库类型。 ::: ## 如何创建 前往 [云开发平台/文档型数据库](https://tcb.cloud.tencent.com/dev?#/db/doc/collection/advantages),点击「新建集合」按钮 ![新建集合页面](https://qcloudimg.tencent-cloud.cn/raw/a58087fb59ff443aad1a3024459e6193.png) 如果您是小程序开发者,可以前往**微信开发者工具 IDE 端**,选择云开发 -> 数据库 -> 新建集合 ![微信开发者工具新建集合页面](https://qcloudimg.tencent-cloud.cn/raw/12de69477da705db90aa2d7f8eaefc0e.png) ## 数据模型 对于期望结构化数据管理、数据质量保障、团队协作开发场景,云开发提供了 **数据模型** 功能,支持可视化定义数据结构、字段类型和关系,并提供自动校验、关联管理、内置 CMS 等能力,帮助开发者快速构建业务数据库。 具体数据模型功能请参考 [数据模型](/model/introduce)。 ## 立即开始 * [数据库增删改查](/database/doc-crud) * [数据权限管理](/database/data-permission) * [索引管理](/database/data-index) * [事务操作](/database/transaction) * [实时推送](/database/realtime) --- # 文档型数据库/数据操作/字段类型 > 当前文档链接: https://docs.cloudbase.net/database/data-type CloudBase 文档型数据库支持多种数据类型,满足不同场景的数据存储需求。 ## 基础数据类型 | 数据类型 | 说明 | 示例 | 适用场景 | | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------------------ | -------------------------- | | String | 字符串类型,用于存储文本数据 | `"Hello World"` | 用户名、描述信息、文本内容 | | Number | 数字类型,包括整数和浮点数 | `123`、`3.14` | 年龄、价格、计数器 | | Object | 对象类型,用于存储键值对结构的数据 | `{ "name": "张三", "age": 25 }` | 用户信息、配置数据 | | Array | 数组类型,用于存储有序的数据集合 | `[1, 2, 3]`、`["a", "b", "c"]` | 标签列表、商品分类 | | Bool | 布尔类型,表示真或假 | `true`、`false` | 开关状态、权限标识 | | Date | 时间类型,精确到毫秒 | `new Date()` | 创建时间、更新时间 | | [GeoPoint](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/reference-sdk-api/database/Geo.html) | 地理位置点,用经纬度标记位置 | `{ "latitude": 39.9, "longitude": 116.4 }` | 位置信息、地图标记 | | Null | 空值类型,表示没有值 | `null` | 可选字段的默认值 | ## 特殊数据类型详解 ### Date 时间类型 Date 类型用于存储时间信息,精确到毫秒级别。支持客户端时间和服务端时间两种创建方式。 #### 客户端时间 使用 JavaScript 内置 Date 对象创建: ```javascript // 创建当前时间 const now = new Date(); // 创建指定时间 const specificDate = new Date('2023-12-25 10:30:00'); // 插入数据 db.collection('articles').add({ data: { title: '文章标题', createTime: now, publishTime: specificDate } }); ``` #### 服务端时间 使用 [`serverDate`](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/reference-sdk-api/database/Database.serverDate.html) 对象创建服务端当前时间标记: ```javascript // 使用服务端时间 db.collection('articles').add({ data: { title: '文章标题', createTime: db.serverDate() } }); ``` ### GeoPoint 地理位置类型 `GeoPoint` 类型用于表示地理位置点,通过经纬度坐标唯一标记一个地理位置。 各SDK实现有所差异,下方分别展示对应示例 #### 创建地理位置 > [小程序 GeoPoint 相关文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/reference-sdk-api/database/Geo.html) ```javascript const db = wx.cloud.database(); // 创建地理位置点 const location = db.Geo.Point(116.4, 39.9); // 经度, 纬度 // 插入带有地理位置的数据 db.collection('stores').add({ data: { name: '北京店', location: location, address: '北京市朝阳区' } }).then(res => { console.log('添加成功', res); }).catch(err => { console.error('添加失败', err); }); ``` ```javascript import cloudbase from '@cloudbase/js-sdk'; const app = cloudbase.init({ env: 'your-env-id' }); const db = app.database(); // 创建地理位置点 const location = new db.Geo.Point(116.4, 39.9); // 经度, 纬度 // 插入带有地理位置的数据 db.collection('stores').add({ name: '北京店', location: location, address: '北京市朝阳区' }).then(res => { console.log('添加成功', res); }).catch(err => { console.error('添加失败', err); }); ``` #### 地理位置查询 > [小程序GEO查询文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/reference-sdk-api/database/command/Command.geoNear.html) ```javascript const _ = db.command db.collection('restaurants').where({ location: _.geoNear({ geometry: db.Geo.Point(116.4, 39.9), minDistance: 1000, maxDistance: 5000, }) }).get() ``` ```javascript const _ = db.command db.collection('restaurants').where({ location: _.geoNear({ geometry: new db.Geo.Point(116.4, 39.9), minDistance: 1000, maxDistance: 5000, }) }).get() ``` > ⚠️ 注意:对地理位置字段进行查询时,请建立地理位置索引,否则查询将失败 地理位置索引 --- # 文档型数据库/数据操作/增删改查 > 当前文档链接: https://docs.cloudbase.net/database/doc-crud 云开发提供了多种 SDK 供开发者进行操作文档型数据库,包括小程序 SDK、JS SDK、Node SDK 等 | SDK 类型 | 适用平台 | | ------------------------------------------------------------------------------------------------------------- | ------------ | | [小程序 SDK](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/guide/database/add.html) | 微信小程序 | | [JS SDK](/api-reference/webv3/database/fetch) | Web 浏览器 | | [Node SDK](/api-reference/server/node-sdk/database/fetch) | Node.js 环境 | --- # 文档型数据库/数据操作/事务操作 > 当前文档链接: https://docs.cloudbase.net/database/transaction 事务操作确保多个数据库操作要么全部成功,要么全部失败,保证数据的一致性。云开发支持数据库事务,并保证事务的 ACID 特性。 :::tip 注意 目前数据库事务只支持在服务端运行,只有 `node-sdk` 支持事务。 ::: ### 使用场景 在大多数场景中,单文档完全可以满足需求。但在一些场景中,使用数据库事务的优势更明显: - **从传统关系型数据库迁移到云开发**:数据模型平滑迁移,业务代码改造成本低 - **涉及多个文档/多个集合的业务流程**:保证一系列读写操作完全成功或者完全失败,防止出现中间态 ### 支持的方法 #### 事务流程方法 | API | 说明 | |-----|------| | **startTransaction** | 发起事务 | | **commit** | 提交事务 | | **rollback** | 回滚事务 | | **runTransaction** | 自动提交事务 | #### 事务中的数据操作 | API | 说明 | |-----|------| | **get** | 查询文档 | | **add** | 插入文档 | | **delete** | 删除文档 | | **update** | 更新文档 | | **set** | 更新文档,文档不存在时,会自动创建 | ### 基础事务 #### 参数说明 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | **updateFunction** | `function` | ✅ 是 | 事务更新函数,接收 transaction 参数 | #### 代码示例 ```javascript const cloudbase = require('@cloudbase/node-sdk') // 初始化数据库 const app = cloudbase.init({ env: 'your-env-id' }) const db = app.database() // 基础事务操作 const result = await db.runTransaction(async transaction => { // 在事务中进行多个操作 const todoDoc = await transaction.collection('todos').doc('todo-id').get() if (!todoDoc.data) { throw new Error('待办事项不存在') } // 更新待办事项状态 await transaction.collection('todos').doc('todo-id').update({ data: { completed: true, completedAt: new Date() } }) // 更新用户统计 await transaction.collection('users').doc('user-id').update({ data: { completedTodos: db.command.inc(1) } }) return { success: true } }) console.log('事务执行结果:', result) ``` #### 返回结果 ```javascript { result: { success: true }, // updateFunction 的返回值 errMsg: "runTransaction:ok" } ``` ### 复杂事务示例 #### 清空购物车场景 以"清空购物车"的需求为例,展示数据库事务在复杂业务场景中的使用: ```javascript // Node.js 环境 const cloudbase = require('@cloudbase/node-sdk') // 初始化数据库 const app = cloudbase.init({ env: 'your-env-id' }) const db = app.database() exports.main = async (event, context) => { const userId = 'user1' const transaction = await db.startTransaction() try { const usersCollection = transaction.collection('users') const goodsCollection = transaction.collection('goods') // 1. 获取用户信息 const user = await usersCollection.doc(userId).get() const { cart, balance } = user.data // 2. 获取购物车数据和对应的商品信息 const goods = [] for (const { id } of cart) { const good = await goodsCollection.doc(id).get() goods.push(good.data) } let totalPrice = 0 for (let i = 0; i < cart.length; ++i) { // 3. 计算购物车中的商品总价 totalPrice += cart[i].num * goods[i].price // 4. 更新商品库存 await goodsCollection.doc(goods[i]._id).set({ inventory: goods[i].inventory - cart[i].num }) } // 5. 更新用户账户余额和清空购物车 await usersCollection.doc(userId).set({ balance: balance - totalPrice, cart: [] }) await transaction.commit() return { success: true, totalPrice } } catch (error) { await transaction.rollback() throw error } } ``` ### 事务原理 #### 快照隔离 - 在调用 `db.startTransaction()` 开始事务后,"快照"是在第一次读之后才会生成 - 在没有调用 `transaction.commit()` 提交事务前,所有的读写操作都是在"快照"上进行 - 在成功提交事务后,"快照"上的数据才会落盘,相关文档数据完成更新 #### 锁与写冲突 - 当事务修改文档时,会锁定相关文档,使其不受其他更改的影响,直到事务结束 - 如果一个事务无法获取到试图修改的文档的锁,事务会终止,并出现写冲突 - 读取文档的操作不需要与文档修改相同的锁 ### 错误处理 推荐在进行事务操作时,使用 `try-catch` 来捕获异常: ```javascript try { const transaction = await db.startTransaction() // 涉及文档更改的事务操作 await transaction.collection('orders').add({ data: { /* 订单数据 */ } }) // 如果发生错误,抛出异常触发回滚 if (someCondition) { throw new Error('业务逻辑错误') } await transaction.collection('products').doc('product-id').update({ data: { stock: db.command.inc(-1) } }) await transaction.commit() } catch (error) { console.error('事务失败,已自动回滚:', error) // 发生写冲突时,进行异常处理 } ``` ### 事务限制 #### 操作限制 | 限制项 | 说明 | 影响 | |--------|------|------| | **操作数量** | 单个事务最多 100 个操作 | 需要合理规划事务范围 | | **执行时间** | 事务执行时间不能超过 30 秒 | 避免复杂计算和外部调用 | | **文档操作** | 仅支持 `doc` 方法,不支持 `where` 方法 | 当前事务仅支持单文档操作 | | **DDL 操作** | 避免在 DDL 操作期间进行事务 | 可能导致事务无法获得锁 | #### 注意事项 ```javascript // ❌ 错误示例:在事务中进行外部调用 await db.runTransaction(async transaction => { // 不要在事务中调用云函数或外部 API const result = await wx.cloud.callFunction({ name: 'someFunction' }) // 事务操作... }) // ✅ 正确示例:先获取数据,再执行事务 const externalData = await wx.cloud.callFunction({ name: 'someFunction' }) await db.runTransaction(async transaction => { // 使用预先获取的数据 await transaction.collection('data').add({ data: externalData.result }) }) ``` --- # 文档型数据库/数据操作/聚合搜索 > 当前文档链接: https://docs.cloudbase.net/database/aggregate 聚合主要用来处理数据(统计平均值,求和等)并返回计算后的数据结果。 ## 流水线/管道 聚合是一个流水线式的批处理作业,初始文档经过多个阶段的流水线处理后,得到转换后的聚合结果。 假设已有一个集合 **books**,其中包含以下格式记录: ```json [ { "_id": "xxx", "category": "Novel", "name": "The Catcher in the Rye", "onSale": true, "sales": 80 } ] ``` 聚合示例如下: ```javascript // 云函数端示例 const cloudbase = require("@cloudbase/node-sdk"); const app = cloudbase.init(); const db = app.database(); const $ = db.command.aggregate; exports.main = async (event, context) => { const res = await db .collection("books") .aggregate() .match({ onSale: true, // 是否正在出售 }) .group({ // 按 category 字段分组 _id: "$category", // 让输出的每组记录有一个 avgSales 字段,其值是组内所有记录的 sales 字段的平均值 avgSales: $.avg("$sales"), }) .end(); return { res, }; }; ``` 第一阶段:**match** 阶段过滤出了集合中的文档数据(`onSale:true` 表示找出正在出售的书籍)并传给下一个阶段。 第二阶段:**group** 阶段基于 `category` 字段进行分组,并统计出每组中所有记录的 `sales` 字段平均值。 ## API 及操作符 参考 Node SDK API 文档,一览所有的聚合阶段 [API](/api-reference/server/node-sdk/database/aggregate/) 及 [操作符](/api-reference/server/node-sdk/database/aggregate/aggregateCommand)。 ## 优化执行 ### 利用索引 **[match](/api-reference/server/node-sdk/database/aggregate/stages/match)** 和 **[sort](/api-reference/server/node-sdk/database/aggregate/stages/sort.md)** 如果是在流水线的开头的话是可以利用索引的。**[geoNear](/api-reference/server/node-sdk/database/aggregate/stages/geoNear.md)** 也可以利用地理位置索引,但要注意的是,**[geoNear](/api-reference/server/node-sdk/database/aggregate/stages/geoNear.md)** 必须是流水线的第一个阶段。 ### 尽早缩小数据集 在不需要集合的全集情况下,应该尽早的通过 **[match](/api-reference/server/node-sdk/database/aggregate/stages/match)**、**[limit](/api-reference/server/node-sdk/database/aggregate/stages/limit.md)** 和 **[skip](/api-reference/server/node-sdk/database/aggregate/stages/skip.md)** 缩小要处理的记录数量。 ## 注意事项 除了 **match** 阶段,在各个聚合阶段中传入的对象中,可使用的操作符都是聚合操作符,需要特别注意的是,**match** 进行的是查询匹配,因此语法同普通查询 **[where](/api-reference/server/node-sdk/database#where)** 的语法,用的是普通查询操作符。 ## FAQ ### Sort exceeded memory limit of 104857600 bytes ``` {"Error":{"Code":"FailedOperation","Message":"(Location16820) Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting. Aborting operation. Pass allowDiskUse:true to opt in."},"RequestId":"1728459320973_0.7685102267589137_33591173-19270342dd4_15"} ``` mongo sort 排序内存溢出问题。 #### 解决方案 1. 合理使用 project,使用更小的数据集进行排序。 2. 合理使用 sort,减少排序字段的数量。 3. 合理使用 match, 尽量在 match 后,或者最后进行 sort。 4. 合理使用索引,使用索引进行排序(如果可能的话)。 --- # 文档型数据库/权限管理/基础权限 > 当前文档链接: https://docs.cloudbase.net/database/data-permission CloudBase 提供了多层次的数据权限管理机制,确保数据安全的同时满足不同业务场景的权限控制需求。 数据库进行读写时会以 `_openid` 字段作为数据归属判定依据 ## 配置方式 在 [云开发平台/文档型数据库/集合管理](https://tcb.cloud.tencent.com/dev?#/db/doc/collection/) 页面,为每个集合设置对应的权限 基础权限控制提供四种预设权限类型,根据用户身份和数据特性选择: | 权限类型 | 适用场景 | 使用建议 | | ------------------------------ | ---------------------- | ---------------------------- | | **读取全部数据,修改本人数据** | 公开内容,如文章、商品 | 适合内容展示类应用 | | **读取和修改本人数据** | 私人数据,如用户资料 | 适合个人信息管理 | | **读取全部数据,不可修改数据** | 配置数据,如系统设置 | 适合只读配置和参考数据 | | **无权限** | 敏感数据,如财务信息 | 适合需要服务端处理的敏感数据 | ![数据库集合-基础权限配置界面](https://qcloudimg.tencent-cloud.cn/raw/fc88faa1009311dac9d582d79d081121.png) ## 安全规则权限 安全规则权限是 CloudBase 数据库提供的**文档级权限控制**能力,相比基础权限控制具有更高的灵活性和精确度。 详情请参考 [数据库安全规则详解](/database/security-rules) --- # 文档型数据库/权限管理/安全规则 > 当前文档链接: https://docs.cloudbase.net/database/security-rules **安全规则** 是 CloudBase 数据库的 **文档级权限控制** 功能,通过自定义表达式精确控制每条数据的读写权限。 ## 配置入口 在 [云开发平台/文档型数据库/集合管理](https://tcb.cloud.tencent.com/dev?#/db/doc/collection/) 选择「权限管理」,点击「切换到安全规则」。 ## 基本语法 ```json { "read": "表达式", // 读取权限控制表达式 "write": "表达式" // 写入权限控制表达式 } ``` > ⚠️ 注意:前端查询条件必须是安全规则的子集,否则访问被拒绝。当安全规则使用 `doc.字段名` 时,查询条件必须包含对应字段。 ### 常用模板 ```json { "read": "doc._openid == auth.openid", "write": "doc._openid == auth.openid" } ``` ```js // 查询当前用户创建的文档 db.collection('articles').where({ _openid: '{openid}' }).get() ``` ```json { "read": true, "write": "doc._openid == auth.openid" } ``` ```js // 读取所有文档 db.collection('articles').get() // 更新自己创建的文档 db.collection('articles').where({ _openid: '{openid}' }).update({ title: '更新后的标题' }) ``` ```json { "read": "doc.published == true || doc.author == auth.openid", "write": "doc.author == auth.openid" } ``` ```js // 查询已发布的文档 db.collection('articles').where({ published: true }).get() // 查询自己的所有文档 db.collection('articles').where({ author: '{openid}' }).get() ``` ```json { "read": "doc.userId == auth.openid || doc.userId == auth.uid", "write": "doc.userId == auth.openid || doc.userId == auth.uid" } ``` > 💡 注意:小程序端使用 `auth.openid` ,Web端使用 `auth.uid` 。查询时使用 `{openid}` 变量会自动适配。 ### \{openid\} 变量 查询条件中可使用字符串常量 `{openid}` ,系统会自动替换为对应的用户身份标识(小程序端为 `openid` ,Web端为 `uid` )。 ```js // 使用 {openid} 变量查询当前用户数据 db.collection('articles').where({ author: '{openid}' }).get() ``` ## 表达式语法 ### 内置变量 | 变量名 | 说明 | 示例 | | ----------- | ------------------------ | -------------------------- | | **auth** | 用户登录信息 | `auth.openid`、`auth.uid` | | **doc** | 文档数据或查询条件字段 | `doc.userId`、`doc.status` | | **now** | 当前时间戳 | `now > doc.expireTime` | ### 运算符 | 运算符 | 说明 | 示例 | | -------- | ------ | -------------------------------------------- | | **==** | 等于 | `auth.openid == doc.userId` | | **!=** | 不等于 | `doc.status != 'deleted'` | | **in** | 包含于 | `auth.openid in doc.editors` | | **&&** | 逻辑与 | `auth.openid == doc.userId && doc.published` | | **\|\|** | 逻辑或 | `auth.openid == doc.userId \|\| doc.public` | ### 操作类型 | 操作 | 说明 | 默认值 | | ---------- | -------- | ------------ | | **read** | 读取文档 | `false` | | **write** | 写入文档 | `false` | | **create** | 创建文档 | 继承 `write` | | **update** | 更新文档 | 继承 `write` | | **delete** | 删除文档 | 继承 `write` | ## get 函数 用于跨文档权限验证,语法:`get('database.集合名.文档ID')` ```json { "read": "auth.openid in get(`database.room.${doc.roomId}`).members" } ``` :::tip 重要提示 使用 `get` 函数时,查询条件必须包含被引用的字段,否则会返回 `DATABASE_PERMISSION_DENIED` 错误。 ::: **正确示例:** ```js // 查询条件包含 roomId 字段 db.collection('messages').where({ roomId: 'room123' }).get() ``` **错误示例:** ```js // 缺少 roomId 字段,会返回权限拒绝错误 db.collection('messages').where({ _id: 'msg123' }).get() ``` **限制条件:** - 查询条件必须包含 `get` 函数中引用的 `doc` 字段 - `in` 操作符中的数组长度必须为1 - 一个表达式最多3个 `get` 函数 - 嵌套深度最多为2层 - 每个 `get` 函数会产生一次数据库读取操作 ## 应用场景 ### 文章发布系统 ```json { "read": "doc.published == true || doc.author == auth.openid || doc.author == auth.uid", "update": "doc.author == auth.openid || doc.author == auth.uid", "delete": "(doc.author == auth.openid || doc.author == auth.uid) && doc.published == false" } ``` ```js // 查询已发布的文章 db.collection('articles').where({ published: true }).get() // 查询自己的文章 db.collection('articles').where({ author: '{openid}' }).get() // 更新自己的文章 db.collection('articles').where({ author: '{openid}', _id: 'article123' }).update({ title: '更新后的标题' }) ``` ### 协作文档系统 ```json { "read": "auth.openid in doc.readers || auth.uid in doc.readers || auth.openid in doc.editors || auth.uid in doc.editors || doc.owner == auth.openid || doc.owner == auth.uid", "write": "auth.openid in doc.editors || auth.uid in doc.editors || doc.owner == auth.openid || doc.owner == auth.uid", "delete": "doc.owner == auth.openid || doc.owner == auth.uid" } ``` ```js // 查询有权限读取的文档 db.collection('documents').where(_.or([ { readers: _.in(['{openid}']) }, { editors: _.in(['{openid}']) }, { owner: '{openid}' } ])).get() ``` ### 群聊系统 **消息集合:** ```json { "read": "auth.openid in get(`database.room.${doc.roomId}`).members", "create": "auth.openid in get(`database.room.${doc.roomId}`).members", "update": "auth.openid == doc.sender" } ``` ```js // 查询房间消息(必须包含 roomId) db.collection('messages').where({ roomId: 'room123' }).get() ``` **房间集合:** ```json { "read": "auth.openid in doc.members", "write": "doc.owner == auth.openid" } ``` ```js // 查询加入的房间 db.collection('rooms').where({ members: _.in(['{openid}']) }).get() ``` --- # 文档型数据库/高级功能/索引管理 > 当前文档链接: https://docs.cloudbase.net/database/data-index 索引是提升数据库查询性能的关键技术。通过为常用查询字段建立索引,可以显著提升查询速度和用户体验。 ## 如何创建索引 1. 访问 [云开发平台/文档型数据库](https://tcb.cloud.tencent.com/dev#/db/doc/model) 2. **进入索引管理**:选择目标集合,点击索引管理标签页 3. **管理索引**:新建、删除或修改索引配置 ![索引管理界面](https://qcloudimg.tencent-cloud.cn/raw/e87380898272a128b25ef55a99e494b1.png) ## 索引类型详解 ### 单字段索引 **适用场景:** 针对单个字段的查询和排序操作 **特点说明:** - 支持嵌套字段索引,使用"点表示法"访问 - 可指定升序或降序排序 - 适合简单的单条件查询 **示例配置:** ```json // 原始数据结构 { "_id": "product_001", "name": "iPhone 15", "price": 5999, "style": { "color": "深空黑", "storage": "128GB" } } ``` **索引配置示例:** | 字段路径 | 索引类型 | 排序方式 | 适用查询 | | ------------- | -------- | -------- | -------------- | | `name` | 单字段 | 升序 | 按商品名称查询 | | `price` | 单字段 | 降序 | 按价格排序 | | `style.color` | 单字段 | 升序 | 按颜色筛选 | > 💡 注意:嵌套字段使用点表示法,如 `style.color` 表示访问 style 对象中的 color 字段。 ### 组合索引 **适用场景:** 多字段联合查询和复杂排序操作 **核心概念:** 一个索引包含多个字段,支持前缀匹配查询 **示例数据:** ```json { "_id": "student_001", "name": "张三", "age": 20, "score": 85, "class": "计算机科学" } ``` **索引前缀规则:** 假设创建组合索引:`name + age + score` | 索引前缀 | 能命中的查询组合 | 查询示例 | | ------------------ | ---------------- | --------------------------------------------------------------------- | | `name` | 单独查询 name | `db.collection('students').where({name: '张三'})` | | `name, age` | 查询 name + age | `db.collection('students').where({name: '张三', age: 20})` | | `name, age, score` | 查询全部字段 | `db.collection('students').where({name: '张三', age: 20, score: 85})` | :::tip 前缀匹配原理 组合索引 `(name, age, score)` 的前缀包含: - ✅ `name` - 可以命中索引 - ✅ `name, age` - 可以命中索引 - ✅ `name, age, score` - 可以命中索引 - ❌ `age` - 无法命中索引(不是前缀) - ❌ `score` - 无法命中索引(不是前缀) - ❌ `age, score` - 无法命中索引(不是前缀) ::: **组合索引的重要特性:** #### 字段顺序的重要性 索引字段的顺序直接影响查询性能: | 索引定义 | 能命中的查询 | 无法命中的查询 | | ------------- | ------------------------------------- | -------------------------------------------- | | `(name, age)` | ✅ `name` 查询
✅ `name + age` 查询 | ❌ 单独 `age` 查询
❌ `age + score` 查询 | | `(age, name)` | ✅ `age` 查询
✅ `age + name` 查询 | ❌ 单独 `name` 查询
❌ `name + score` 查询 | #### 排序方向的影响 排序查询时,索引的排序方向会影响是否能命中索引: **索引配置:** `age: 升序, score: 降序` | 查询排序方式 | 是否命中索引 | 说明 | | ------------------------ | ------------ | ------------------ | | `age: 升序, score: 降序` | ✅ 命中 | 与索引方向完全一致 | | `age: 降序, score: 升序` | ✅ 命中 | 索引可反向使用 | | `age: 升序, score: 升序` | ❌ 未命中 | 排序方向不一致 | | `age: 降序, score: 降序` | ❌ 未命中 | 排序方向不一致 | | `score: 任意, age: 任意` | ❌ 未命中 | 不符合前缀规则 | **索引配置:** `age: 升序, score: 升序` | 查询排序方式 | 是否命中索引 | 说明 | | ------------------------ | ------------ | ------------------ | | `age: 升序, score: 升序` | ✅ 命中 | 与索引方向完全一致 | | `age: 降序, score: 降序` | ✅ 命中 | 索引可反向使用 | | `age: 升序, score: 降序` | ❌ 未命中 | 排序方向不一致 | | `age: 降序, score: 升序` | ❌ 未命中 | 排序方向不一致 | ### 地理位置索引 **适用场景:** 基于地理位置的查询,如附近商家、距离计算等。 **创建步骤:** 1. 在 [云开发平台/数据库](https://tcb.cloud.tencent.com/dev#/db/doc/model) 选择集合 2. 进入索引管理页面 3. 选择地理位置字段(如 `location`) 4. 设置索引类型为"地理位置索引" **查询示例:** 详细文档请参考[小程序 GEO 查询文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/reference-sdk-api/database/command/Command.geoNear.html) ```javascript const _ = db.command db.collection('restaurants').where({ location: _.geoNear({ geometry: db.Geo.Point(116.4, 39.9), minDistance: 1000, maxDistance: 5000, }) }).get() ``` ```javascript const _ = db.command db.collection('restaurants').where({ location: _.geoNear({ geometry: new db.Geo.Point(116.4, 39.9), minDistance: 1000, maxDistance: 5000, }) }).get() ``` 地理位置索引 ## 索引使用注意事项 ### 索引数量限制 :::warning 重要提示 建议单个集合的索引数量不超过 **20 个**。索引过多不仅不会提升性能,反而会带来严重的负面影响。 ::: **索引过多的负面影响:** 1. **写入性能显著下降** - 每次插入或更新数据时,需要同步维护所有索引,导致写入操作耗时增加 2. **存储空间和内存压力增大** - 每个索引都会占用额外的存储空间和内存资源 3. **查询性能反而降低** - 当工作集超过 RAM 后,会进行频繁的磁盘交换,查询性能大幅下降 **索引优化建议:** - **定期审查索引使用情况** - 在 [云开发平台/文档型数据库](https://tcb.cloud.tencent.com/dev#/db/doc/model) 的索引管理页面查看各索引的命中频率 - **清理低频索引** - 对于很少使用或从未使用的索引,及时删除以释放资源 - **优先保留高频索引** - 保留那些能显著提升查询性能的核心索引 - **合理使用组合索引** - 一个精心设计的组合索引可以替代多个单字段索引 ### 唯一性限制 创建索引时,索引属性选择**唯一**,即可添加唯一性限制。此限制会要求集合中**索引字段对应的值不能重复**。 例如,某个集合内建立了索引字段 `foo` ,且属性为"唯一",那么在这个集合内,要求不能存在 `foo` 字段相同的文档。 > ⚠️ 注意:假如记录中不存在某个字段,则对索引字段来说其值默认为 null。如果索引有唯一性限制,则不允许存在两个或以上的该字段为空/不存在该字段的记录。 ### 大小限制 - 索引的字段大小限制不能超过 1024 字节 - 添加索引时,如果集合中已有文档索引字段超过 1024 字节,添加索引时将报错 - 已设置索引的字段,如果插入一个文档,文档中该字段超过 1024 字节将会报错 > 💡 注意:每个英文字母(不分大小写)占一字节的空间,每个中文汉字占两字节的空间。 ### 正则表达式查询 > ⚠️ 注意:正则表达式查询无法使用索引提升性能,建议在必要时使用全文索引替代。 --- # 文档型数据库/高级功能/实时推送 > 当前文档链接: https://docs.cloudbase.net/database/realtime 云开发数据库支持监听集合中符合查询条件的数据的更新事件。 ## 建立监听 使用 `watch()` 方法即可建立监听,并且返回 `watcher` 对象,用于关闭监听。 符合条件的文档有任何变化,都会触发 `onChange` 回调。 ```javascript // 1. 获取数据库引用 const db = wx.cloud.database(); const watcher = db .collection("todos") .where({ // query... }) .watch({ onChange: function(snapshot) { console.log("snapshot", snapshot); }, onError: function(err) { console.error("the watch closed because of error", err); } }); ``` ```javascript const cloudbase = require("@cloudbase/js-sdk"); const app = cloudbase.init({ env: "xxxx" }); // 1. 获取数据库引用 var db = app.database(); const watcher = db .collection("todos") .where({ // query... }) .watch({ onChange: function(snapshot) { console.log("snapshot", snapshot); }, onError: function(err) { console.error("the watch closed because of error", err); } }); ``` ## 关闭监听 调用 `watcher.close()` 即可关闭监听。 --- # 文档型数据库/高级功能/导入/导出 > 当前文档链接: https://docs.cloudbase.net/database/manage 「文档型数据库」提供完善的数据导入/导出功能,帮助您快速完成数据迁移、备份和分析等任务。本文档将详细介绍如何使用集合管理功能进行数据的导入和导出操作。 ## 数据导入 「数据导入」功能支持批量导入数据,帮助您快速迁移现有数据或初始化测试数据。 ### 操作步骤 1. **进入集合管理**:访问 [云开发平台/文档型数据库/集合管理](https://tcb.cloud.tencent.com/dev#/data-model/database) ,选择目标集合 2. **开始导入**:点击「导入」按钮 3. **选择文件**:上传准备好的数据文件(支持 JSON 和 CSV 格式) 4. **配置导入参数**: - 选择文件格式(JSON 或 CSV) - 选择冲突处理模式(Insert 或 Upsert) 5. **执行导入**:确认配置后,点击「导入」开始数据导入 ![集合管理-数据导入操作界面](https://qcloudimg.tencent-cloud.cn/raw/410a1f6e306921f807c06def14ca8795.png) ### 冲突处理模式 导入数据时,您需要选择合适的冲突处理模式来决定如何处理已存在的记录: | 模式 | 处理方式 | 适用场景 | 使用注意 | |------|----------|----------|----------| | **Insert** | 总是插入新记录 | • 全新数据导入
• 数据初始化 | 导入文件中不能包含与数据库中相同的 `_id` | | **Upsert** | 存在则更新,不存在则插入 | • 数据更新
• 增量导入
• 数据同步 | 基于 `_id` 字段判断记录是否存在 | :::tip 模式选择建议 - **首次导入**:推荐使用 **Insert** 模式,确保数据完整导入,避免意外覆盖 - **数据更新**:推荐使用 **Upsert** 模式,支持增量更新,避免重复数据 - **定期同步**:使用 **Upsert** 模式,实现数据的持续同步更新 ::: ### 支持的文件格式 #### JSON 格式 **编码要求**:UTF-8 编码 **格式说明**:采用 [JSON Lines](http://jsonlines.org/examples/) 格式,每行一个完整的 JSON 对象 **示例文件**: ```json {"_id": "user_001", "name": "张三", "age": 25, "email": "zhang@example.com", "role": "admin"} {"_id": "user_002", "name": "李四", "age": 30, "email": "li@example.com", "role": "editor"} {"_id": "user_003", "name": "王五", "age": 28, "email": "wang@example.com", "role": "viewer"} ``` **格式特点**: - 每行代表一条独立的数据记录 - 支持嵌套对象和数组结构 - 完整保留数据类型信息 #### CSV 格式 **编码要求**:UTF-8 编码 **格式说明**:标准 CSV 格式,第一行为字段名(表头),后续行为数据记录 **示例文件**: ```csv _id,name,age,email,role user_001,张三,25,zhang@example.com,admin user_002,李四,30,li@example.com,editor user_003,王五,28,wang@example.com,viewer ``` **格式特点**: - 结构简单,易于使用 Excel 等工具编辑 - 适合扁平化的表格数据 - 字段名和数据用逗号分隔 ### 格式要求与限制 #### JSON 格式规范 | 规范项 | 具体要求 | 正确示例 | 错误示例 | |--------|----------|----------|----------| | **记录分隔** | 使用换行符 `\n` 分隔每条记录 | 每行一个 JSON 对象 | 多个对象写在同一行 | | **字段命名** | 首尾不能是 `.`,不能包含连续的 `..` | `name`、`user.id`、`data.info` | `.name`、`name.`、`a..b` | | **键名唯一** | 同一对象内不能有重复的键名 | `{"id": 1, "name": "张三"}` | `{"a": 1, "a": 2}` | | **时间格式** | 使用 MongoDB ISODate 格式 | `{"date": {"$date": "2024-01-15T10:30:00.882Z"}}` | `{"date": "2024-01-15"}` | #### 数据完整性要求 | 导入模式 | `_id` 字段要求 | 详细说明 | |---------|---------------|----------| | **Insert 模式** | 不能重复 | • 导入文件内部的 `_id` 不能重复
• 导入文件中的 `_id` 不能与数据库现有记录重复 | | **Upsert 模式** | 允许重复 | • 文件中的 `_id` 与数据库现有记录相同时,会更新该记录
• 文件中的 `_id` 不存在时,会插入新记录 | > ⚠️ **重要提示**: > - 如果不指定 `_id` 字段,系统会自动生成唯一的 `_id` > - 导入前建议先备份数据,避免误操作导致数据丢失 ### 导入结果说明 导入完成后,系统会显示详细的导入统计信息: - **成功导入记录数**:已成功写入数据库的记录数量 - **失败记录数**:导入失败的记录数量 - **失败原因**:具体的错误信息(如格式错误、`_id` 重复等) - **跳过记录数**:在 Insert 模式下因 `_id` 重复而跳过的记录 ## 数据导出 「数据导出」功能支持将集合数据导出为文件,便于数据备份、分析或迁移到其他系统。 ### 操作步骤 1. **进入集合管理**:访问 [云开发平台/文档型数据库/集合管理](https://tcb.cloud.tencent.com/dev#/data-model/database) ,选择要导出的集合 2. **开始导出**:点击「导出」按钮 3. **配置导出参数**: - 选择导出格式(JSON 或 CSV) - 设置字段范围(可选) - 选择保存位置 4. **执行导出**:确认配置后,点击「导出」开始数据导出 5. **下载文件**:导出完成后,下载生成的文件到本地 ### 导出格式配置 #### JSON 格式 **适用场景**: - 数据完整备份 - 跨系统数据迁移 - 保留完整数据结构 **格式特点**: - 完整保留数据类型和结构信息 - 支持嵌套对象和数组 - 导出文件可直接用于导入操作 **字段配置**: - **不指定字段**:导出集合的所有字段和数据(推荐用于备份) - **指定字段**:只导出指定的字段,减小文件体积 **导出示例**: ```json {"_id": "user_001", "name": "张三", "profile": {"age": 25, "city": "北京"}, "tags": ["vip", "active"]} {"_id": "user_002", "name": "李四", "profile": {"age": 30, "city": "上海"}, "tags": ["normal"]} ``` #### CSV 格式 **适用场景**: - 数据分析和统计 - Excel 导入处理 - 生成数据报表 **格式特点**: - 表格结构,易于阅读 - 兼容 Excel、Numbers 等办公软件 - 数据会被扁平化处理 **字段配置**: - **必须指定字段**:CSV 格式要求明确指定需要导出的字段列表 - **嵌套字段访问**:使用点表示法访问嵌套对象,如 `profile.age`、`profile.city` **字段配置示例**: ```text _id, name, age, email _id, name, profile.age, profile.city, createdAt, updatedAt ``` **导出示例**: ```csv _id,name,age,email user_001,张三,25,zhang@example.com user_002,李四,30,li@example.com ``` ### 导出格式对比 | 对比项 | JSON 格式 | CSV 格式 | |--------|----------|----------| | **字段指定** | 可选(默认导出全部) | 必须指定 | | **数据完整性** | 完整保留所有类型和结构 | 扁平化处理,丢失嵌套结构 | | **文件体积** | 相对较大 | 相对较小 | | **可读性** | 适合技术人员 | 适合业务人员 | | **再导入** | 可直接导入 | 需要转换格式 | | **适用工具** | 代码编辑器、数据库工具 | Excel、Numbers、数据分析工具 | ### 使用场景与最佳实践 | 使用场景 | 推荐格式 | 配置建议 | 说明 | |---------|---------|---------|------| | **数据完整备份** | JSON | 不指定字段,导出全部数据 | 保留完整数据结构,便于恢复 | | **跨系统迁移** | JSON | 不指定字段 | 确保数据迁移的完整性 | | **数据分析** | CSV | 指定需要分析的字段 | 便于使用 Excel 等工具处理 | | **生成报表** | CSV | 指定报表所需字段 | 快速生成业务报表 | | **大数据导出** | JSON 或 CSV | 分批导出,每批 1-5 万条 | 避免单次导出数据量过大 | | **嵌套数据导出** | JSON | 不指定字段 | CSV 无法完整保留嵌套结构 | :::tip 导出建议 - **定期备份**:建议每周使用 JSON 格式导出完整数据作为备份 - **大数据集**:数据量超过 10 万条时,建议分批导出 - **嵌套数据**:包含复杂嵌套结构时,优先使用 JSON 格式 - **业务分析**:需要使用 Excel 分析时,使用 CSV 格式并指定关键字段 ::: --- # 文档型数据库/高级功能/数据库回档 > 当前文档链接: https://docs.cloudbase.net/database/backup **文档型数据库** 提供完善的数据备份和回档功能,帮助您保障数据安全,支持在数据丢失或误操作时快速恢复数据。 数据回档功能允许您基于已有备份将数据库恢复到指定时间点的状态。 ### 回档特性 - **新集合生成**:回档后将产生新的集合 - **自定义命名**:支持为回档后的集合自定义重命名 - **灵活选择**:支持选择单个或多个集合进行回档 ### 操作步骤 1. 进入 [云开发平台/文档型数据库/数据库设置](https://tcb.cloud.tencent.com/dev?#/db/doc/setting) 2. 点击「新建回档」按钮 3. 选择回档时间点和目标集合 4. 设置回档后的集合名称 5. 确认执行回档任务 ![数据库回档-新建回档操作界面](https://qcloudimg.tencent-cloud.cn/raw/e467793f26213b9623ba7e5899776b11.png) ### 配置说明 #### 时间点选择 - 根据备份时间范围选择具体时间点 - 系统显示可用的备份时间列表 #### 集合选择 - 支持选择单个或多个集合进行回档 #### 命名规则 | 配置项 | 说明 | 示例 | |--------|------|------| | 默认命名 | 原集合名 + `_bak` 后缀 | `users` → `users_bak` | | 自定义命名 | 手动指定回档后的集合名 | `users_backup_20240115` | > ⚠️ 注意:回档后的集合名不能与现有集合名重复 --- # 文档型数据库/高级功能/概述 > 当前文档链接: https://docs.cloudbase.net/model/introduce ## 什么是数据模型? 数据模型是云开发提供的**声明式数据管理解决方案**,它构建在云开发数据库之上,为开发者提供了一种更高效、更安全的数据操作方式。 简单来说,数据模型就像是为您的数据定制的"智能管家": - **定义数据结构**:通过可视化界面定义数据的字段、类型和关系 - **自动数据校验**:确保数据的准确性和一致性 - **处理关联关系**:自动管理数据之间的复杂关系 - **包含多端 SDK**:一次定义,多端使用(小程序、Web、云函数) - **内置管理界面**:提供开箱即用的数据管理后台 - **AI 智能分析**:利用人工智能挖掘数据价值 > **核心理念**:让开发者专注于业务逻辑,而不是底层数据操作的复杂性。 ## 核心特性一览 | 特性 | 描述 | 价值 | | ------------------ | ---------------------------------------- | -------------------------- | | **智能数据校验** | 自动检查数据类型和格式,防止错误数据入库 | 提高数据质量,减少 Bug | | **关联关系管理** | 自动处理一对一、一对多、多对多关系 | 简化复杂查询,提升开发效率 | | **多端 SDK 支持** | 一次定义,自动生成小程序/Web/云函数 SDK | 统一开发体验,减少重复工作 | | **可视化管理** | 内置 CMS 管理界面,支持非技术人员操作 | 降低运营成本,提升协作效率 | | **低代码应用生成** | 一键生成可定制的管理后台应用 | 快速交付,缩短开发周期 | | **AI 数据分析** | 智能分析数据趋势和模式 | 数据驱动决策,挖掘业务价值 | | **高级查询能力** | 支持复杂条件查询、聚合分析等 | 满足多样化业务需求 | ## 数据模型和数据库的关系 - 数据模型是对数据库的建模,它定义了数据模型的结构、字段、约束和关系。数据模型提供了一种统一的方式来描述和操作数据,简化了数据操作和查询。数据模型能力即为开发中常用的对象关系映射 ORM。 - 数据模型和数据库是关联关系,一个数据模型,对应了数据库中的一个集合(非结构化数据库)或表(结构化数据库)。 - 使用数据模型,不代表被限制为仅能使用数据模型。在使用数据模型的同时,如果需要有更复杂、或数据模型无法很好完成的操作,也可以通过数据库的原生方法,直接操作数据库完成数据读写。 - 相对于传统开发框架中的 ORM,云开发数据模型除 ORM 相关的 SDK 操作数据的能力外,额外整合了数据管理界面、用户及权限体系、内容管理以及进一步的基于数据模型的应用生成、数据分析等更多能力。更多优势点可以查看 "为什么要使用数据模型"。 ## 快速开始 ### 第一步:访问控制台 1. **选择数据库**: - 访问 [云开发平台/文档型数据库/数据模型](https://tcb.cloud.tencent.com/dev?#/db/doc/model/?sourceType=internal_flexdb) - 访问 [云开发平台/MySQL数据库/数据模型](https://tcb.cloud.tencent.com/dev?#/db/mysql/model/?sourceType=internal_mysql) 2. **创建模型**:点击「新建模型」,选择创建方式 ### 第二步:开始使用 新建模型后,您就可以立即使用数据模型提供的各项强大能力了! > **建议先阅读** [快速开始教程](/model/initialization),通过实际案例快速上手。 ## 为什么选择数据模型? 数据模型为开发者带来了**全方位的开发体验提升**: - **提高开发效率**:自动生成 SDK,减少重复代码编写 - **降低维护成本**:统一的数据管理,减少运维工作量 - **增强数据安全**:内置权限控制和数据校验机制 - **强化数据分析**:AI 智能分析,挖掘数据价值 - **简化团队协作**:可视化管理界面,技术与非技术人员都能轻松使用 ### 智能数据校验和类型检查 数据模型提供**自动化的数据校验机制**,在数据入库前进行严格的类型检查和格式验证,确保数据的准确性和一致性,从源头避免数据质量问题。 #### 实际案例演示 假设我们定义了一个文章模型 `post`,包含以下字段: - `title`(字符串类型)- 文章标题 - `body`(字符串类型)- 文章内容 现在我们故意传入错误类型的数据来测试校验机制: ```javascript try { const { data } = await models.post.create({ data: { title: "你好,世界", body: 123456, // ❌ 故意传入数字类型,期望的是字符串类型 }, }); console.log("创建成功:", data); } catch (error) { console.error("校验失败:", error); } ``` #### 校验结果 当我们尝试插入类型错误的数据时,数据模型会立即检测到问题并阻止操作: ```bash Error: WxCloudSDKError: 【错误】数据格式校验失败。根因:[#/body: expected type: String, found: Integer],原因:字段[正文], 标识[body], 类型不匹配:期望类型:字符串。,解决步骤:请按照原因修改数据类型。 errRecord:{"title":"你好,世界","body":123456}【操作】调用 post.create ``` #### 校验优势 - **精确定位**:准确指出哪个字段出现了什么问题 - **友好提示**:提供清晰的错误原因和解决建议 - **数据保护**:防止脏数据进入数据库 - **减少 Bug**:在开发阶段就发现数据类型问题 ### 智能关联关系处理 数据模型能够**自动处理复杂的数据关联关系**,支持一对一、一对多、多对多等各种关系类型。无需手动编写复杂的 JOIN 查询,系统会自动处理数据之间的关联逻辑。 #### 关联查询示例 以文章和评论的关联关系为例,我们可以轻松实现跨模型的数据查询。通过 [`select`](sdk-reference/model#select) 参数,可以精确控制返回的字段和关联数据: ```javascript const { data } = await models.post.list({ // 精确控制返回字段,优化查询性能 select: { _id: true, title: true, updatedAt: true, // 关联查询评论数据 comments: { _id: true, createdAt: true, comment: true, }, }, filter: { where: {}, // 查询条件 }, getCount: true, // 获取总数 }); console.log("查询结果:", data); ``` #### 返回结果 系统会自动处理关联关系,返回结构化的数据: ```json { "records": [ { "_id": "9FSAHWM9VV", "title": "Bonjour le Monde", "updatedAt": 1718096503886, "comments": [ { "_id": "9FSAJF3GLG", "createdAt": 1718096509916, "comment": "这是一条评论" } ] }, { "_id": "9FSAHWM9VU", "title": "你好,世界", "updatedAt": 1718096503886, "comments": [] // 暂无评论 } ], "total": 2 } ``` #### 关联查询优势 - **性能优化**:只返回需要的字段,减少数据传输 - **自动关联**:无需手写复杂的 JOIN 语句 - **多层嵌套**:支持深层次的关联数据查询 - **精确控制**:灵活指定每个关联对象的返回字段 ### 多端 SDK 自动生成 云开发平台提供了**多端对接的 SDK**,支持小程序、Web、云函数等多个平台。一次定义,多端复用,大幅提升开发效率。 #### 智能 Upsert 操作 以 [upsert()](sdk-reference/model#upsert) 方法为例,它能智能判断是创建新记录还是更新现有记录: ```javascript const postData = { title: "Hello World", body: "这是一篇示例文章", _id: "hello-world-post", }; const { data } = await models.post.upsert({ create: postData, // 如果不存在则创建 update: postData, // 如果存在则更新 filter: { where: { _id: { $eq: postData._id } }, }, }); console.log("操作结果:", data); ``` #### 返回结果说明 ```javascript // 创建新记录时 { "count": 0, // 更新的记录数为 0 "id": "hello-world-post" // 新创建记录的 ID } // 更新现有记录时 { "count": 1, // 更新的记录数为 1 "id": "" // 更新操作不返回 ID } ``` #### 多端 SDK 优势 - **统一接口**:所有平台使用相同的 API 调用方式 - **类型安全**:TypeScript 支持,编译时发现错误 - **自动优化**:根据平台特性自动优化性能 - **实时同步**:模型变更后 SDK 自动更新 ### 内置数据管理系统 数据模型提供**开箱即用的内容管理系统(CMS)**,让非技术人员也能轻松进行数据管理和维护,大幅降低运营成本。 #### 核心功能 - **可视化编辑**:直观的表单界面,无需编程知识 - **权限管理**:细粒度的用户权限控制 - **数据统计**:内置数据分析和报表功能 - **高级搜索**:支持多条件筛选和排序 - **响应式设计**:支持桌面和移动端访问 > **了解更多**:查看 [使用数据管理](/toolbox/manage-data) 了解完整功能 ![管理界面](https://cloudcache.tencent-cloud.com/qcloud/ui/static/static_source_business/8cdd0b8d-83b7-496f-a93b-38949ac362d5.png) ### 低代码应用生成器 数据模型支持**一键生成完整的管理后台应用**,采用先进的低代码技术,支持可视化修改和定制开发,让您无需从零编写管理界面。 #### 低代码特性 - **拖拽式设计**:通过拖拽组件快速构建界面 - **模板丰富**:提供多种预设模板和组件 - **深度定制**:支持自定义样式和业务逻辑 - **一键部署**:生成的应用可直接部署使用 - **实时预览**:所见即所得的开发体验 ![低代码应用生成器](https://cloudcache.tencent-cloud.com/qcloud/ui/static/static_source_business/b2b54b47-7b1d-48ec-b294-4e98a27e5d58.png) ### AI 智能数据分析 数据模型集成了**强大的 AI 分析引擎**,能够自动挖掘数据中的模式和趋势,为业务决策提供智能化支持。 #### AI 分析能力 - **趋势预测**:基于历史数据预测未来趋势 - **异常检测**:自动识别数据中的异常模式 - **智能报表**:自动生成可视化分析报告 - **洞察建议**:提供基于数据的业务建议 - **用户画像**:智能分析用户行为特征 > **核心价值**:从海量数据中提取有价值的商业洞察,让数据真正驱动业务增长 ![AI 智能分析界面](https://qcloudimg.tencent-cloud.cn/image/document/22a107ad53ac7ac6d527c6630b46ce7a.png) ### 高级数据库查询支持 数据模型支持**原生 SQL 查询**,兼容 MySQL 等关系型数据库的高级查询功能。当模型 API 无法满足复杂查询需求时,您可以直接使用 SQL 语句进行精确控制。 #### 高级查询能力 - **复杂条件筛选**:支持多表联查、子查询等 - **聚合分析**:GROUP BY、HAVING、聚合函数等 - **性能优化**:索引优化、查询计划分析 - **安全防护**:参数化查询,防止 SQL 注入 #### 实际案例 查询作者联系电话以"1858"开头的记录: ```javascript const result = await models.$runSQL( "SELECT * FROM `article_table` WHERE author_tel LIKE ?;", ["1858%"] // 参数化查询,安全可靠 ); console.log("查询结果:", result); ``` #### 返回结果 ```json { "data": { "total": 1, "executeResultList": [ { "_id": "9JXU7BWFZJ", "title": "示例文章", "author_tel": "18588881111", "createdAt": 1719475245475, "region": "北京市" } ], "backendExecute": "28" }, "requestId": "0d4c98c3-a3ff-4c55-93cc-d0f5c835f82c" } ``` > **深入学习**:查看 [数据库原生查询文档](/model/db-raw-query) 了解更多高级用法 --- ## 总结 云开发数据模型为现代应用开发提供了**全方位的数据管理解决方案**: - **架构优势**:基于云原生架构,稳定可靠 - **开发效率**:自动生成 SDK,减少重复工作 - **数据安全**:内置校验和权限控制机制 - **管理便捷**:可视化管理界面,降低运营成本 - **智能分析**:AI 驱动的数据洞察能力 - **灵活扩展**:支持原生查询和自定义逻辑 **立即开始您的数据模型之旅!** 查看 [快速开始教程](/model/initialization) --- # 文档型数据库/高级功能/连接自有 MongoDB > 当前文档链接: https://docs.cloudbase.net/database/custom-database 您可以将腾讯云 MongoDB 数据库接入云开发平台,复用现有数据库资源,降低数据迁移成本。 接入腾讯云数据库后,您可以: - **无缝集成**:直接管理现有数据库中的数据,无需数据迁移 - **数据模型**:基于现有集合快速生成数据模型,自动化数据管理 - **SDK 支持**:使用云开发 Web SDK / Node SDK / 小程序 SDK 统一访问 - **安全可控**:数据保留在您的数据库中,完全掌控数据安全 :::warning 注意 当前仅支持连接「相同地域腾讯云 MongoDB 数据库」,暂不支持其他云服务商或自建的 MongoDB 数据库。 ::: ## 前置条件 在配置腾讯云 MongoDB 数据库连接前,请确保: | 检查项 | 要求 | | :--------- | :----------------------------------------------- | | 数据库来源 | 必须是腾讯云 MongoDB 数据库实例 | | 网络访问 | 数据库具有公网访问能力或已配置内网访问 | | 用户权限 | 已创建专用数据库用户账号,具备集合操作权限 | | 防火墙配置 | 数据库防火墙已开放相应端口(MongoDB 默认 27017) | | IP 白名单 | 已配置云开发服务 IP 白名单(见文档末尾) | ## 创建连接配置 ### 1. 进入配置页面 访问 [云开发平台/文档型数据库](https://tcb.cloud.tencent.com/dev?#/db/doc/collection/),点击顶部「新增连接」按钮 ![文档型数据库新增页面](https://qcloudimg.tencent-cloud.cn/raw/2ada130a62891a31226f628b0422c69e.png) ### 2. 填写连接信息 | 参数 | 说明 | 必填 | | :------- | :------------------------------------------------- | :--- | | 配置名称 | 自定义名称,用于在平台中识别此连接 | 是 | | 配置标识 | 英文标识符,用于 SDK 中指定连接(如 `my-mongodb`) | 是 | | 数据库名 | 要连接的目标数据库名称 | 是 | | 用户名 | 数据库连接用户名 | 是 | | 密码 | 数据库连接密码 | 是 | | 连接参数 | 额外的 MongoDB 连接参数(如 `authSource=admin`) | 否 | | 超时时间 | 连接和查询的超时时间(单位:秒) | 否 | ### 3. 设置默认实例(可选) 创建连接配置后,您可以将其设置为默认实例,简化代码调用。 #### 默认实例的作用 - **数据模型支持**:默认数据库支持创建数据模型,可通过数据模型的 API 操作数据,也可在微搭低代码中使用模型方法 - **SDK 自动连接**:所有 SDK(JavaScript/Node.js/小程序)在调用相关接口时,若不主动指定目标实例与数据库,将会默认指向该默认实例的默认数据库 #### 设置方法 访问 [云开发平台/文档型数据库/连接管理](https://tcb.cloud.tencent.com/dev?#/db/doc/connect),在目标连接配置处点击「设置为默认实例」按钮。 :::warning 切换影响 默认数据库的切换可能影响业务代码中原数据库的调用方式,建议检查代码后在业务低峰期进行操作。 **受影响的 SDK 调用示例**: - 小程序:`wx.cloud.database()`(未指定 instance 参数) - Web SDK:`app.database()`(未指定 instance 参数) - Node SDK:`app.database()`(未指定 instance 参数) ::: ## 使用方式 接入自有 NoSQL 数据库后,可通过以下两种方式操作数据。 ### 方式一:直接操作数据库集合 直接通过 SDK 进行 CRUD 操作 **方式 1:使用默认实例(wx 原生 API,仅中国站可用)** 小程序中使用 `wx.cloud.database()` 调用的是**默认数据库实例**。如需访问腾讯云 MongoDB 数据库,请先在云开发控制台将其设置为「默认实例」。 ```javascript // 初始化云开发 wx.cloud.init({ env: "your-env-id", // 替换为您的环境ID }); // 自动连接默认实例(无需指定 instance) const db = wx.cloud.database(); const result = await db .collection("users") .where({ status: "active", }) .get(); ``` **方式 2:使用 JS SDK 指定实例** 通过 `@cloudbase/js-sdk` 可以明确指定要访问的数据库实例。 ```javascript import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境ID region: "ap-shanghai", // 不指定 region 时,默认使用 ap-shanghai }); // 登录认证 const auth = app.auth(); await auth.signInWithOpenId(); // 指定访问腾讯云 MongoDB 实例 const db = app.database({ instance: "my-mongodb", // 配置标识 database: "test", }); const result = await db .collection("users") .where({ status: "active", }) .get(); ``` ```javascript // 引入 SDK import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为你的环境ID region: "ap-shanghai", // 不指定 region 时,默认使用 ap-shanghai }); // 登录认证 const auth = app.auth(); await auth.signInAnonymously(); // 或使用其他登录方式 // 连接自有数据库实例 const db = app.database({ instance: "my-mongodb", // 配置标识(创建连接时填写的标识) database: "test", // 数据库名称 }); // 查询数据 const result = await db .collection("users") .where({ status: "active", }) .get(); if (result.data) { console.log("查询结果:", result.data); } else { console.error("查询失败:", result.errMsg); } ``` ```javascript // 引入 SDK const cloudbase = require("@cloudbase/node-sdk"); const app = cloudbase.init({ env: "your-env-id", // 替换为你的环境ID }); // 连接自有数据库实例 const db = app.database({ instance: "my-mongodb", // 配置标识 database: "test", // 数据库名称 }); // 查询数据 const result = await db .collection("users") .where({ status: "active", }) .limit(10) .get(); console.log(result.data); ``` :::tip 参数说明 - `instance`:创建连接配置时填写的「配置标识」 - `database`:目标数据库名称 - 查询语法请参考:[Web SDK](/api-reference/webv3/database/fetch) 或 [Node SDK](/api-reference/server/node-sdk/database/fetch) ::: ### 方式二:使用数据模型 适用于需要结构化数据管理、自动校验、关联查询等场景。 #### 1. 创建数据模型 访问 [云开发平台/文档型数据库/连接管理](https://tcb.cloud.tencent.com/dev?#/db/doc/connect),在对应连接配置处点击「数据模型」 选择现有集合,系统将自动生成对应的数据模型。 #### 2. 操作数据模型 ```javascript // 引入 SDK const { init } = require("@cloudbase/wx-cloud-client-sdk"); wx.cloud.init({ env: "your-env-id", // 替换为你的环境ID }); const client = init(wx.cloud); const models = client.models; // 创建数据(假设已创建 todos 模型) const { data, error } = await models.todos.create({ data: { title: "学习云开发", completed: false, }, }); // 查询数据 const todos = await models.todos.findMany({ where: { completed: false, }, orderBy: { createdAt: "desc", }, }); console.log(todos); ``` ```javascript // 引入 SDK import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为你的环境ID region: "ap-shanghai", // 不指定 region 时,默认使用 ap-shanghai }); // 登录认证 const auth = app.auth(); await auth.signInAnonymously(); // 创建数据(假设已创建 todos 模型) const { data, error } = await app.models.todos.create({ data: { title: "学习云开发", completed: false, }, }); // 查询数据 const todos = await app.models.todos.findMany({ where: { completed: false, }, orderBy: { createdAt: "desc", }, }); console.log(todos); ``` ```javascript const cloudbase = require("@cloudbase/node-sdk"); const app = cloudbase.init({ env: "your-env-id", // 替换为你的环境ID }); // 创建数据 const { data } = await app.models.todos.create({ data: { title: "学习云开发", completed: false, }, }); // 更新数据 await app.models.todos.update({ where: { id: data.id, }, data: { completed: true, }, }); ``` :::tip 提示 详细使用方法请参考 [数据模型文档](/model/introduce) ::: ## 常见问题 ### 数据会被同步到云开发吗? 不会。连接自有数据库后: - **数据存储**:所有数据仍保留在您的数据库中 - **访问方式**:云开发 SDK 直接连接您的数据库进行操作 - **数据控制**:您完全掌控数据的存储和备份 --- # 文档型数据库/其他参考/独占实例升级指南 > 当前文档链接: https://docs.cloudbase.net/database/instance-update ## 概述 云开发文档型数据库默认采用共享实例模式,即多个用户或环境的数据库共享计算资源(CPU、内存等),但数据严格隔离。升级到独占实例可为您的业务提供专属资源保障和更高性能。 ## 为什么选择独占实例? | 特性 | 共享实例 | 独占实例 | |------|---------|---------| | 资源分配 | 多用户共享计算资源 | 专属计算资源,独享CPU和内存 | | 性能稳定性 | 可能受其他用户负载影响 | 性能稳定可预期,不受外部干扰 | | 资源上限 | 有限制,需遵循资源配额 | 更高的资源上限,可根据需求扩展 | | 适用场景 | 开发测试、小型应用 | 生产环境、核心业务、高负载应用 | ## 升级条件 升级到独占实例需满足以下条件: - **环境要求**:您的云开发环境必须是企业版或更高级别套餐 > 提示:如您当前环境不是企业版,请先完成 [套餐升级](https://tcb.cloud.tencent.com/dev),再申请独占实例升级。 ## 升级流程 ### 1. 提交申请 1. 登录[云开发平台/文档型数据库/数据库设置](https://tcb.cloud.tencent.com/dev?#/db/doc/setting) 2. 点击【升级独占实例】按钮 3. 根据引导,提供相关信息 ### 2. 方案确认 1. 技术支持团队将通过工单与您沟通 2. 评估数据量和迁移时间 ### 3. 升级计划制定 1. 确定具体升级时间窗口(建议选择业务低峰期) 2. 明确升级过程中的注意事项和应急预案 3. 确认升级后的验证方案 ### 4. 执行升级 云开发团队将在约定时间执行以下操作: 1. **数据同步**:将现有数据库数据同步至新的独占实例 2. **数据校验**:确保数据完整性和一致性 3. **流量切换**:将业务流量从共享实例切换至独占实例 ### 5. 升级验证 1. 升级完成后,您需要验证业务功能是否正常 2. 确认数据是否完整 3. 检查数据库性能是否符合预期 ## 重要注意事项 ### 升级前准备 - **数据备份**:强烈建议在升级前进行全量数据导出备份 - **业务评估**:评估业务高峰期和低峰期,选择合适的升级时间窗口 - **应用兼容性**:确认您的应用代码与独占实例兼容(通常无需修改) ### 升级过程影响 - **数据同步阶段**: - 不影响现有业务访问,旧实例继续提供服务 - 同步时长取决于数据量大小,通常在1小时内完成 - **流量切换阶段**: - 需要短暂停服(约10-20分钟) - 停服时长与数据量及校验速度相关 - 切换期间数据库无法写入,应用可能出现暂时不可用 ### 升级后影响 - **回档功能**:迁移完成后,旧集群上的回档数据将不可用,回档功能将于次日恢复 - **监控指标**:监控数据会重新开始统计,历史监控数据将不再可见 - **性能变化**:通常会观察到性能提升,响应时间降低 ## 故障处理 如在升级过程中或升级后遇到问题: 1. 立即通过工单联系技术支持 2. 提供详细的问题描述和错误日志 3. 严重问题可申请回滚至原共享实例(仅升级后短时间内可行) ## 常见问题 ### Q: 升级过程中是否会丢失数据? A: 不会。升级过程包含严格的数据同步和校验机制,确保数据完整性。但我们仍建议您在升级前进行数据备份。 ### Q: 升级后需要修改应用代码吗? A: 通常不需要。独占实例与共享实例的接口完全兼容,您的应用代码无需任何修改。 ### Q: 如何判断是否需要升级到独占实例? A: 如果您遇到以下情况,建议考虑升级: - 数据库操作响应时间不稳定 - 业务高峰期性能下降明显 - 数据量和访问量持续增长 - 业务对数据库性能和可用性要求较高 ### Q: 升级后可以降级回共享实例吗? A: 通常不建议降级。如有特殊需求,请通过工单与技术支持团队沟通。 --- # 文档型数据库/其他参考/常见问题 > 当前文档链接: https://docs.cloudbase.net/database/faq ## 计量计费相关问题 ### 文档型数据库与 MySQL 数据库有什么计量计费差异? - **文档型数据库**:计量包括调用次数和数据存储量 - **MySQL 数据库**:计量包括计算资源使用量及存储量 :::tip 注意 在两种数据库上使用数据模型时,针对数据模型的调用也会记录调用次数。 ::: ## 使用相关问题 ### 查询条件正确但结果为空 在使用数据库查询时,如果返回空结果,通常有以下两种情况: 1. 没有符合查询条件的数据 2. 数据被权限控制过滤 #### 排查方法 1. **确认数据存在性** - 在云开发控制台直接查看集合中是否存在目标数据 - 检查数据的创建时间和字段值是否符合预期 2. **检查权限配置** - 查看集合的基础权限设置是否允许当前用户读取 - 数据库查询时会以 `_openid` 字段作为数据归属判定依据 - 如果使用安全规则,验证规则表达式是否正确 - 确认查询条件是否包含安全规则要求的必要字段 3. **验证查询条件** - 简化查询条件,逐步排查哪个条件导致结果为空 - 检查字段名称、数据类型和查询语法是否正确 --- # relational_database Documentation > CloudBase 提供了 MySQL 数据库 服务,支持完整的 SQL 功能,为开发者提供稳定可靠的数据存储解决方案。 # MySQL 数据库/未命名文档 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/initialization --- sidebar_label: 概述 --- import AIDevelopmentPrompt from '../../../../ai/cloudbase-ai-toolkit/components/AIDevelopmentPrompt'; # MySQL 数据库 CloudBase 提供了 **MySQL 数据库** 服务,支持完整的 SQL 功能,为开发者提供稳定可靠的数据存储解决方案。 :::tip 其他数据库类型 CloudBase 还提供 [文档型数据库](../../../introduce) 和 [PostgreSQL 数据库](../postgresql/initialization),可根据业务需求选择合适的数据库类型。 ::: ## 如何创建 ### 初始化数据库 访问 [云开发平台/MySQL数据库](https://tcb.cloud.tencent.com/dev?#/db/mysql/model),首次使用云开发需要开通 MySQL 数据库 > 💡 注意:启用过程需要 2~3 分钟,请耐心等待。 ![MySQL数据库创建界面](https://qcloudimg.tencent-cloud.cn/raw/3d7fe7606af74058028de8ba23068cb4.png) ### 创建表 初始化完成后,点击「+」按钮进行创建 **MySQL表** ![MySQL数据库-新建数据表操作按钮](https://qcloudimg.tencent-cloud.cn/raw/27ebf7514babd164ddf2633e1c72912a.png) ## 自动暂停机制 MySQL数据库基于serverless底座,采用按需计费模式: - 计算节点启动后产生 CCU 计量单位 - 启动后最小运行时长为 10 分钟 - 连续 10 分钟未被访问时会自动暂停,节省成本 数据库自动暂停可节省环境资源使用成本,但同时会产生冷启动影响业务性能。若不希望有该耗时可以关闭 「自动暂停」功能 **操作步骤:** 1. 前往 [云开发平台/MySQL数据库/数据库设置](https://tcb.cloud.tencent.com/dev?#/db/mysql/setting) 2. 关闭「自动暂停」开关 ## 数据模型 针对结构化数据管理、数据质量保障、团队协作开发等场景,云开发提供了**数据模型**功能,具备以下特性: - 可视化定义数据结构、字段类型和关系 - 自动数据校验 - 关联管理 - 内置 CMS 管理界面 通过数据模型功能,开发者可以快速构建业务数据库。 具体使用方法请参考[数据模型](/model/introduce)。 ## 下一步 - [数据库增删改查](/database/configuration/db/tdsql/crud) - [数据权限管理](/database/configuration/db/tdsql/data-permission) - [索引管理](/database/configuration/db/tdsql/data-index) --- # MySQL 数据库/数据操作/字段类型 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/data-type MySQL数据库 面向专业开发者使用,如果您对数据库的数据类型不熟悉,推荐使用 [数据模型](/model/introduce) 定义数据源结构 ## 数值类型 | 数据类型 | 取值范围 | 说明 | 适用场景 | | --------- | ----------------------------------------------------------------- | -------------------------------------------- | -------------------- | | TINYINT | -128 到 127(有符号)
0 到 255(无符号) | 最小整数类型 | 状态值、年龄、小数值 | | SMALLINT | -32768 到 32767(有符号)
0 到 65535(无符号) | 小整数类型 | 计数器、小范围数值 | | MEDIUMINT | -8388608 到 8388607(有符号)
0 到 16777215(无符号) | 中等整数类型 | 中等范围计数、ID | | INT | -2147483648 到 2147483647(有符号)
0 到 4294967295(无符号) | 标准整数类型 | 主键ID、数量、计数 | | BIGINT | -9223372036854775808 到 9223372036854775807(有符号) | 大整数类型 | 大数值、时间戳、大ID | | DECIMAL | 依赖于精度和标度设置 | 精确小数类型 | 金额、精确计算 | | FLOAT | 约 -3.4E+38 到 3.4E+38 | 单精度浮点数 | 科学计算、近似值 | | DOUBLE | 约 -1.8E+308 到 1.8E+308 | 双精度浮点数 | 高精度科学计算 | | REAL | 同 DOUBLE | DOUBLE 的同义词 | 兼容性使用 | | BIT | 0 到 2^n-1 | 位字段类型 | 标志位、位掩码 | | SERIAL | 1 到 9223372036854775807 | 自增大整数(BIGINT UNSIGNED AUTO_INCREMENT) | 自增主键 | | BOOLEAN | TRUE(1) 或 FALSE(0) | 布尔类型(TINYINT(1) 的别名) | 开关状态、是否判断 | ## 日期时间类型 | 数据类型 | 取值范围 | 格式 | 适用场景 | | --------- | ------------------------------------------ | ------------------- | -------------------- | | DATE | 1000-01-01 到 9999-12-31 | YYYY-MM-DD | 生日、日期记录 | | DATETIME | 1000-01-01 00:00:00 到 9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 创建时间、更新时间 | | TIMESTAMP | 1970-01-01 00:00:01 到 2038-01-19 03:14:07 | YYYY-MM-DD HH:MM:SS | 时间戳、自动更新时间 | | TIME | -838:59:59 到 838:59:59 | HH:MM:SS | 时间段、持续时间 | | YEAR | 1901 到 2155 | YYYY | 年份记录 | ## 文本类型 | 数据类型 | 最大长度 | 说明 | 适用场景 | | ---------- | --------------- | ---------------------------------- | ---------------------- | | CHAR | 255字符 | 固定长度字符串 | 固定格式编码、状态码 | | VARCHAR | 65535字符 | 可变长度字符串 | 用户名、标题、描述信息 | | TINYTEXT | 255字符 | 短文本 | 短描述、标签 | | TEXT | 65535字符 | 文本类型 | 文章内容、详细描述 | | MEDIUMTEXT | 16777215字符 | 中等长度文本 | 长文章、富文本内容 | | LONGTEXT | 4294967295字符 | 长文本 | 大型文档、HTML内容 | | BINARY | 255字节 | 固定长度二进制数据 | 哈希值、固定长度编码 | | VARBINARY | 65535字节 | 可变长度二进制数据 | 文件数据、加密信息 | | TINYBLOB | 255字节 | 小型二进制对象 | 小图标、缩略图 | | MEDIUMBLOB | 16777215字节 | 中等二进制对象 | 图片、音频文件 | | BLOB | 65535字节 | 二进制大对象 | 文件、图片数据 | | LONGBLOB | 4294967295字节 | 长二进制对象 | 大文件、视频数据 | | ENUM | 65535个不同值 | 枚举类型,从预定义值列表中选择一个 | 状态值、分类选项 | | SET | 64个不同成员 | 集合类型,从预定义值列表中选择多个 | 权限设置、多选标签 | | JSON | 最大文档大小1GB | JSON格式数据 | 配置信息、结构化数据 | ## 空间数据类型 | 数据类型 | 说明 | 适用场景 | | ------------------ | ------------ | ------------------ | | GEOMETRY | 通用几何类型 | 任意几何形状 | | POINT | 点类型 | 位置标记、坐标点 | | LINESTRING | 线串类型 | 路径、轨迹 | | POLYGON | 多边形类型 | 区域边界、地理围栏 | | MULTIPOINT | 多点类型 | 多个位置点 | | MULTILINESTRING | 多线串类型 | 多条路径 | | MULTIPOLYGON | 多多边形类型 | 多个区域 | | GEOMETRYCOLLECTION | 几何集合类型 | 混合几何对象集合 | --- # MySQL 数据库/数据操作/增删改查 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/crud 云开发提供了多种 SDK 供开发者进行操作 MySQL 数据库,包括小程序 SDK、JS SDK、Node SDK 等 | SDK 类型 | 适用平台 | | --------------------------------------------------------------------------------------------------------------------- | ------------ | | [小程序 ClientSDK](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/guide/model/init-sdk.html) | 小程序 | | [JS SDK](/api-reference/webv3/mysql/fetch) | Web 浏览器 | | [Node SDK](/api-reference/server/node-sdk/mysql/fetch) | Node.js 环境 | | [HTTP API](/http-api/mysqldb/mysql-restful-api) | 通用 | :::tip ⚠️ 注意 小程序 ClientSDK 获取 db 实例后,操作 MySQL 数据库语法与 WebSDK 一致,具体语法请参考 [JS SDK](/api-reference/server/node-sdk/mysql/fetch) ::: ## 常见报错处理 ### xxx.rdb is not a function **报错原因**:当前使用的 CloudBase SDK 版本过旧,不支持 MySQL 数据库操作。 **解决方法**:更新当前 SDK 到最新版本。 ### Generating default gateway base url failed: env not found **报错原因**:微信基础库版本过低,不支持云开发 MySQL 数据库功能。 **解决方法**:更新微信基础库到 **3.8.9** 版本以上。 在微信开发者工具中设置最低基础库版本: 1. 打开「项目详情」 2. 在「本地设置」中,将「调试基础库」设置为 3.8.9 或更高版本 3. 在「基本信息」中,将「最低基础库版本」设置为 3.8.9 或更高版本 :::warning 注意 更新基础库版本后,请确保小程序在低版本微信中的兼容性处理,或在小程序管理后台设置最低版本要求。 ::: --- # MySQL 数据库/权限管理/基础权限 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/data-permission CloudBase MySQL数据库层支持配置基础的数据权限 数据库进行读写时会以 `_openid` 字段作为数据归属判定依据 ## 配置方式 前往 [云开发平台/MySQL数据库](https://tcb.cloud.tencent.com/dev?#/db/mysql/table/default/) ,为每个表设置对应的权限 基础权限控制提供四种预设权限类型,根据用户身份和数据特性选择: | 权限类型 | 适用场景 | | ------------------------------ | ---------------------- | | **读取全部数据,修改本人数据** | 公开内容,如文章、商品 | | **读取和修改本人数据** | 私人数据,如用户资料 | | **读取全部数据,不可修改数据** | 配置数据,如系统设置 | | **无权限** | 敏感数据,如财务信息 | ![云开发平台-MySQL数据库-数据权限](https://qcloudimg.tencent-cloud.cn/raw/c133664816240c393dcdd217e92625fe.png) :::tip 提示 如果期望能对数据做更精细的权限控制,请参考通过 [数据模型管理数据权限](/model/data-permission) ::: --- # MySQL 数据库/高级功能/外键配置 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/foreign-key 外键(Foreign Key)是MySQL数据库中用于建立和维护两个表之间关系的重要约束。通过外键约束,可以确保数据的引用完整性,防止无效数据的插入和更新。 ## 配置外键 在MySQL数据表中新建列时,可以为该列配置外键约束。 ### 配置步骤 1. 进入 [云开发平台/MySQL数据库/数据库表](https://tcb.cloud.tencent.com/dev?#/db/mysql/table/default/) 管理页面 2. 选择目标数据表 3. 点击「新建列」或编辑现有列 4. 在列配置中勾选「设置为外键」 云开发平台-MySQL数据库-数据库表-新建列 5. 配置外键参数 云开发平台-MySQL数据库-数据库表-配置外键 ### 外键配置参数 | 参数名称 | 说明 | 是否必填 | 示例 | | -------- | -------------------------------- | -------- | -------------------- | | 外键名称 | 外键约束的名称,用于标识该外键 | 是 | `fk_user_department` | | 关联库表 | 外键引用的父表名称 | 是 | `departments` | | 关联字段 | 父表中被引用的字段名称 | 是 | `id` | | 删除规则 | 当父表记录被删除时的处理方式 | 是 | `级联` | | 更新规则 | 当父表关联字段被更新时的处理方式 | 是 | `无动作` | ## 删除规则详解 当父表中的记录被删除时,外键约束会根据删除规则对子表中的相关记录进行相应处理: | 规则名称 | 英文标识 | 行为说明 | 使用场景 | | ---------- | ------------- | ------------------------------------------ | -------------------------- | | 无动作 | `NO ACTION` | 如果子表中存在引用记录,则拒绝删除父表记录 | 需要严格控制数据删除的场景 | | 限制 | `RESTRICT` | 与 NO ACTION 相同,拒绝删除操作 | 保护重要关联数据不被误删 | | 级联 | `CASCADE` | 自动删除子表中所有引用该记录的数据 | 主从关系明确,需要同步删除 | | 设为NULL | `SET NULL` | 将子表中的外键字段设置为 NULL | 允许子表记录独立存在 | | 设为默认值 | `SET DEFAULT` | 将子表中的外键字段设置为默认值 | 有合理默认值的业务场景 | ### 删除规则示例 假设有用户表(users)和订单表(orders),订单表的 `user_id` 字段引用用户表的 `id` 字段: ```sql -- 用户表 CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50) ); -- 订单表(带外键约束) CREATE TABLE orders ( id INT PRIMARY KEY, user_id INT, amount DECIMAL(10,2), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); ``` **不同删除规则的效果:** - **CASCADE**:删除用户时,该用户的所有订单也会被删除 - **SET NULL**:删除用户时,相关订单的 `user_id` 设为 NULL - **RESTRICT**:如果用户有订单,则无法删除该用户 - **NO ACTION**:与 RESTRICT 相同,拒绝删除操作 - **SET DEFAULT**:删除用户时,相关订单的 `user_id` 设为默认值 ## 更新规则详解 当父表中被引用字段的值发生更新时,外键约束会根据更新规则处理子表中的相关记录: | 规则名称 | 英文标识 | 行为说明 | 使用场景 | | ---------- | ------------- | ------------------------------------------ | -------------------------- | | 无动作 | `NO ACTION` | 如果子表中存在引用记录,则拒绝更新父表字段 | 需要严格控制主键更新的场景 | | 限制 | `RESTRICT` | 与 NO ACTION 相同,拒绝更新操作 | 保护关联关系不被破坏 | | 级联 | `CASCADE` | 自动更新子表中所有引用该值的字段 | 需要保持数据同步的场景 | | 设为NULL | `SET NULL` | 将子表中的外键字段设置为 NULL | 允许临时断开关联关系 | | 设为默认值 | `SET DEFAULT` | 将子表中的外键字段设置为默认值 | 有合理默认值的业务场景 | ### 更新规则示例 继续使用上面的用户表和订单表示例: ```sql -- 订单表(带更新规则的外键约束) CREATE TABLE orders ( id INT PRIMARY KEY, user_id INT, amount DECIMAL(10,2), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE ); ``` **不同更新规则的效果:** - **CASCADE**:更新用户ID时,相关订单的 `user_id` 也会同步更新 - **SET NULL**:更新用户ID时,相关订单的 `user_id` 设为 NULL - **RESTRICT**:如果用户有订单,则无法更新该用户的ID - **NO ACTION**:与 RESTRICT 相同,拒绝更新操作 - **SET DEFAULT**:更新用户ID时,相关订单的 `user_id` 设为默认值 ## 实际应用示例 ### 示例1:用户与订单关系 ```sql -- 创建用户表 CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL, email VARCHAR(100) UNIQUE ); -- 创建订单表(带外键约束) CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, order_date DATETIME DEFAULT CURRENT_TIMESTAMP, total_amount DECIMAL(10,2), -- 外键约束:级联删除,限制更新 CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE RESTRICT ); ``` ### 示例2:分类与商品关系 ```sql -- 创建商品分类表 CREATE TABLE categories ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, description TEXT ); -- 创建商品表(带外键约束) CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, category_id INT, price DECIMAL(10,2), -- 外键约束:删除时设为NULL,更新时级联 CONSTRAINT fk_product_category FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL ON UPDATE CASCADE ); ``` --- # MySQL 数据库/高级功能/索引管理 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/data-index 索引是提升数据库查询性能的关键技术。通过为常用查询字段建立索引,可以显著提升查询速度和用户体验。 ## 如何创建索引 1. 访问 [云开发平台/MySQL数据库](https://tcb.cloud.tencent.com/dev?#/db/mysql/table/default/) 2. 进入索引管理:选择目标表,点击索引管理标签页 3. 管理索引:新建、删除或修改索引配置 ![MySQL数据库-表索引管理界面](https://qcloudimg.tencent-cloud.cn/raw/139ded1612c40d32b53dee082839eb73.png) --- # MySQL 数据库/高级功能/DMC 数据库管理 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/dmc-data-manage 用户可以通过 [DMC数据库管理平台](https://cloud.tencent.com/document/product/1222/98737) 进行管理MySQL数据库,包括创建、编辑、删除、导入、导出等操作。 ## 进入DMC平台 1. 访问 [云开发平台/MySQL数据库/数据库设置](https://tcb.cloud.tencent.com/dev?#/db/mysql/setting) ![MySQL数据库-数据库设置页面](https://qcloudimg.tencent-cloud.cn/raw/bcbb3cfc5ee8bb0995d49fe1dfbc4870.png) 2. 在「账号管理」模块进行创建账号密码,在弹出的对话框中输入账号名、主机、密码等信息 3. 创建账号后,点击「数据库管理」按钮,进入 DMC 工具页面,输入之前创建的账号密码登录 - 类型为 **云开发(微信云托管数据库)** - 地域已默认选择环境所在地域 ![DMC数据库管理-登录配置界面](https://qcloudimg.tencent-cloud.cn/raw/d099b9b81535795dd4410b64977a933d.png) ## SQL 执行 SQL语句执行是MySQL数据库提供的基本功能,方便开发者通过基础SQL语法执行处理数据库中存储的数据,相较于可视化表编辑方式更加灵活。 进入 DMC 工具后,选择 **SQL 窗口**,即可进入执行面板,进行 SQL 语句编辑 ![DMC工具-SQL窗口执行界面](https://qcloudimg.tencent-cloud.cn/raw/b6dbe85d08d496d15d760966b52c5d16.png) ## 导入导出 MySQL 数据库提供导入导出能力,方便开发者进行数据迁移。 进入 DMC 工具后,点击「悬浮工具」,选择「导入导出」, ![DMC工具-数据导入导出功能入口](https://qcloudimg.tencent-cloud.cn/raw/60f36171dd2f7cadb6691e1cdedce9d8.png) --- # MySQL 数据库/高级功能/备份与回档 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/backup **MySQL数据库** 提供完善的数据备份和回档功能,帮助您保障数据安全,支持在数据丢失或误操作时快速恢复数据。本文档将详细介绍备份类型、操作流程以及回档方法。 ## 数据备份 **MySQL数据库** 支持自动备份和手动备份两种方式: - **自动备份**:平台每天自动执行一次备份,数据备份保留 7 天 - **手动备份**:用户可根据业务需要主动触发备份操作 :::info 重要说明 - 自动备份当前仅支持「快照备份」,用于原集群快速回档 - 自动备份与手动备份默认限制频率为 1 小时 1 次 ::: ### 备份类型 **MySQL数据库** 提供两种备份类型,您可以根据业务场景选择合适的备份方式: | 备份类型 | 技术原理 | 优势 | 劣势 | 适用场景 | | ------------ | --------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------- | -------------------------------- | | **逻辑备份** | 以 SQL 语句方式保存数据库逻辑结构和内容 | • 支持精细的库表级备份
• 对数据库性能影响小
• 包含完整数据库对象 | • 备份速度较慢
• 占用存储体积大 | 需要精确控制备份范围或跨版本迁移 | | **快照备份** | 采用写时重定向技术(ROW)创建存储层快照 | • 备份速度极快(秒级)
• 业务无感知
• 占用体积相对较小 | • 备份粒度较粗 | 生产环境快速备份恢复 | ### 操作步骤 1. 前往 [云开发平台/MySQL数据库/数据库设置](https://tcb.cloud.tencent.com/dev) 2. 在「备份与回档」区域,点击「手动备份」按钮 3. 在弹出的备份面板中选择备份类型(逻辑备份或快照备份) 4. 点击「确认」开始执行备份任务 ![手动备份操作界面](https://qcloudimg.tencent-cloud.cn/raw/ff6fa09f9f67db51a3304cdadb6d521f.png) ## 数据回档 数据回档功能允许您基于已有备份将数据库恢复到指定时间点的状态。 ### 回档特性 - **新实例生成**:回档后将产生新的数据库实例 - **自定义命名**:支持为回档后的数据库自定义重命名 - **原数据处理**:支持选择是否删除原有数据库、数据表 ### 操作步骤 1. 前往 [云开发平台/MySQL数据库/数据库设置](https://tcb.cloud.tencent.com/dev) 2. 在备份记录列表中,找到需要回档的备份记录 3. 点击对应备份记录的「回档」按钮 ![数据回档操作界面](https://qcloudimg.tencent-cloud.cn/raw/a57da62a9490fa6cc76d0b73458860b7.png) --- # MySQL 数据库/高级功能/直连服务 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/direct-connection MySQL 数据库提供连接字符串方式,支持外部服务直接连接云开发的 MySQL 数据库,满足开发者在各类场景下的数据访问需求。 通过开启直连服务,您可以获得数据库的连接地址,在云托管环境或本地开发环境中直接连接 MySQL 数据库进行数据操作。直连服务提供以下两种连接方式: - **内网地址**:仅在云托管环境中可访问,提供高速稳定的数据库连接 - **外网地址**:可在任意网络环境访问,**适用于本地开发和调试场景** > ⚠️ 注意:外网连接地址仅用于开发调试,**生产环境的业务访问请使用内网连接**,以保障性能和安全性。 ## 开启直连服务 ### 操作步骤 1. 访问 [云开发平台/MySQL数据库/数据库设置](https://tcb.cloud.tencent.com/dev?#/db/mysql/setting) ![MySQL数据库-数据库设置-直连服务页面](https://qcloudimg.tencent-cloud.cn/raw/c1d96f0577836ca9c7b190bad119e032.png) 2. 在「直连服务」模块,点击「开启」按钮启用直连功能 3. 开启后,系统会自动生成内网地址和外网地址 ### 连接信息说明 | 连接类型 | 访问范围 | 使用场景 | 说明 | | :------- | :------------------------------------ | :----------------- | :----------------------------- | | 内网地址 | 云托管环境,云函数环境(需要开启VPC) | 生产环境业务访问 | 提供高性能、低延迟的数据库连接 | | 外网地址 | 任意网络环境 | 本地开发、远程调试 | 便于开发调试,可选择关闭 | ## 使用连接字符串 获取连接地址后,您可以在应用代码中使用标准的 MySQL 连接字符串格式进行连接: ``` mysql://:@:/ ``` ### 参数说明 | 参数 | 说明 | | :--------- | :--------------------------------------- | | `username` | 数据库用户名(需在「账号管理」模块创建) | | `password` | 数据库密码 | | `host` | 数据库连接地址(内网地址或外网地址) | | `port` | 数据库端口 | | `database` | 数据库名称 | > 💡 提示:如果您还没有创建数据库账号,请先前往「账号管理」模块创建账号密码 ### 代码示例 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; ```javascript const mysql = require('mysql2/promise'); // 使用内网地址连接(推荐用于生产环境) const connection = await mysql.createConnection({ host: 'your-internal-host.mysql.tencentcdb.com', port: 3306, user: 'your-username', password: 'your-password', database: 'your-database' }); // 执行查询 const [rows] = await connection.execute('SELECT * FROM users WHERE id = ?', [1]); console.log(rows); // 关闭连接 await connection.end(); ``` ```python import pymysql # 使用内网地址连接(推荐用于生产环境) connection = pymysql.connect( host='your-internal-host.mysql.tencentcdb.com', port=3306, user='your-username', password='your-password', database='your-database', charset='utf8mb4' ) try: with connection.cursor() as cursor: # 执行查询 cursor.execute('SELECT * FROM users WHERE id = %s', (1,)) result = cursor.fetchone() print(result) finally: connection.close() ``` ```java import java.sql.*; public class DatabaseConnection { public static void main(String[] args) { // 使用内网地址连接(推荐用于生产环境) String host = 'your-internal-host.mysql.tencentcdb.com'; int port = 3306; String url = String.format("jdbc:mysql://%s:%d/%s", host, port, "your-database"); String username = "your-username"; String password = "your-password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { String sql = "SELECT * FROM users WHERE id = ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 1); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { System.out.println(rs.getString("name")); } } catch (SQLException e) { e.printStackTrace(); } } } ``` ```php setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 执行查询 $stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?'); $stmt->execute([1]); $result = $stmt->fetch(PDO::FETCH_ASSOC); print_r($result); } catch (PDOException $e) { echo 'Connection failed: ' . $e->getMessage(); } ?> ``` ```go package main import ( "database/sql" "fmt" "log" _ "github.com/go-sql-driver/mysql" ) func main() { // 使用内网地址连接(推荐用于生产环境) dsn := "your-username:your-password@tcp(your-internal-host.mysql.tencentcdb.com:3306)/your-database" db, err := sql.Open("mysql", dsn) if err != nil { log.Fatal(err) } defer db.Close() // 执行查询 var name string err = db.QueryRow("SELECT name FROM users WHERE id = ?", 1).Scan(&name) if err != nil { log.Fatal(err) } fmt.Println(name) } ``` --- # MySQL 数据库/高级功能/概述 > 当前文档链接: https://docs.cloudbase.net/model/introduce ## 什么是数据模型? 数据模型是云开发提供的**声明式数据管理解决方案**,它构建在云开发数据库之上,为开发者提供了一种更高效、更安全的数据操作方式。 简单来说,数据模型就像是为您的数据定制的"智能管家": - **定义数据结构**:通过可视化界面定义数据的字段、类型和关系 - **自动数据校验**:确保数据的准确性和一致性 - **处理关联关系**:自动管理数据之间的复杂关系 - **包含多端 SDK**:一次定义,多端使用(小程序、Web、云函数) - **内置管理界面**:提供开箱即用的数据管理后台 - **AI 智能分析**:利用人工智能挖掘数据价值 > **核心理念**:让开发者专注于业务逻辑,而不是底层数据操作的复杂性。 ## 核心特性一览 | 特性 | 描述 | 价值 | | ------------------ | ---------------------------------------- | -------------------------- | | **智能数据校验** | 自动检查数据类型和格式,防止错误数据入库 | 提高数据质量,减少 Bug | | **关联关系管理** | 自动处理一对一、一对多、多对多关系 | 简化复杂查询,提升开发效率 | | **多端 SDK 支持** | 一次定义,自动生成小程序/Web/云函数 SDK | 统一开发体验,减少重复工作 | | **可视化管理** | 内置 CMS 管理界面,支持非技术人员操作 | 降低运营成本,提升协作效率 | | **低代码应用生成** | 一键生成可定制的管理后台应用 | 快速交付,缩短开发周期 | | **AI 数据分析** | 智能分析数据趋势和模式 | 数据驱动决策,挖掘业务价值 | | **高级查询能力** | 支持复杂条件查询、聚合分析等 | 满足多样化业务需求 | ## 数据模型和数据库的关系 - 数据模型是对数据库的建模,它定义了数据模型的结构、字段、约束和关系。数据模型提供了一种统一的方式来描述和操作数据,简化了数据操作和查询。数据模型能力即为开发中常用的对象关系映射 ORM。 - 数据模型和数据库是关联关系,一个数据模型,对应了数据库中的一个集合(非结构化数据库)或表(结构化数据库)。 - 使用数据模型,不代表被限制为仅能使用数据模型。在使用数据模型的同时,如果需要有更复杂、或数据模型无法很好完成的操作,也可以通过数据库的原生方法,直接操作数据库完成数据读写。 - 相对于传统开发框架中的 ORM,云开发数据模型除 ORM 相关的 SDK 操作数据的能力外,额外整合了数据管理界面、用户及权限体系、内容管理以及进一步的基于数据模型的应用生成、数据分析等更多能力。更多优势点可以查看 "为什么要使用数据模型"。 ## 快速开始 ### 第一步:访问控制台 1. **选择数据库**: - 访问 [云开发平台/文档型数据库/数据模型](https://tcb.cloud.tencent.com/dev?#/db/doc/model/?sourceType=internal_flexdb) - 访问 [云开发平台/MySQL数据库/数据模型](https://tcb.cloud.tencent.com/dev?#/db/mysql/model/?sourceType=internal_mysql) 2. **创建模型**:点击「新建模型」,选择创建方式 ### 第二步:开始使用 新建模型后,您就可以立即使用数据模型提供的各项强大能力了! > **建议先阅读** [快速开始教程](/model/initialization),通过实际案例快速上手。 ## 为什么选择数据模型? 数据模型为开发者带来了**全方位的开发体验提升**: - **提高开发效率**:自动生成 SDK,减少重复代码编写 - **降低维护成本**:统一的数据管理,减少运维工作量 - **增强数据安全**:内置权限控制和数据校验机制 - **强化数据分析**:AI 智能分析,挖掘数据价值 - **简化团队协作**:可视化管理界面,技术与非技术人员都能轻松使用 ### 智能数据校验和类型检查 数据模型提供**自动化的数据校验机制**,在数据入库前进行严格的类型检查和格式验证,确保数据的准确性和一致性,从源头避免数据质量问题。 #### 实际案例演示 假设我们定义了一个文章模型 `post`,包含以下字段: - `title`(字符串类型)- 文章标题 - `body`(字符串类型)- 文章内容 现在我们故意传入错误类型的数据来测试校验机制: ```javascript try { const { data } = await models.post.create({ data: { title: "你好,世界", body: 123456, // ❌ 故意传入数字类型,期望的是字符串类型 }, }); console.log("创建成功:", data); } catch (error) { console.error("校验失败:", error); } ``` #### 校验结果 当我们尝试插入类型错误的数据时,数据模型会立即检测到问题并阻止操作: ```bash Error: WxCloudSDKError: 【错误】数据格式校验失败。根因:[#/body: expected type: String, found: Integer],原因:字段[正文], 标识[body], 类型不匹配:期望类型:字符串。,解决步骤:请按照原因修改数据类型。 errRecord:{"title":"你好,世界","body":123456}【操作】调用 post.create ``` #### 校验优势 - **精确定位**:准确指出哪个字段出现了什么问题 - **友好提示**:提供清晰的错误原因和解决建议 - **数据保护**:防止脏数据进入数据库 - **减少 Bug**:在开发阶段就发现数据类型问题 ### 智能关联关系处理 数据模型能够**自动处理复杂的数据关联关系**,支持一对一、一对多、多对多等各种关系类型。无需手动编写复杂的 JOIN 查询,系统会自动处理数据之间的关联逻辑。 #### 关联查询示例 以文章和评论的关联关系为例,我们可以轻松实现跨模型的数据查询。通过 [`select`](sdk-reference/model#select) 参数,可以精确控制返回的字段和关联数据: ```javascript const { data } = await models.post.list({ // 精确控制返回字段,优化查询性能 select: { _id: true, title: true, updatedAt: true, // 关联查询评论数据 comments: { _id: true, createdAt: true, comment: true, }, }, filter: { where: {}, // 查询条件 }, getCount: true, // 获取总数 }); console.log("查询结果:", data); ``` #### 返回结果 系统会自动处理关联关系,返回结构化的数据: ```json { "records": [ { "_id": "9FSAHWM9VV", "title": "Bonjour le Monde", "updatedAt": 1718096503886, "comments": [ { "_id": "9FSAJF3GLG", "createdAt": 1718096509916, "comment": "这是一条评论" } ] }, { "_id": "9FSAHWM9VU", "title": "你好,世界", "updatedAt": 1718096503886, "comments": [] // 暂无评论 } ], "total": 2 } ``` #### 关联查询优势 - **性能优化**:只返回需要的字段,减少数据传输 - **自动关联**:无需手写复杂的 JOIN 语句 - **多层嵌套**:支持深层次的关联数据查询 - **精确控制**:灵活指定每个关联对象的返回字段 ### 多端 SDK 自动生成 云开发平台提供了**多端对接的 SDK**,支持小程序、Web、云函数等多个平台。一次定义,多端复用,大幅提升开发效率。 #### 智能 Upsert 操作 以 [upsert()](sdk-reference/model#upsert) 方法为例,它能智能判断是创建新记录还是更新现有记录: ```javascript const postData = { title: "Hello World", body: "这是一篇示例文章", _id: "hello-world-post", }; const { data } = await models.post.upsert({ create: postData, // 如果不存在则创建 update: postData, // 如果存在则更新 filter: { where: { _id: { $eq: postData._id } }, }, }); console.log("操作结果:", data); ``` #### 返回结果说明 ```javascript // 创建新记录时 { "count": 0, // 更新的记录数为 0 "id": "hello-world-post" // 新创建记录的 ID } // 更新现有记录时 { "count": 1, // 更新的记录数为 1 "id": "" // 更新操作不返回 ID } ``` #### 多端 SDK 优势 - **统一接口**:所有平台使用相同的 API 调用方式 - **类型安全**:TypeScript 支持,编译时发现错误 - **自动优化**:根据平台特性自动优化性能 - **实时同步**:模型变更后 SDK 自动更新 ### 内置数据管理系统 数据模型提供**开箱即用的内容管理系统(CMS)**,让非技术人员也能轻松进行数据管理和维护,大幅降低运营成本。 #### 核心功能 - **可视化编辑**:直观的表单界面,无需编程知识 - **权限管理**:细粒度的用户权限控制 - **数据统计**:内置数据分析和报表功能 - **高级搜索**:支持多条件筛选和排序 - **响应式设计**:支持桌面和移动端访问 > **了解更多**:查看 [使用数据管理](/toolbox/manage-data) 了解完整功能 ![管理界面](https://cloudcache.tencent-cloud.com/qcloud/ui/static/static_source_business/8cdd0b8d-83b7-496f-a93b-38949ac362d5.png) ### 低代码应用生成器 数据模型支持**一键生成完整的管理后台应用**,采用先进的低代码技术,支持可视化修改和定制开发,让您无需从零编写管理界面。 #### 低代码特性 - **拖拽式设计**:通过拖拽组件快速构建界面 - **模板丰富**:提供多种预设模板和组件 - **深度定制**:支持自定义样式和业务逻辑 - **一键部署**:生成的应用可直接部署使用 - **实时预览**:所见即所得的开发体验 ![低代码应用生成器](https://cloudcache.tencent-cloud.com/qcloud/ui/static/static_source_business/b2b54b47-7b1d-48ec-b294-4e98a27e5d58.png) ### AI 智能数据分析 数据模型集成了**强大的 AI 分析引擎**,能够自动挖掘数据中的模式和趋势,为业务决策提供智能化支持。 #### AI 分析能力 - **趋势预测**:基于历史数据预测未来趋势 - **异常检测**:自动识别数据中的异常模式 - **智能报表**:自动生成可视化分析报告 - **洞察建议**:提供基于数据的业务建议 - **用户画像**:智能分析用户行为特征 > **核心价值**:从海量数据中提取有价值的商业洞察,让数据真正驱动业务增长 ![AI 智能分析界面](https://qcloudimg.tencent-cloud.cn/image/document/22a107ad53ac7ac6d527c6630b46ce7a.png) ### 高级数据库查询支持 数据模型支持**原生 SQL 查询**,兼容 MySQL 等关系型数据库的高级查询功能。当模型 API 无法满足复杂查询需求时,您可以直接使用 SQL 语句进行精确控制。 #### 高级查询能力 - **复杂条件筛选**:支持多表联查、子查询等 - **聚合分析**:GROUP BY、HAVING、聚合函数等 - **性能优化**:索引优化、查询计划分析 - **安全防护**:参数化查询,防止 SQL 注入 #### 实际案例 查询作者联系电话以"1858"开头的记录: ```javascript const result = await models.$runSQL( "SELECT * FROM `article_table` WHERE author_tel LIKE ?;", ["1858%"] // 参数化查询,安全可靠 ); console.log("查询结果:", result); ``` #### 返回结果 ```json { "data": { "total": 1, "executeResultList": [ { "_id": "9JXU7BWFZJ", "title": "示例文章", "author_tel": "18588881111", "createdAt": 1719475245475, "region": "北京市" } ], "backendExecute": "28" }, "requestId": "0d4c98c3-a3ff-4c55-93cc-d0f5c835f82c" } ``` > **深入学习**:查看 [数据库原生查询文档](/model/db-raw-query) 了解更多高级用法 --- ## 总结 云开发数据模型为现代应用开发提供了**全方位的数据管理解决方案**: - **架构优势**:基于云原生架构,稳定可靠 - **开发效率**:自动生成 SDK,减少重复工作 - **数据安全**:内置校验和权限控制机制 - **管理便捷**:可视化管理界面,降低运营成本 - **智能分析**:AI 驱动的数据洞察能力 - **灵活扩展**:支持原生查询和自定义逻辑 **立即开始您的数据模型之旅!** 查看 [快速开始教程](/model/initialization) --- # MySQL 数据库/高级功能/连接自有 MySQL > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/custom-database 您可以将自有的 MySQL 数据库接入云开发平台,复用现有数据库资源,降低数据迁移成本。 接入自有数据库后,您可以: - **无缝集成**:直接管理现有数据库中的数据,无需数据迁移 - **数据模型**:基于现有数据表快速生成数据模型,自动化数据管理 - **SDK 支持**:使用云开发 Web SDK / Node SDK / 小程序 SDK 统一访问 - **安全可控**:数据保留在您的数据库中,完全掌控数据安全 ## 前置条件 在配置自有 MySQL 数据库连接前,请确保: | 检查项 | 要求 | | :--------- | :--------------------------------------- | | 网络访问 | 数据库具有公网访问能力 | | 用户权限 | 已创建专用数据库用户账号,具备表操作权限 | | 防火墙配置 | 数据库防火墙已开放相应端口(默认 3306) | | IP 白名单 | 已配置云开发服务 IP 白名单(见文档末尾) | ## 创建连接配置 ### 1. 进入配置页面 访问 [云开发平台/MySQL 数据库](https://tcb.cloud.tencent.com/dev?#/db/mysql/table/default/),点击顶部「新增」按钮 ![MySQL数据库新增页面](https://qcloudimg.tencent-cloud.cn/raw/de873f028ffc3d063904a880e323b6d7.png) ### 2. 填写连接信息 | 参数 | 说明 | 必填 | | :--------- | :--------------------------------------------------------------------------------------------------------------- | :--- | | 数据库类型 | 选择「MySQL」或「SQLServer」(SQLServer 需提工单申请) | 是 | | 配置名称 | 自定义名称,用于在平台中识别此连接 | 是 | | 配置标识 | 英文标识符,用于 SDK 中指定连接(如 `my-database`) | 是 | | 接入方式 | 选择「自有数据库」或「腾讯云数据库」 | 是 | | 主机地址 | MySQL 数据库的公网 IP 地址或域名 | 是 | | 端口 | MySQL 服务端口(默认 3306) | 是 | | 数据库名 | 要连接的目标数据库名称 | 是 | | 用户名 | 数据库连接用户名 | 是 | | 密码 | 数据库连接密码 | 是 | | 连接参数 | 额外的 [MySQL 连接参数](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html)(如 `charset=utf8mb4`) | 否 | | 超时时间 | 连接和查询的超时时间(单位:秒) | 否 | :::warning 注意 若需连接「SQLServer 数据库」,请联系腾讯侧架构师或提交工单申请开通。 ::: ## 使用方式 接入自有 MySQL 数据库后,可通过以下两种方式操作数据。 ### 方式一:直接操作数据库表 直接通过 SDK 进行 CRUD 操作 ```javascript // 引入 SDK const { init } = require("@cloudbase/wx-cloud-client-sdk"); // 指定云开发环境 ID wx.cloud.init({ env: "your-env-id", // 替换为你的环境ID }); const client = init(wx.cloud); // 连接自有数据库实例 const db = client.rdb({ instance: "my-database", // 配置标识(创建连接时填写的标识) database: "test", // 数据库名称 }); // 查询数据 const { data, error } = await db .from("users") .select("*") .eq("status", "active"); if (error) { console.error("查询失败:", error); } else { console.log("查询结果:", data); } ``` ```javascript // 引入 SDK import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为你的环境ID region: "ap-shanghai", // 不指定 region 时,默认使用 ap-shanghai }); // 登录认证 const auth = app.auth(); await auth.signInAnonymously(); // 或使用其他登录方式 // 连接自有数据库实例 const db = app.rdb({ instance: "my-database", // 配置标识(创建连接时填写的标识) database: "test", // 数据库名称 }); // 查询数据 const { data, error } = await db .from("users") .select("*") .eq("status", "active"); if (error) { console.error("查询失败:", error); } else { console.log("查询结果:", data); } ``` ```javascript // 引入 SDK const cloudbase = require("@cloudbase/node-sdk"); const app = cloudbase.init({ env: "your-env-id", // 替换为你的环境ID }); // 连接自有数据库实例 const db = app.rdb({ instance: "my-database", // 配置标识 database: "test", // 数据库名称 }); // 查询数据 const { data, error } = await db .from("users") .select("id, name, email") .eq("status", "active") .limit(10); console.log(data); ``` :::tip 参数说明 - `instance`:创建连接配置时填写的「配置标识」 - `database`:目标数据库名称 - 查询语法请参考:[Web SDK](/api-reference/webv3/mysql/fetch) 或 [Node SDK](/api-reference/server/node-sdk/mysql/fetch) ::: ### 方式二:使用数据模型 适用于需要结构化数据管理、自动校验、关联查询等场景。 #### 1. 创建数据模型 访问 [云开发平台/MySQL 数据库/连接管理](https://tcb.cloud.tencent.com/dev?#/db/mysql/connect),在对应连接配置处点击「数据模型」 ![云开发平台-MySQL数据库-连接配置点击数据模型页面](https://qcloudimg.tencent-cloud.cn/raw/05878c158ff72e48679fadecebb9e1ec.png) 选择现有数据表,系统将自动生成对应的数据模型。 #### 2. 操作数据模型 ```javascript // 引入 SDK const { init } = require("@cloudbase/wx-cloud-client-sdk"); wx.cloud.init({ env: "your-env-id", // 替换为你的环境ID }); const client = init(wx.cloud); const models = client.models; // 创建数据(假设已创建 todos 模型) const { data, error } = await models.todos.create({ data: { title: "学习云开发", completed: false, }, }); // 查询数据 const todos = await models.todos.findMany({ where: { completed: false, }, orderBy: { createdAt: "desc", }, }); console.log(todos); ``` ```javascript // 引入 SDK import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为你的环境ID region: "ap-shanghai", }); // 登录认证 const auth = app.auth(); await auth.signInAnonymously(); // 创建数据(假设已创建 todos 模型) const { data, error } = await app.models.todos.create({ data: { title: "学习云开发", completed: false, }, }); // 查询数据 const todos = await app.models.todos.findMany({ where: { completed: false, }, orderBy: { createdAt: "desc", }, }); console.log(todos); ``` ```javascript const cloudbase = require("@cloudbase/node-sdk"); const app = cloudbase.init({ env: "your-env-id", // 替换为你的环境ID }); // 创建数据 const { data } = await app.models.todos.create({ data: { title: "学习云开发", completed: false, }, }); // 更新数据 await app.models.todos.update({ where: { id: data.id, }, data: { completed: true, }, }); ``` :::tip 提示 详细使用方法请参考 [数据模型文档](/model/introduce) ::: ## IP 白名单配置 为保障数据库安全,请将以下云开发服务 IP 地址添加到您的 MySQL 数据库白名单中: ```text 175.24.211.44 175.24.212.162 175.24.213.48 175.24.214.104 175.24.214.93 49.234.25.245 49.234.27.58 49.234.3.160 49.234.34.31 49.234.35.33 ``` :::warning 重要 - **必须添加所有 IP 地址**才能确保服务正常运行 - 不同云服务商的白名单配置方式不同,请参考对应的数据库管理文档 ::: ## 常见问题 ### 连接失败如何排查? 按照以下步骤逐一排查: 1. **网络连通性**:确认数据库可通过公网访问 2. **防火墙配置**:检查数据库端口是否开放 3. **IP 白名单**:确认已添加所有云开发服务 IP 4. **连接参数**:检查主机地址、端口、数据库名、用户名、密码是否正确 ### 是否支持内网数据库? 目前仅支持具有公网访问能力的数据库。如需连接内网数据库,可考虑: - 通过云服务商提供的公网代理访问 - 使用 VPN 或专线打通网络 - 使用腾讯云数据库(支持内网直连) ### 数据会被同步到云开发吗? 不会。连接自有数据库后: - **数据存储**:所有数据仍保留在您的数据库中 - **访问方式**:云开发 SDK 直接连接您的数据库进行操作 - **数据控制**:您完全掌控数据的存储和备份 --- # MySQL 数据库/HTTP 调用/查询数据 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/http/query 通过 HTTP RESTful API 对 MySQL 数据库进行查询操作。具体接口详情请参考 [HTTP API/MySQL数据库](/http-api/mysqldb/query-records) ## 基础语法 ```bash GET https://your-envId.api.tcloudbasegateway.com/v1/rdb/rest/:table Authorization: Bearer Content-Type: application/json ``` > 💡 提示:access_token 请参考 [获取AccessToken](/http-api/basic/access-token) ## 基础查询 ```bash # 查询 film表 所有数据 curl -X GET 'https://{{host}}/v1/rdb/rest/film' \ -H 'Authorization: Bearer ' ``` > 💡 注意:返回的数据是所有字段数据,默认为`/table?select=*`
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 1037 content-type: application/json; charset=utf-8 Body: [ { "_openid": "1977683311217119233", "director": "陈凯歌", "duration": 171, "id": 1, "release_year": 1993, "title": "霸王别姬" }, // ... 共 15 条数据 ] ```
## 指定字段返回 ```bash # 指定 film表 返回字段 curl -X GET 'https://{{host}}/v1/rdb/rest/film?select=title,director,release_year' \ -H 'Authorization: Bearer ' ```
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 1080 content-type: application/json; charset=utf-8 Body: [ { "director": "陈凯歌", "release_year": 1993, "title": "霸王别姬" }, { "director": "姜文", "release_year": 2010, "title": "让子弹飞" } // ... 共 15 条数据 ] ```
## 条件查询 - 具体操作符参考:[操作符](#操作符) - 具体逻辑运算符参考:[逻辑运算符](#逻辑运算符) ```bash # 查询导演为 Christopher Nolan 的电影 curl -X GET 'https://{{host}}/v1/rdb/rest/film?director=eq.Christopher Nolan' \ -H 'Authorization: Bearer ' # 查询 2000 年后上映的电影,电影时长不超过 120 分钟 curl -X GET 'https://{{host}}/v1/rdb/rest/film?release_year=gte.2000&duration=lt.120' \ -H 'Authorization: Bearer ' # 查询 2000 年后上映的电影,导演是 Christopher Nolan 或者 姜文 curl -X GET 'https://{{host}}/v1/rdb/rest/film?or=(director.eq.Christopher Nolan, director.eq.姜文)' \ -H 'Authorization: Bearer ' ```
查询导演为 Christopher Nolan 的电影 ```text HTTP/1.1 200 OK Header: content-length: 260 content-type: application/json; charset=utf-8 Body: [ { "_openid": "1977683311217119233", "director": "Christopher Nolan", "duration": 169, "id": 13, "release_year": 2014, "title": "Interstellar" }, { "_openid": "1977683311217119233", "director": "Christopher Nolan", "duration": 148, "id": 14, "release_year": 2010, "title": "Inception" } ] ```
## 排序和分页 ```bash # 查询 2000 年前的电影,展示播放时长最长的 top3 电影 curl -X GET 'https://{{host}}/v1/rdb/rest/film?order=duration.desc&limit=3' \ -H 'Authorization: Bearer ' ```
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 266 content-type: application/json; charset=utf-8 Body: [ { "director": "James Cameron", "duration": 194, "release_year": 1997, "title": "Titanic" }, { "director": "陈凯歌", "duration": 171, "release_year": 1993, "title": "霸王别姬" }, { "director": "Frank Darabont", "duration": 142, "release_year": 1994, "title": "The Shawshank Redemption" } ] ```
## 计数查询 ```bash # 查询 2000 年后的电影和总数 curl -X GET 'https://{{host}}/v1/rdb/rest/film?select=*&release_year=gte.2000' \ -H 'Authorization: Bearer ' \ -H 'Prefer: count=exact' ``` **Request** - 指定 **Header** ` Prefer: count=exact` 时,返回符合条件的数据总数 **Response** - `content-range: 0-9/10` 表示本次查询返回的数据范围,共计 10 条数据,0表示无偏移,9 表示最后一条数据的索引,10 表示总数
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 853 content-range: 0-9/10 content-type: application/json; charset=utf-8 Body: [ { "director": "姜文", "duration": 132, "release_year": 2010, "title": "让子弹飞" }, { "director": "郭帆", "duration": 125, "release_year": 2019, "title": "流浪地球" }, { "director": "吴京", "duration": 123, "release_year": 2017, "title": "战狼2" } // ...等 共计 10 条数据 ] ```
## 强制返回对象格式 当前数据返回都是以数组的形式返回,如果需要强制返回对象格式,可以指定 `Header Accept: application/vnd.pgrst.object+json` > ⚠️ 注意:当明确只有一条数据返回时才能使用此方式,可以加上 `limit=1` 用来限制返回的数据条数 ```bash curl -i -X GET 'http://{{host}}/v1/rdb/rest/v1/film?select=*&id=eq.1&limit=1' \ -H 'Authorization: Bearer ' \ -H 'Accept: application/vnd.pgrst.object+json' ```
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 121 content-type: application/vnd.pgrst.object+json Body: { "director": "陈凯歌", "duration": 171, "id": 1, "release_year": 1993, "title": "霸王别姬", "_openid": "1977683311217119233" } ```
## 字面量查询 MySQL 允许创建带有空格等特殊字符的表和字段,此时 url 请求就需要做字面量处理(使用双引号标记)避免和特殊字符、关键字冲突。 假设有如下表结构: - **表名**: `my table` - **字段名**: `full name`、`email address` ```bash curl -i -X 'GET http://{{host}}/v1/rdb/rest/my table?select="full name","email address"' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ ```
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 61 content-type: application/json; charset=utf-8 Body: [ { "email address": "john@example.com", "full name": "John Doe" } ] ```
## 操作符 | 操作符 | 说明 | 示例 | | :----: | :------: | :-------------------------------: | | `eq` | 等于 | `?id=eq.1` | | `neq` | 不等于 | `?id=neq.1` | | `gt` | 大于 | `?age=gt.18` | | `gte` | 大于等于 | `?age=gte.18` | | `lt` | 小于 | `?age=lt.35` | | `lte` | 小于等于 | `?age=lte.35` | | `like` | 模糊匹配 | `?name=like.%value%` | | `in` | 包含 | `?id=in.[1,2,3]` | | `is` | 为空 | `?age=is.null` | | `and` | 逻辑与 | `?and=(age.gt.18,age.lt.35)` | | `or` | 逻辑或 | `?or=(id.eq.1,name.like.%value%)` | | `not` | 逻辑非 | `?age=not.eq.18` | ## 支持的逻辑运算符 | 运算符 | 说明 | Mysql 等效操作符 | 示例 | | :----: | :----: | :--------------: | :-------------------------------------------------------------------------: | | `and` | 逻辑与 | `and` | `/table?id=eq.1&age=gt.18` 以及 `/table?select=*&and=(age.gt.18,age.lt.35)` | | `or` | 逻辑或 | `or` | `/table?select=*&or=(id.eq.1,name.like.%value%)` | | `not` | 逻辑非 | `not` | `/table?select=*&age=not.eq.18&gender=not.is.null&isTrue=not.is.true` | --- # MySQL 数据库/HTTP 调用/关联关系查询 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/http/relate-query ## 概述 关联关系查询允许您在单个请求中获取多个相关表的数据,避免多次查询的性能开销。系统通过 MySQL 外键约束自动识别表间关系。 ### 基本概念 - **主表**:外键字段所在的表,是关联关系中的「多方」 - **从表**:被关联的表,通过主键与主表的外键建立关系 - **关联方式**:支持外关联(LEFT JOIN)和内关联(INNER JOIN) ## 查询语法 ### 关联方式 | 关联方式 | 语法 | 说明 | SQL 等价 | | :------- | :-------------------------------- | :--------------------------------------------------- | :----------- | | 外关联 | `关联字段(子字段1,子字段2)` | 默认使用 LEFT JOIN,即使关联表无数据也会返回主表记录 | `LEFT JOIN` | | 内关联 | `关联字段!inner(子字段1,子字段2)` | 使用 INNER JOIN,只返回两表都有数据的记录 | `INNER JOIN` | ### 关联字段表达方式 | 表达方式 | 语法 | 示例 | 说明 | | :--------- | :--------------------- | :--------------------------------- | :----------------------------- | | 外键字段名 | `外键字段(子字段列表)` | `director_id(name,country)` | 最常用方式,直接使用外键字段名 | | 关联表名 | `表名(子字段列表)` | `director(name,country)` | 使用被关联的表名 | | 外键约束名 | `约束名(子字段列表)` | `fk_relate_director(name,country)` | 使用外键约束的名称 | ### 别名设置 | 用法 | 语法 | 示例 | 说明 | | :--------- | :---------------------------------- | :------------------------------------------------ | :----------------------- | | 字段别名 | `别名:关联字段(子字段列表)` | `d:director_id(name,country)` | 为关联字段设置别名 | | 自关联别名 | `别名1:字段1(...),别名2:字段2(...)` | `manager:manager_id(name),mentor:mentor_id(name)` | 自关联时必须使用别名区分 | ## 多对一 ### 表和E-R ```mermaid erDiagram director ||--o{ film : "执导" director { INT id PK VARCHAR(50) name VARCHAR(30) country VARCHAR(64) _openid } film { INT id PK VARCHAR(100) title YEAR release_year INT director_id FK INT duration VARCHAR(64) _openid } ``` ### 查询示例 **需求**:查询电影信息,同时获取对应导演的详细信息 **说明**:通过外键字段 `director_id` 关联 `director` 表,获取导演的 id、姓名和国籍信息。使用外关联方式,即使某些电影没有导演信息也会返回电影记录。 ```shell curl -i -X GET 'http://{{host}}/v1/rdb/rest/film?select=id,title,release_year,duration,director_id(id,name,country)' \ -H 'Authorization: Bearer ' ```
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 573 content-type: application/json; charset=utf-8 Body: [ { "director_id": { "country": "USA", "id": 1, "name": "Steven Spielberg" }, "duration": 164, "id": 1, "release_year": 1993, "title": "Jurassic Park" }, { "director_id": { "country": "UK", "id": 2, "name": "Christopher Nolan" }, "duration": 172, "id": 2, "release_year": 2010, "title": "Inception" }, { "director_id": { "country": "Japan", "id": 3, "name": "Hayao Miyazaki" }, "duration": 98, "id": 3, "release_year": 2001, "title": "Spirited Away" }, { "director_id": null, "duration": 100, "id": 4, "release_year": 2020, "title": "Unknown Film 1" }, { "director_id": null, "duration": 100, "id": 5, "release_year": 2015, "title": "Untitled Project" } ] ```
## 一对多 ### 表和E-R ```mermaid erDiagram film ||--o{ review : "has" film { INT id PK VARCHAR(100) title YEAR release_year INT director_id FK VARCHAR(64) _openid } review { INT id PK TEXT content INT film_id FK VARCHAR(64) _openid } ``` ### 查询示例 **需求**:查询电影信息,同时获取该电影的所有评论 **说明**:通过反向关联查询,从 `film` 表查询关联的 `review` 表数据。系统会自动识别 `review` 表中的 `film_id` 外键,返回该电影的所有评论内容。 ```shell curl -i -X GET 'http://{{host}}/v1/rdb/rest/film?select=id,title,release_year,duration,review(content)' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' ```
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 437 content-type: application/json; charset=utf-8 Body: [ { "duration": 142, "id": 1, "release_year": 1994, "review": [ { "content": "One of the greatest movies ever made!" }, { "content": "Tim Robbins and Morgan Freeman delivered outstanding performances." } ], "title": "The Shawshank Redemption" }, { "duration": 173, "id": 2, "release_year": 1972, "review": [ { "content": "Marlon Brando's performance is legendary." } ], "title": "The Godfather" }, { "duration": 98, "id": 3, "release_year": 1994, "review": [ ], "title": "Pulp Fiction" } ] ```
## 一对一 ### 表和E-R ```mermaid erDiagram film ||--|| award : "has" film { INT id PK VARCHAR(100) title YEAR release_year INT duration VARCHAR(64) _openid } award { INT id PK VARCHAR(100) award_name YEAR year INT film_id FK,UK VARCHAR(64) _openid } ``` ### 查询示例 **需求**:查询电影信息及其获奖情况 **说明**:一对一关系查询,每部电影最多对应一个奖项记录。 ```shell curl -i -X GET 'http://{{host}}/v1/rdb/rest/film?select=id,title,release_year,duration,award(award_name,year)' \ -H 'Authorization: Bearer ' ```
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 354 content-type: application/json; charset=utf-8 Body: [ { "award": { "award_name": "Best Picture Nominee", "year": 1994 }, "duration": 142, "id": 1, "release_year": 1994, "title": "The Shawshank Redemption" }, { "award": null, "duration": 154, "id": 3, "release_year": 1994, "title": "Pulp Fiction" } ] ```
## 多对多 ### 表和E-R ```mermaid erDiagram film ||--o{ film_actor : "has" actor ||--o{ film_actor : "participates" film { INT id PK VARCHAR(100) title YEAR release_year INT duration VARCHAR(64) _openid } actor { INT id PK VARCHAR(50) name VARCHAR(50) country VARCHAR(64) _openid } film_actor { INT id PK INT film_id FK INT actor_id FK VARCHAR(50) role_name VARCHAR(64) _openid } ``` ### 关系识别规则 > 💡 注意:多对多关系需要通过中间表实现,中间表必须包含两个外键字段作为联合主键的一部分 系统会自动识别多对多关系,无需在请求中指定中间表。 ### 查询示例 **需求**:查询电影信息及参演的演员列表 **说明**:通过中间表 `film_actor` 实现多对多关联,系统自动识别关系并返回每部电影的演员信息。 ```shell curl -i -X GET 'http://{{host}}/v1/rdb/rest/film?select=id,title,release_year,duration,actor(name,country)' \ -H 'Authorization: Bearer ' ```
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 367 content-type: application/json; charset=utf-8 Body: [ { "actor": [ { "country": "USA", "name": "Morgan Freeman" } ], "duration": 142, "id": 1, "release_year": 1994, "title": "The Shawshank Redemption" }, { "actor": [ ], "duration": 175, "id": 3, "release_year": 1972, "title": "The Godfather" } ] ```
## 自关联 ### 表和E-R ```mermaid erDiagram employee ||--o{ employee : "manages" employee ||--o{ employee : "mentors" employee { INT id PK VARCHAR(50) name VARCHAR(100) position INT manager_id FK INT mentor_id FK VARCHAR(64) _openid } ``` ### 查询示例 **需求**:查询员工信息,同时获取其上级和导师信息 **说明**:自关联查询必须使用别名区分不同的关联字段。这里使用 `manager` 和 `mentor` 别名分别表示上级和导师关系。 ```shell curl -i -X GET 'http://{{host}}/v1/rdb/rest/employee?select=name,position,manager:manager_id(name,position),mentor:mentor_id(name,position)' \ -H 'Authorization: Bearer ' ```
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 979 content-type: application/json; charset=utf-8 Body: [ { "manager": null, "mentor": null, "name": "John Smith", "position": "CEO" }, { "manager": null, "mentor": null, "name": "Sarah Johnson", "position": "CTO" } ] ```
--- # MySQL 数据库/HTTP 调用/新增数据 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/http/insert 通过 HTTP RESTful API 对 MySQL 数据库进行新增操作。具体接口详情请参考 [HTTP API/MySQL数据库](/http-api/mysqldb/insert-records) ## 基础语法 ```bash POST https://your-envId.api.tcloudbasegateway.com/v1/rdb/rest/:table Authorization: Bearer Content-Type: application/json ``` > 💡 提示:access_token 请参考 [获取AccessToken](/http-api/basic/access-token) ## 基础新增 ```bash # 新增单条数据到 film表 curl -X POST 'https://{{host}}/v1/rdb/rest/film' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "title": "Inception", "release_year": 2010, "director": "Christopher Nolan", "duration": 148 }' ``` > 💡 注意:默认情况下新增操作不返回数据,仅返回状态码和受影响行数 **Response** - `content-range: */1`,表示受影响行数,此时标表示插入了 1 条数据 - `preference-applied: return=minimal`,表示执行写数据时,采用了 `return=minimal` 的返回策略(写操作默认行为,即不产生返回体)
请求返回示例 ```text HTTP/1.1 201 Created Header: content-length: 0 content-range: */1 preference-applied: return=minimal Body: (Empty) ```
## 新增并返回数据 ```bash # 新增数据并返回插入的完整记录 curl -X POST 'https://{{host}}/v1/rdb/rest/film?select=*' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -H 'Prefer: return=representation' \ -d '{ "title": "The Shawshank Redemption", "release_year": 1994, "director": "Frank Darabont", "duration": 142 }' ``` **Request** - 请求中指定 Header `Prefer: return=representation`,表示执行新增时,返回新增后的数据,**不会**额外产生一次查询,默认返回所有字段数据 **Response** - 如果表中存在自增字段,则返回值中会包含自增字段的值
请求返回示例 ```text HTTP/1.1 201 Created Header: content-length: 266 content-range: */1 preference-applied: return=representation Body: [ { "_openid": "1977683311217119233", "director": "Frank Darabont", "duration": 142, "id": 2, "release_year": 1994, "title": "The Shawshank Redemption" } ] ```
## 指定返回字段 ```bash # 新增数据并返回指定字段 curl -X POST 'https://{{host}}/v1/rdb/rest/film?select=title,director' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -H 'Prefer: return=representation' \ -d '{ "title": "The Godfather", "release_year": 1972, "director": "Francis Ford Coppola", "duration": 175 }' ``` **Request** - 请求中指定 Header `Prefer: return=representation`,表示执行新增时,返回新增后的数据,**不会**额外产生一次查询,默认返回所有字段数据 - 指定`/table?select=*`时,默认返回插入的所有字段数据;指定`/table?select=id,title,director`时,返回插入的指定字段数据
请求返回示例 ```text HTTP/1.1 201 Created Header: content-length: 78 content-range: */1 preference-applied: return=representation Body: [ { "id": 1, "director": "Francis Ford Coppola", "title": "The Godfather" } ] ```
## 批量新增 ```bash # 批量新增多条数据 curl -X POST 'https://{{host}}/v1/rdb/rest/film?select=id,title,director,duration' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -H 'Prefer: return=representation' \ -d '[ { "title": "The Godfather: Part II", "release_year": 1974, "director": "Francis Ford Coppola", "duration": 202 }, { "title": "The Godfather: Part III", "release_year": 1990, "director": "Francis Ford Coppola", "duration": 194 } ]' ``` **Response** - `content-range: */2` 表示本次请求创建了2条数据 - 批量创建数据时,整体在一个事务中,如果有失败,则整体回滚
请求返回示例 ```text HTTP/1.1 201 Created Header: content-length: 312 content-range: */2 preference-applied: return=representation Body: [ { "director": "Francis Ford Coppola", "duration": 202, "id": 4, "title": "The Godfather: Part II" }, { "director": "Francis Ford Coppola", "duration": 194, "id": 5, "title": "The Godfather: Part III" } ] ```
:::warning 注意 注意: 在执行批量创建时,自增字段回填可能不准确,请不要依赖批量创建时返回的自增字段! 原因:受 MySQL 数据库特性影响,创建数据时 MySQL 仅能知道受影响行数,以及最终插入的自增值是多少,从而推算出这一批数据的自增值。但是自增值分配并非连续的,可能遇到"手动指定"、"INSERT IGNORE"、"INSERT ON DUPLICATE KEY UPDATE"等情况,导致自增值分配不连续。 ::: ## 新增冲突时执行更新 ```bash # 新增或更新数据 curl -X POST 'https://{{host}}/v1/rdb/rest/film?select=*' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -H 'Prefer: return=representation, resolution=merge-duplicates' \ -d '{ "title": "The Godfather", "release_year": 1972, "director": "Francis Ford Coppola", "duration": 175 }' ``` **Request** - `return=representation` 表示执行写数据时,返回写入后的数据,配合 select 一起使用,默认返回所有字段数据 - `resolution=merge-duplicates` 表示当创建冲突时,进行更新处理。冲突依据:当主键或唯一索引存在创建冲突时,进行更新处理 - `content-range: */2` 表示受影响行数,此时表示本次请求创建 1 条数据时发生冲突,从而进行更新处理,所以受影响行数为 2 :::tip 注意 - 由于 MySQL 数据库特性限制,`resolution=merge-duplicates` 在 **高并发** 场景下存在 **死锁** 风险,请谨慎使用。 - 当使用 `resolution=merge-duplicates` 时,请确保请求体中包含主键或唯一索引字段。 - 当使用 `resolution=merge-duplicates` 时, MySQL 将这种情况视为 ​先尝试插入再更新,因此: - ​插入尝试​:计为 1 行受影响(即使未实际插入)。 - ​更新操作​:计为 1 行受影响。 - ​总计 content-range 返回 2 :::
请求返回示例 ```text HTTP/1.1 201 Created Header: content-length: 116 content-range: */2 content-type: application/json; charset=utf-8 preference-applied: return=representation, resolution=merge-duplicates Body: [ { "_openid": "1977683311217119233", "director": "Nolan", "duration": 150, "id": 1, "release_year": 2010, "title": "Inception" } ] ```
## 新增冲突时执行忽略 ```bash # 新增或更新数据 curl -X POST 'https://{{host}}/v1/rdb/rest/film?select=*' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -H 'Prefer: return=representation, resolution=ignore-duplicates' \ -d '{ "title": "The Godfather", "release_year": 1972, "director": "Francis Ford Coppola", "duration": 175 }' ``` **Request** - `return=representation` 表示执行写数据时,返回写入后的数据,配合 select 一起使用,默认返回所有字段数据 - `resolution=ignore-duplicates` 表示当创建冲突时,进行忽略处理。冲突依据:当主键或唯一索引存在创建冲突 - `content-range: */0` 当创建冲突时,进行忽略,此时受影响行数为 0, `content-range: */0` 。 :::tip 注意 - 当使用 `resolution=ignore-duplicates` 时, MySQL 将这种情况视为 ​静默忽略​(不插入也不更新),所以受影响行数是 0。 即使忽略了冲突,仍会消耗一个自增值 :::
Response ```text HTTP/1.1 201 Created Header: content-length: 125 content-range: */0 content-type: application/json; charset=utf-8 preference-applied: return=representation, resolution=ignore-duplicates Body: [ { "_openid": "1977683311217119233", "director": "Sidney Lumet", "duration": 96, "id": 2, "release_year": 1957, "title": "12 Angry Men" } ] ```
--- # MySQL 数据库/HTTP 调用/更新数据 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/http/update 通过 HTTP RESTful API 对 MySQL 数据库进行更新操作。具体接口详情请参考 [HTTP API/MySQL数据库](/http-api/mysqldb/update-records) ## 基础语法 ```bash PATCH https://your-envId.api.tcloudbasegateway.com/v1/rdb/rest/:table Authorization: Bearer Content-Type: application/json ``` > 💡 提示:access_token 请参考 [获取AccessToken](/http-api/basic/access-token) ## 基础更新 ```bash # 更新单条数据 curl -X PATCH 'https://{{host}}/v1/rdb/rest/film?id=eq.1' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "duration": 202 }' ``` > 💡 注意:默认情况下更新操作不返回数据,仅返回状态码和受影响行数 **Response** - `content-range: */1`,表示受影响行数,此时表示更新了 1 条数据 - `preference-applied: return=minimal`,表示执行写数据时,采用了 `return=minimal` 的返回策略(写操作默认行为,即不产生返回体)
请求返回示例 ```text HTTP/1.1 204 No Content Header: content-range: */1 preference-applied: return=minimal Body: (Empty) ```
## 更新并返回数据 ```bash # 更新数据并返回更新后的完整记录 curl -X PATCH 'https://{{host}}/v1/rdb/rest/film?select=*&id=in.(3,4,5)' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -H 'Prefer: return=representation' \ -d '{ "director": "Francis Ford Coppola" }' ``` **Request** - 请求中指定 Header `Prefer: return=representation`,表示执行更新时,返回更新后的数据 > 💡 注意:更新后返回数据,实际上是更新+查询,因此会产生 **两次** db 请求。两次请求在同一事务内,因此不会产生脏读,但如果查询失败会导致更新也一起回滚。
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 387 content-range: */3 content-type: application/json; charset=utf-8 preference-applied: return=representation Body: [ { "director": "Francis Ford Coppola", "duration": 175, "id": 3, "release_year": 1972, "title": "The Godfather" }, { "director": "Francis Ford Coppola", "duration": 202, "id": 4, "release_year": 1974, "title": "The Godfather: Part II" }, { "director": "Francis Ford Coppola", "duration": 194, "id": 5, "release_year": 1990, "title": "The Godfather: Part III" } ] ```
## 无条件更新限制 ```bash # 无条件更新会被拒绝 curl -X PATCH 'https://{{host}}/v1/rdb/rest/film' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "duration": 202 }' ``` **Response** - 无条件更新属于不安全的操作,当请求发生时,系统会做保护,拒绝此类请求
请求返回示例 ```text HTTP/1.1 400 Bad Request Header: content-length: 113 content-type: application/json; charset=utf-8 Body: { "code": "BadApiRequest", "details": "", "hint": "", "message": "UPDATE requires a WHERE clause" } ```
## 复杂条件更新 ```bash # 更新时指定复杂条件+排序+分页 curl -X PATCH 'https://{{host}}/v1/rdb/rest/film?select=*&id=in.(3,4,5)&and=(duration.gte.150,duration.lt.200)&or=(director.eq.Christopher Nolan,director.eq.Frank Darabont)&limit=1&order=id.desc' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -H 'Prefer: return=representation' \ -d '{ "director": "Francis Ford Coppola" }' ``` **Request** - `id=in.(3,4,5)`: 更新 id 在 3、4、5中的记录 - `and=(duration.gte.150,duration.lt.200)`: 且时长大于等于 150 分钟,且时长小于 200 分钟 - `or=(director.eq.Christopher Nolan,director.eq.Frank Darabont)`: 或导演是 Christopher Nolan 或 Frank Darabont - `limit=1`: 最多更新 1 条记录 - `order=id.desc`: 按 id 降序排序后更新
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 142 content-range: */1 content-type: application/json; charset=utf-8 preference-applied: return=representation Body: [ { "_openid": "1977683311217119233", "director": "Francis Ford Coppola", "duration": 194, "id": 5, "release_year": 1990, "title": "The Godfather: Part III" } ] ```
## 特殊场景 当执行更新操作且返回数据时,系统会根据主键来获取更新后的数据,但需要注意甄别以下场景: ### 单字段主键表 如果表的主键是单字段,那么在更新时,主键既作为更新条件又作为更新值时,更新会成功,但查询返回可能不符合预期: ```bash # 将 id=1 的电影信息主键改为 2 curl -X PATCH 'https://{{host}}/v1/rdb/rest/film?select=title,release_year,duration&id=eq.1' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -H 'Prefer: return=representation' \ -d '{ "id": 2 }' ``` 此时可以正常更新数据,返回头的 `Content-Range` 会表明受影响行数 1,但是由于 `id` 既作为主键又作为更新值,所以查询结果会为空 ### 联合主键表 同单字段主键一样,如果联合主键其中任意一个字段既作为条件又作为更新值时,更新会成功,但查询返回可能不符合预期: ```bash # 表 user_activities 的联合主键为 (activity_date, activity_type) # 将 user_activities 表中 activity_type 为 login 的记录的 activity_type 更新为 logout curl -X PATCH 'https://{{host}}/v1/rdb/rest/user_activities?activity_type=eq.login' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -H 'Prefer: return=representation' \ -d '{ "activity_type": "logout" }' ``` 此时可以正常更新数据,返回头的 `Content-Range` 会表明受影响行数 1,但是由于 `activity_type` 是联合主键的一部分,且作为更新值,所以查询结果会为空 ### 无主键表 如果表没有主键,那么在更新时,不能指定请求头 `Prefer: return=representation`,不支持无主键表更新且返回数据,会报错提示: ```text table has no primary key, cannot use update-with-return, suggest remove query ``` 当移除 `Prefer: return=representation` 后,无主键表可以正常更新 --- # MySQL 数据库/HTTP 调用/删除数据 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/http/delete 通过 HTTP RESTful API 对 MySQL 数据库进行删除操作。具体接口详情请参考 [HTTP API/MySQL数据库](/http-api/mysqldb/delete-records) ## 基础语法 ```bash DELETE https://your-envId.api.tcloudbasegateway.com/v1/rdb/rest/:table Authorization: Bearer ``` > 💡 提示:access_token 请参考 [获取AccessToken](/http-api/basic/access-token) ## 基础删除 ```bash # 删除单条数据 curl -X DELETE 'https://{{host}}/v1/rdb/rest/film?id=eq.1' \ -H 'Authorization: Bearer ' ``` > 💡 注意:默认情况下删除操作不返回数据,仅返回状态码和受影响行数 **Response** - `content-range: */1`,表示受影响行数,此时表示删除了 1 条数据 - `preference-applied: return=minimal`,表示执行删除时,采用了 `return=minimal` 的返回策略(删除操作默认行为,即不产生返回体)
请求返回示例 ```text HTTP/1.1 204 No Content Header: content-length: 0 content-range: */1 preference-applied: return=minimal Body: (Empty) ```
## 删除并返回数据 ```bash # 删除数据并返回被删除的记录 curl -X DELETE 'https://{{host}}/v1/rdb/rest/film?select=*&id=eq.2' \ -H 'Authorization: Bearer ' \ -H 'Prefer: return=representation' ``` **Request** - 请求中指定 Header `Prefer: return=representation`,表示执行删除时,默认返回 **删除前** 的所有字段数据 > 💡 注意:删除前返回数据,实际上是查询+删除,因此会产生 两次 db 请求。两次请求在同一事务内,因此不会产生脏读,但如果查询失败会导致删除也一起回滚。
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 266 content-range: */1 preference-applied: return=representation Body: [ { "_openid": "1977683311217119233", "director": "Frank Darabont", "duration": 142, "id": 2, "release_year": 1994, "title": "The Shawshank Redemption" } ] ```
## 复杂条件删除 ```bash # 使用复杂条件删除数据 curl -X DELETE 'https://{{host}}/v1/rdb/rest/film?id=in.(7,8,9)&duration.gte.150&limit=2&order=id.desc' \ -H 'Authorization: Bearer ' \ -H 'Prefer: return=representation' ``` **Request** - `id=in.(7,8,9)`:删除 id 在 7、8、9 中的记录 - `duration.gte.150`:且时长大于等于 150 分钟 - `limit=2`:最多删除 2 条记录 - `order=id.desc`:按 id 降序排列后删除
请求返回示例 ```text HTTP/1.1 200 OK Header: content-length: 156 content-range: */2 preference-applied: return=representation Body: [ { "id": 9, "title": "The Dark Knight", "director": "Christopher Nolan", "duration": 152 }, { "id": 8, "title": "Pulp Fiction", "director": "Quentin Tarantino", "duration": 154 } ] ```
--- # MySQL 数据库/框架快速入门/未命名文档 > 当前文档链接: https://docs.cloudbase.net/database/framework-quickstarts/table/mp --- sidebar_label: "微信小程序" --- # 在微信小程序中访问云开发 MySQL 数据库表 学习如何创建云开发 MySQL 数据库表,添加示例数据,以及从微信小程序中查询数据。 ## 1. 创建 MySQL 数据库表并添加数据 > 注意:必须使用你的微信小程序关联的腾讯云账号登录云开发平台 在云开发平台创建名为 `todos` 的数据库表,字段信息如下: | 列名 | 描述 | 类型 | 是否必填 | 是否唯一 | | ------------ | -------- | ------- | -------- | -------- | | id | 标识 | int | 是 | 是 | | is_completed | 是否完成 | boolean | 否 | 否 | | description | 描述 | text | 否 | 否 | | title | 标题 | varchar | 否 | 否 | 创建完成后,在 `todos` 表中录入示例数据。 ## 2. 创建微信小程序 ![微信开发者工具-新建小程序项目界面](https://qcloudimg.tencent-cloud.cn/raw/3bbd8b1e0e3775158c64ea98e7f873c1.png) ## 3. 安装云开发 SDK 依赖 1. 在小程序 `app.json` 所在的目录执行命令安装 npm 包。 ```bash npm install @cloudbase/wx-cloud-client-sdk ``` 2. 点击微信开发者工具工具栏上的“工具” -> “构建 npm”。 ## 4. 从小程序中查询数据表 - 初始化 SDK ```javascript // app.js const { init } = require("@cloudbase/wx-cloud-client-sdk"); App({ onLaunch() { let db = null; this.globalData.getDB = async () => { if (!db) { wx.cloud.init({ env: "<云开发环境 ID>", }); db = init(wx.cloud).rdb(); } return db; }; }, globalData: {}, }); ``` - 在页面 `js` 文件中添加以下查询代码 ```javascript // pages/index/index.js Page({ data: { todos: [], }, onLoad() { this.fetchTodos(); }, async fetchTodos() { try { const db = await getApp().globalData.getDB(); const { data } = await db.from("todos").select("*").range(0, 9); this.setData({ todos: data, }); } catch (error) { console.error("获取待办事项失败:", error); } }, }); ``` - 在页面对应的 `wxml` 文件中展示数据 ```xml {{item.title}} ``` ## 5. 运行小程序 在微信开发者工具中,点击编译预览小程序。 ## 注意事项 - 将 \<云开发环境 ID> 替换为你实际的云开发环境 ID。 --- # MySQL 数据库/框架快速入门/未命名文档 > 当前文档链接: https://docs.cloudbase.net/database/framework-quickstarts/table/react --- sidebar_label: "React" --- # 在 React 应用中访问云开发 MySQL 数据库 学习如何创建云开发 MySQL 数据库表,添加示例数据,以及从 React 应用查询数据。 ## 1. 创建 MySQL 数据库表并添加数据 在云开发平台创建名为 `todos` 的数据库表,字段信息如下: | 列名 | 描述 | 类型 | 是否必填 | 是否唯一 | | ------------ | -------- | ------- | -------- | -------- | | id | 标识 | int | 是 | 是 | | is_completed | 是否完成 | boolean | 否 | 否 | | description | 描述 | text | 否 | 否 | | title | 标题 | varchar | 否 | 否 | 创建完成后,在 `todos` 数据库表中录入示例数据。 ## 2. 创建 React 应用 > 要求 Node.js 版本 >= 20.19 ```bash npm create vite@latest todo -- --template react ``` ## 3. 安装云开发 SDK 依赖 在项目根目录下,执行以下命令: ```bash cd todo && npm install @cloudbase/js-sdk ``` ## 4. 声明云开发环境变量 创建 `.env.local` 文件,并填入云开发环境 ID ``` VITE_CLOUDBASE_ENV_ID=<云开发环境 ID> ``` ## 5. 从应用查询数据 在 `app.jsx` 中添加以下代码查询数据: ```javascript import cloudbase from "@cloudbase/js-sdk"; import { useEffect, useState } from "react"; import "./App.css"; let db = null; const getDB = async () => { if (!db) { const app = cloudbase.init({ env: import.meta.env.VITE_CLOUDBASE_ENV_ID, }); const auth = app.auth({ persistence: "local", }); await auth.signInAnonymously(); db = app.rdb(); } return db; }; function App() { const [todos, setTodos] = useState([]); useEffect(() => { fetchTodos(); }, []); const fetchTodos = async () => { try { const { data } = await getDB().from("todos").select("*").range(0, 9); setTodos(data); } catch (error) { console.error("获取待办事项失败:", error); } }; return ( <>
    {" "} {todos.map((todo) => (
  • {todo.title}
  • ))}{" "}
{" "}
{" "} ); } export default App; ``` ## 6. 启动应用 在项目根目录下执行以下命令启动应用: ```bash npm run dev ``` ## 注意事项 - 操作不同环境数据时,需设置对应的 `envType`。 - 若使用其他登录方式(参考[【登录认证】](https://docs.cloudbase.net/api-reference/webv3/authentication)),将 `auth.signInAnonymously()` 替换为对应登录方法。 --- # MySQL 数据库/框架快速入门/未命名文档 > 当前文档链接: https://docs.cloudbase.net/database/framework-quickstarts/table/vue --- sidebar_label: "Vue" --- # 在 Vue 应用中访问云开发 MySQL 数据库 学习如何创建云开发 MySQL 数据表,添加示例数据,以及从 Vue 应用查询数据。 ## 1. 创建 MySQL 数据表并添加数据 在云开发平台创建名为 `todos` 的数据表,字段信息如下: | 列名 | 描述 | 类型 | 是否必填 | 是否唯一 | | ------------ | -------- | ------- | -------- | -------- | | id | 标识 | int | 是 | 是 | | is_completed | 是否完成 | boolean | 否 | 否 | | description | 描述 | text | 否 | 否 | | title | 标题 | varchar | 否 | 否 | 创建完成后,在 `todos` 数据表中录入示例数据。 ## 2. 创建 Vue 应用 > 要求 Node.js 版本 >= 20.19 ```bash npm create vite@latest todo -- --template vue ``` ## 3. 安装云开发 SDK 依赖 在项目根目录下,执行以下命令: ```bash cd todo && npm install @cloudbase/js-sdk ``` ## 4. 声明云开发环境变量 创建 `.env.local` 文件,并填入云开发环境 ID ``` VITE_CLOUDBASE_ENV_ID=<云开发环境 ID> ``` ## 5. 从应用查询数据 在 `App.vue` 中添加以下代码查询数据: ```vue ``` ## 6. 启动应用 在项目根目录下执行以下命令启动应用: ```bash npm run dev ``` ## 注意事项 - 操作不同环境数据时,需设置对应的 `envType`。 - 若使用其他登录方式(参考[【登录认证】](https://docs.cloudbase.net/api-reference/webv3/authentication)),将 `auth.signInAnonymously()` 替换为对应登录方法。 --- # MySQL 数据库/其他参考/MySQL 迁移至自有账号 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/migrate-to-user-account 云开发 MySQL 数据库默认部署在「云开发侧」,现支持将数据库从「云开发侧」迁移到「用户自有账号」。迁移后,数据库将部署在您自己的腾讯云账号下,带来以下优势: - **内网连接支持**:可通过 VPC 内网连接数据库,支持云函数直连 - **更高性能**:内网连接延迟更低,性能更优 - **更强安全性**:数据库在您的账号下,资源隔离更彻底 - **灵活管理**:可通过腾讯云控制台直接管理数据库实例 > ⚠️ **重要说明**: > - 迁移前数据库在云开发官方账号下,仅支持公网连接 > - 迁移后数据库在您的账号下,支持 VPC 内网连接 > - 迁移过程需要先备份数据,再重新安装数据库 ## 迁移前准备 在开始迁移前,请确认: 1. **数据备份**:已完成数据库的完整备份 2. **业务影响评估**:了解迁移过程中数据库将暂时不可用 3. **权限确认**:确保您的腾讯云账号有创建 MySQL 实例的权限 4. **连接方式调整**:迁移后需要修改应用程序的数据库连接配置 ## 迁移流程 ### 步骤 1:备份数据 迁移前必须先备份现有数据,避免数据丢失。 #### 1.1 登录 DMC 平台 参考 [数据库管理](/database/configuration/db/tdsql/dmc-data-manage) 文档,登录到 DMC 平台。 ![DMC数据库管理-登录配置界面](https://qcloudimg.tencent-cloud.cn/raw/d099b9b81535795dd4410b64977a933d.png) > 💡 **提示**:若在 DMC 平台无法正常选择实例,请前往 [云开发平台/MySQL数据库/数据库设置](https://tcb.cloud.tencent.com/dev?#/db/mysql/setting),点击「直连服务」,根据提示提交工单。 #### 1.2 导出表结构和数据 1. 在 DMC 平台点击「悬浮工具」,选择「导入导出」 2. 点击「数据导出」 3. 执行数据库选择名为「当前环境 ID」的库 4. 选择文件类型为 **SQL** 5. 选择表导出,导出内容选择 **数据和结构** 6. 点击「确定」按钮,导出表结构和数据(为 zip 格式) 7. 解压后得到 `.sql` 文件 > ⚠️ **重要提醒**: > - 请妥善保存导出的 SQL 文件,这是您的数据备份 > - 如果数据量较大,建议分批导出 > - 确保导出完成后再进行下一步操作 ### 步骤 2:卸载数据库 备份完成后,需要卸载当前的 MySQL 数据库。 1. 进入 [云开发平台/MySQL数据库/数据库设置](https://tcb.cloud.tencent.com/dev?#/db/mysql/setting) 2. 点击右上角「销毁数据库」按钮 3. 确认销毁操作 ![MySQL数据库设置-销毁数据库操作按钮](https://qcloudimg.tencent-cloud.cn/raw/3477a730469bfcdf8b75ac73c1b4badf.png) > ⚠️ **警告**:销毁数据库后,原数据库中的所有数据将被清除,请确保已完成数据备份。 ### 步骤 3:重新安装数据库 卸载完成后,重新安装 MySQL 数据库。此次安装将在您的账号下创建数据库实例。 1. 在 [云开发平台/MySQL数据库](https://tcb.cloud.tencent.com/dev?#/db/mysql/table/default/) 页面 2. 点击「开通 MySQL 数据库」按钮 3. 选择 MySQL 版本(推荐 8.0) 4. **关键步骤**:选择私有网络(VPC)和子网 - 如需云函数内网连接,选择云函数所在的 VPC - 建议选择与其他云资源相同的 VPC,便于内网互联 5. 确认开通 ![MySQL数据库-版本选择界面](https://qcloudimg.tencent-cloud.cn/raw/3d7fe7606af74058028de8ba23068cb4.png) > 💡 **提示**: > - 开通完成后,数据库将部署在您的腾讯云账号下 > - 系统会自动生成默认表结构 > - 数据库的内网连接地址将在「数据库设置」页面显示 ### 步骤 4:导入数据 数据库重新安装完成后,导入之前备份的数据。 #### 4.1 登录 DMC 平台 参考 [步骤 1.1](#11-登录-dmc-平台) 登录 DMC 平台。 #### 4.2 导入表结构和数据 1. 点击「悬浮工具」,选择「导入导出」 2. 点击「数据导入」 3. 执行数据库选择名为「当前环境 ID」的库 4. 选择文件类型为 **SQL** 5. 上传步骤 1 中导出的 `.sql` 文件 6. 点击「确定」开始导入 > 💡 **提示**: > - 导入成功后即可完成数据迁移 > - 如有删除的数据模型,需手动重建 > - 建议导入后验证数据完整性 ## 完整迁移示例 以下是完整的迁移流程总结,可作为操作检查清单: ``` ✓ 步骤 1:备份数据 ├─ 登录 DMC 平台 ├─ 导出表结构和数据(SQL 格式) └─ 验证 SQL 文件完整性 ✓ 步骤 2:卸载数据库 ├─ 进入数据库设置页面 └─ 销毁当前数据库 ✓ 步骤 3:重新安装数据库 ├─ 选择 MySQL 版本 ├─ 配置 VPC 和子网 └─ 确认开通 ✓ 步骤 4:导入数据 ├─ 登录 DMC 平台 ├─ 导入 SQL 文件 └─ 验证数据完整性 ✓ 步骤 5:配置内网连接(可选) ├─ 获取内网连接地址 ├─ 配置云函数 VPC └─ 更新数据库连接代码 ``` ## 注意事项 ### 数据安全 - 迁移前务必完整备份数据,建议保留多个备份副本 - 导出的 SQL 文件包含敏感数据,请妥善保管 - 迁移过程中数据库不可用,建议在业务低峰期进行 ### 应用程序调整 - 迁移后需更新应用程序中的数据库连接地址 - 从公网地址更改为内网地址 - 建议使用环境变量管理数据库连接配置 ### 性能优化 - 内网连接延迟通常在 1-5ms,远低于公网连接 - 建议使用连接池提升数据库操作性能 - 详细的性能优化方法请参考 [云函数/调用 MySQL 数据库/最佳实践](/cloud-function/resource-integration/mysql#最佳实践) ## 常见问题 ### 云函数连接不上数据库? 请检查以下配置: 1. 云函数是否配置了正确的 VPC 和子网 2. VPC 是否与数据库所在 VPC 一致 3. 数据库连接地址是否使用内网地址 4. 云函数环境变量是否配置正确 ## 相关文档 - [数据库管理](/database/configuration/db/tdsql/dmc-data-manage) - DMC 平台使用指南 - [云函数/调用 MySQL 数据库](/cloud-function/resource-integration/mysql) - 云函数内网连接数据库 - [云托管/调用 MySQL 数据库](/run/develop/databases/mysql) - 云托管内网连接数据库 - [云开发资源迁移指南](/quick-start/env-transfer) - 环境迁移完整指南 - [MySQL版本升级](/database/configuration/db/tdsql/up-version) - 数据库版本升级操作 --- # MySQL 数据库/其他参考/MySQL版本升级 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/up-version 云开发 MySQL数据库 版本包含 5.7、8.0,当前开通MySQL数据库时默认为8.0版本 如果您当前为5.7版本,则可以通过当前文档进行升级 ## 升级流程 通过DMC平台导出导入表结构及数据 1. 导出表结构、数据 2. 升级数据库版本 3. 导入表结构、数据 ## 操作步骤 ### 1. 登录 DMC 平台 参考 [数据库管理](/database/configuration/db/tdsql/dmc-data-manage) 文档,登录到 DMC 平台 ![DMC数据库管理-登录配置界面](https://qcloudimg.tencent-cloud.cn/raw/d099b9b81535795dd4410b64977a933d.png) ### 1.1 提交工单 若在DMC平台无法正常选择实例,请前往 [云开发平台/MySQL数据库/数据库设置](https://tcb.cloud.tencent.com/dev?#/db/mysql/setting),点击「直连服务」,若需要提交工单,待工单处理完成后,即可正常选择实例 ![MySQL数据库设置-提交工单操作按钮](https://qcloudimg.tencent-cloud.cn/raw/09728eb6de0f16dab41f30b7fe706a46.png) ### 2. 导出表结构、数据 点击「悬浮工具」,选择「导入导出」 1. 点击「数据导出」 2. 执行数据库选择名为「当前环境ID」的库 3. 选择文件类型为 **SQL** 4. 选择表导出,导出内容选择 **数据和结构** 5. 点击「确定」按钮,即可导出表结构、数据(为zip格式),解压后为 **.sql** 文件 ### 3. 升级数据库版本 1. 进入 [云开发平台/MySQL数据库](https://tcb.cloud.tencent.com/dev?#/db/mysql/setting) 2. 点击右上角「销毁数据库」按钮 ![MySQL数据库设置-销毁数据库操作按钮](https://qcloudimg.tencent-cloud.cn/raw/3477a730469bfcdf8b75ac73c1b4badf.png) 3. 重新开通 MySQL 8.0 ![MySQL数据库-版本选择界面](https://qcloudimg.tencent-cloud.cn/raw/3d7fe7606af74058028de8ba23068cb4.png) 4. 开通完成后,平台会重新生成系统默认表 ### 导入表结构、数据 1. 参考 [1.登录 DMC 平台](#1-登录-dmc-平台) 登录 DMC 平台 2. 点击「悬浮工具」,选择「导入导出」 3. 点击「数据导入」 4. 执行数据库选择名为「当前环境ID」的库 5. 选择文件类型为 **SQL** 6. 上传 [2.导出表结构数据](2-导出表结构数据) 导出的 **.sql** 文件 7. 导入成功后即可完成整体数据迁移,若有删除的数据模型,则手动建立即可 --- # MySQL 数据库/其他参考/错误码 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/errorcode 本文档介绍云开发「MySQL 数据库」操作过程中可能遇到的错误码及其解决方法。 ## 业务错误码 业务错误码用于标识云开发平台在处理 MySQL 数据库操作时产生的业务逻辑错误。 | 错误码 | 错误信息 | 说明 | 解决方法 | | ------------------------------------------------ | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | AuthFailure.DataSourceOperationAuthFailure | Permission denied. Please check permissions in the permission module. | 权限拒绝,数据库操作权限不足 | 前往 [云开发平台/MySQL 数据库/权限管理](https://tcb.cloud.tencent.com) 检查并配置权限 | | AuthFailure.SetOwnerNotAllowed | Setting user identifier field is not allowed. | 不允许设置用户标识字段 `_openid` | 移除对 `_openid` 字段的显式赋值操作,该字段由系统自动管理 | | AuthFailure.PermissionNotConfigured | Permission not configured. Please configure proper permissions in the permission module. | 未配置数据访问权限 | 前往 [云开发平台/MySQL 数据库/权限管理](https://tcb.cloud.tencent.com) 配置相应权限规则 | | AuthFailure.AuthError | Permission error. | 权限验证失败,通常是自定义表中缺少系统依赖的 `_openid` 字段 | 确认表结构中包含 `_openid` 字段(VARCHAR 类型),或关闭权限验证 | | FailedOperation.EmptyDatabaseEndpoint | Database endpoint is empty. | 数据库连接信息为空 | 检查数据库实例是否正常启动,确认连接配置是否正确 | | FailedOperation.DatabaseConnectError | Failed to establish database connection. | 数据库连接失败 | 检查网络连接、数据库实例状态、账号密码是否正确 | | FailedOperation.DatabaseExecSqlError | Execute sql error. | SQL 执行失败,详细错误信息请参考「MySQL 原生错误码」章节 | 查看完整错误信息,根据 MySQL 错误码进行排查 | | FailedOperation.DatabaseSchemaError | Database schema error. | 数据库 schema 元数据信息异常 | 检查表结构定义是否正确,必要时联系客服获取技术支持 | | FailedOperation.DatabaseBuildSqlError | Build sql error. | SQL 构建失败,可能是请求参数错误或元数据信息缺失 | 检查请求参数格式是否正确,必要时联系客服获取技术支持 | | FailedOperation.DatabaseDataProcessError | Process data error. | 数据处理失败 | 检查数据格式是否符合要求,必要时联系客服获取技术支持 | | ResourceNotFound.InstanceNotFound | Database instance not found. | 数据库实例不存在 | 确认实例 ID 是否正确,或前往控制台创建新实例 | | ResourceNotFound.SchemaNotFound | Database schema not found. | 数据库 schema 不存在 | 确认数据库名称是否正确,或前往控制台创建数据库 | | ResourceNotFound.TableNotFound | Table not found. | 数据表不存在 | 确认表名是否正确,或前往控制台创建数据表 | | UnsupportedOperation.TooManyTables | Number of tables exceeds the limit. | 数据表数量超过限制 | 删除不必要的表,或联系客服申请扩容 | | InvalidParameter.ResponseUnacceptableSingleError | Response does not meet the requirement for a single record. | 响应数据不符合单条记录格式要求 | 检查请求头中的 `Accept` 参数设置,确保与查询结果匹配 | ## MySQL 原生错误码 当业务错误码为 `FailedOperation.DatabaseExecSqlError` 时,错误详情中会包含 MySQL 数据库返回的原生错误码。您可以根据错误码范围判断错误类型,并查阅 MySQL 官方文档获取详细说明。 ### 错误码分类规则 MySQL 错误码按照数值范围分为以下几类: #### Server Error(服务器错误) **错误码范围**:1000-1999、3000-5000+ **错误特征**:数据库引擎、查询优化器或存储引擎层面的问题 **典型示例**: ``` Error 1054 (42S22): Unknown column 'username' in 'field list' ``` **解决思路**: - 检查 SQL 语句语法是否正确 - 确认表结构、字段名、索引等是否存在 - 检查数据类型是否匹配 #### Client Error(客户端错误) **错误码范围**:2000-2999 **错误特征**:客户端驱动程序、连接库或网络层面的问题 **典型示例**: ``` Error 2003 (HY000): Can't connect to MySQL server on '192.168.1.100' ``` **解决思路**: - 检查网络连接是否正常 - 确认数据库服务是否启动 - 检查防火墙和安全组配置 #### Global Error(全局错误) **错误码范围**:1-999 **错误特征**:系统级别的通用错误 **典型示例**: ``` Error 13 (HY000): Can't settle log file (OS errno 13 - Permission denied) ``` **解决思路**: - 检查文件系统权限 - 确认磁盘空间是否充足 - 检查系统资源使用情况 #### CynosDB Error(云数据库产品错误) **错误码范围**:9000-9999 **错误特征**:腾讯云 CynosDB 产品特定错误 **典型示例**: ``` Error 9449 (08S01): CynosDB serverless instance is resuming, please try connecting again ``` **错误码文档** [CynosDB 错误码说明](https://cloud.tencent.com/document/product/1003/48104) ### 官方文档参考 根据错误码类型,可查阅以下 MySQL 官方文档: **Server Error(服务器错误)** | MySQL 版本 | 官方文档链接 | | ---------- | ---------------------------------------------------------------------------- | | MySQL 8.0 | [Server Error Reference](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html) | | MySQL 5.7 | [Server Error Reference](https://dev.mysql.com/doc/mysql-errors/5.7/en/server-error-reference.html) | **Client Error(客户端错误)** | MySQL 版本 | 官方文档链接 | | ---------- | ---------------------------------------------------------------------------- | | MySQL 8.0 | [Client Error Reference](https://dev.mysql.com/doc/mysql-errors/8.0/en/client-error-reference.html) | | MySQL 5.7 | [Client Error Reference](https://dev.mysql.com/doc/mysql-errors/5.7/en/client-error-reference.html) | **Global Error(全局错误)** | MySQL 版本 | 官方文档链接 | | ---------- | ---------------------------------------------------------------------------- | | MySQL 8.0 | [Global Error Reference](https://dev.mysql.com/doc/mysql-errors/8.0/en/global-error-reference.html) | | MySQL 5.7 | [Global Error Reference](https://dev.mysql.com/doc/mysql-errors/5.7/en/global-error-reference.html) | :::tip 💡 提示 - 遇到错误时,建议先记录完整的错误信息(包括错误码、SQLSTATE 和错误描述) - 根据错误码范围快速判断问题来源(服务器端 vs 客户端) - 查阅对应版本的官方文档获取详细的错误说明和解决建议 - 如果问题无法自行解决,可以联系 [技术支持](https://cloud.tencent.com/online-service) 获取帮助 ::: --- # MySQL 数据库/其他参考/常见问题 > 当前文档链接: https://docs.cloudbase.net/database/configuration/db/tdsql/faq ## 计量计费相关问题 ### 微信云托管中的 MySQL 数据库与云开发中的 MySQL 数据库有什么差异? **微信云托管环境中的 MySQL 数据库**: - 采用按量计费方式 - 计量包括计算资源使用量及存储量 - 当天产生的用量会在次日凌晨进行结算及扣费 **云开发中的 MySQL 数据库**: - 计量同样包括计算资源使用量及存储量 - 扣费方式按照优先级:套餐 > 资源包 > 超限按量 ## 使用相关问题 ### 访问 MySQL 数据模型响应慢或报错? **问题原因**: MySQL 数据库支持自动暂停功能,30 分钟未被访问会自动暂停以减少计算资源消耗。当重新访问时需要启动服务,可能导致首次请求耗时较长。 **解决方案**: 在控制台关闭自动暂停功能来避免此问题。 **操作步骤**: 1. 进入 [云开发平台/MySQL数据库/数据库设置](https://tcb.cloud.tencent.com/dev?#/db/mysql/setting) 2. 关闭「自动暂停」功能 ### MySQL如何支持事务 云开发 MySQL SDK 目前不支持事务操作。如需使用事务功能,可直接使用原生 MySQL SDK 来实现(MySQL 本身支持事务)。 > 💡 注意:使用原生 MySQL SDK 时,需要自行管理数据库连接和事务状态。 #### 实现示例 ```js const mysql = require('mysql2/promise'); exports.main = async () => { const conn = await mysql.createConnection({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME }); try { await conn.beginTransaction(); // 执行多个相关操作 await conn.query('UPDATE accounts SET balance = balance - 100 WHERE user_id = 1'); await conn.query('UPDATE accounts SET balance = balance + 100 WHERE user_id = 2'); await conn.commit(); return { success: true }; } catch (error) { await conn.rollback(); return { success: false, error: error.message }; } finally { conn.end(); } }; ``` ### 查询条件正确但结果为空 在使用数据库查询时,如果返回空结果,通常有以下两种情况: 1. 没有符合查询条件的数据 2. 数据被权限控制过滤 #### 排查方法 1. **确认数据存在性** - 在云开发控制台直接查看集合中是否存在目标数据 - 检查数据的创建时间和字段值是否符合预期 2. **检查权限配置** - 查看集合的基础权限设置是否允许当前用户读取 - 数据库查询时会以 `_openid` 字段作为数据归属判定依据 - 如果使用安全规则,验证规则表达式是否正确 - 确认查询条件是否包含安全规则要求的必要字段 3. **验证查询条件** - 简化查询条件,逐步排查哪个条件导致结果为空 - 检查字段名称、数据类型和查询语法是否正确 --- # model Documentation > 数据模型是云开发提供的声明式数据管理解决方案,它构建在云开发数据库之上,为开发者提供了一种更高效、更安全的数据操作方式 # 数据模型/概述 > 当前文档链接: https://docs.cloudbase.net/model/introduce ## 什么是数据模型? 数据模型是云开发提供的**声明式数据管理解决方案**,它构建在云开发数据库之上,为开发者提供了一种更高效、更安全的数据操作方式。 简单来说,数据模型就像是为您的数据定制的"智能管家": - **定义数据结构**:通过可视化界面定义数据的字段、类型和关系 - **自动数据校验**:确保数据的准确性和一致性 - **处理关联关系**:自动管理数据之间的复杂关系 - **包含多端 SDK**:一次定义,多端使用(小程序、Web、云函数) - **内置管理界面**:提供开箱即用的数据管理后台 - **AI 智能分析**:利用人工智能挖掘数据价值 > **核心理念**:让开发者专注于业务逻辑,而不是底层数据操作的复杂性。 ## 核心特性一览 | 特性 | 描述 | 价值 | | ------------------ | ---------------------------------------- | -------------------------- | | **智能数据校验** | 自动检查数据类型和格式,防止错误数据入库 | 提高数据质量,减少 Bug | | **关联关系管理** | 自动处理一对一、一对多、多对多关系 | 简化复杂查询,提升开发效率 | | **多端 SDK 支持** | 一次定义,自动生成小程序/Web/云函数 SDK | 统一开发体验,减少重复工作 | | **可视化管理** | 内置 CMS 管理界面,支持非技术人员操作 | 降低运营成本,提升协作效率 | | **低代码应用生成** | 一键生成可定制的管理后台应用 | 快速交付,缩短开发周期 | | **AI 数据分析** | 智能分析数据趋势和模式 | 数据驱动决策,挖掘业务价值 | | **高级查询能力** | 支持复杂条件查询、聚合分析等 | 满足多样化业务需求 | ## 数据模型和数据库的关系 - 数据模型是对数据库的建模,它定义了数据模型的结构、字段、约束和关系。数据模型提供了一种统一的方式来描述和操作数据,简化了数据操作和查询。数据模型能力即为开发中常用的对象关系映射 ORM。 - 数据模型和数据库是关联关系,一个数据模型,对应了数据库中的一个集合(非结构化数据库)或表(结构化数据库)。 - 使用数据模型,不代表被限制为仅能使用数据模型。在使用数据模型的同时,如果需要有更复杂、或数据模型无法很好完成的操作,也可以通过数据库的原生方法,直接操作数据库完成数据读写。 - 相对于传统开发框架中的 ORM,云开发数据模型除 ORM 相关的 SDK 操作数据的能力外,额外整合了数据管理界面、用户及权限体系、内容管理以及进一步的基于数据模型的应用生成、数据分析等更多能力。更多优势点可以查看 "为什么要使用数据模型"。 ## 快速开始 ### 第一步:访问控制台 1. **选择数据库**: - 访问 [云开发平台/文档型数据库/数据模型](https://tcb.cloud.tencent.com/dev?#/db/doc/model/?sourceType=internal_flexdb) - 访问 [云开发平台/MySQL数据库/数据模型](https://tcb.cloud.tencent.com/dev?#/db/mysql/model/?sourceType=internal_mysql) 2. **创建模型**:点击「新建模型」,选择创建方式 ### 第二步:开始使用 新建模型后,您就可以立即使用数据模型提供的各项强大能力了! > **建议先阅读** [快速开始教程](/model/initialization),通过实际案例快速上手。 ## 为什么选择数据模型? 数据模型为开发者带来了**全方位的开发体验提升**: - **提高开发效率**:自动生成 SDK,减少重复代码编写 - **降低维护成本**:统一的数据管理,减少运维工作量 - **增强数据安全**:内置权限控制和数据校验机制 - **强化数据分析**:AI 智能分析,挖掘数据价值 - **简化团队协作**:可视化管理界面,技术与非技术人员都能轻松使用 ### 智能数据校验和类型检查 数据模型提供**自动化的数据校验机制**,在数据入库前进行严格的类型检查和格式验证,确保数据的准确性和一致性,从源头避免数据质量问题。 #### 实际案例演示 假设我们定义了一个文章模型 `post`,包含以下字段: - `title`(字符串类型)- 文章标题 - `body`(字符串类型)- 文章内容 现在我们故意传入错误类型的数据来测试校验机制: ```javascript try { const { data } = await models.post.create({ data: { title: "你好,世界", body: 123456, // ❌ 故意传入数字类型,期望的是字符串类型 }, }); console.log("创建成功:", data); } catch (error) { console.error("校验失败:", error); } ``` #### 校验结果 当我们尝试插入类型错误的数据时,数据模型会立即检测到问题并阻止操作: ```bash Error: WxCloudSDKError: 【错误】数据格式校验失败。根因:[#/body: expected type: String, found: Integer],原因:字段[正文], 标识[body], 类型不匹配:期望类型:字符串。,解决步骤:请按照原因修改数据类型。 errRecord:{"title":"你好,世界","body":123456}【操作】调用 post.create ``` #### 校验优势 - **精确定位**:准确指出哪个字段出现了什么问题 - **友好提示**:提供清晰的错误原因和解决建议 - **数据保护**:防止脏数据进入数据库 - **减少 Bug**:在开发阶段就发现数据类型问题 ### 智能关联关系处理 数据模型能够**自动处理复杂的数据关联关系**,支持一对一、一对多、多对多等各种关系类型。无需手动编写复杂的 JOIN 查询,系统会自动处理数据之间的关联逻辑。 #### 关联查询示例 以文章和评论的关联关系为例,我们可以轻松实现跨模型的数据查询。通过 [`select`](sdk-reference/model#select) 参数,可以精确控制返回的字段和关联数据: ```javascript const { data } = await models.post.list({ // 精确控制返回字段,优化查询性能 select: { _id: true, title: true, updatedAt: true, // 关联查询评论数据 comments: { _id: true, createdAt: true, comment: true, }, }, filter: { where: {}, // 查询条件 }, getCount: true, // 获取总数 }); console.log("查询结果:", data); ``` #### 返回结果 系统会自动处理关联关系,返回结构化的数据: ```json { "records": [ { "_id": "9FSAHWM9VV", "title": "Bonjour le Monde", "updatedAt": 1718096503886, "comments": [ { "_id": "9FSAJF3GLG", "createdAt": 1718096509916, "comment": "这是一条评论" } ] }, { "_id": "9FSAHWM9VU", "title": "你好,世界", "updatedAt": 1718096503886, "comments": [] // 暂无评论 } ], "total": 2 } ``` #### 关联查询优势 - **性能优化**:只返回需要的字段,减少数据传输 - **自动关联**:无需手写复杂的 JOIN 语句 - **多层嵌套**:支持深层次的关联数据查询 - **精确控制**:灵活指定每个关联对象的返回字段 ### 多端 SDK 自动生成 云开发平台提供了**多端对接的 SDK**,支持小程序、Web、云函数等多个平台。一次定义,多端复用,大幅提升开发效率。 #### 智能 Upsert 操作 以 [upsert()](sdk-reference/model#upsert) 方法为例,它能智能判断是创建新记录还是更新现有记录: ```javascript const postData = { title: "Hello World", body: "这是一篇示例文章", _id: "hello-world-post", }; const { data } = await models.post.upsert({ create: postData, // 如果不存在则创建 update: postData, // 如果存在则更新 filter: { where: { _id: { $eq: postData._id } }, }, }); console.log("操作结果:", data); ``` #### 返回结果说明 ```javascript // 创建新记录时 { "count": 0, // 更新的记录数为 0 "id": "hello-world-post" // 新创建记录的 ID } // 更新现有记录时 { "count": 1, // 更新的记录数为 1 "id": "" // 更新操作不返回 ID } ``` #### 多端 SDK 优势 - **统一接口**:所有平台使用相同的 API 调用方式 - **类型安全**:TypeScript 支持,编译时发现错误 - **自动优化**:根据平台特性自动优化性能 - **实时同步**:模型变更后 SDK 自动更新 ### 内置数据管理系统 数据模型提供**开箱即用的内容管理系统(CMS)**,让非技术人员也能轻松进行数据管理和维护,大幅降低运营成本。 #### 核心功能 - **可视化编辑**:直观的表单界面,无需编程知识 - **权限管理**:细粒度的用户权限控制 - **数据统计**:内置数据分析和报表功能 - **高级搜索**:支持多条件筛选和排序 - **响应式设计**:支持桌面和移动端访问 > **了解更多**:查看 [使用数据管理](/toolbox/manage-data) 了解完整功能 ![管理界面](https://cloudcache.tencent-cloud.com/qcloud/ui/static/static_source_business/8cdd0b8d-83b7-496f-a93b-38949ac362d5.png) ### 低代码应用生成器 数据模型支持**一键生成完整的管理后台应用**,采用先进的低代码技术,支持可视化修改和定制开发,让您无需从零编写管理界面。 #### 低代码特性 - **拖拽式设计**:通过拖拽组件快速构建界面 - **模板丰富**:提供多种预设模板和组件 - **深度定制**:支持自定义样式和业务逻辑 - **一键部署**:生成的应用可直接部署使用 - **实时预览**:所见即所得的开发体验 ![低代码应用生成器](https://cloudcache.tencent-cloud.com/qcloud/ui/static/static_source_business/b2b54b47-7b1d-48ec-b294-4e98a27e5d58.png) ### AI 智能数据分析 数据模型集成了**强大的 AI 分析引擎**,能够自动挖掘数据中的模式和趋势,为业务决策提供智能化支持。 #### AI 分析能力 - **趋势预测**:基于历史数据预测未来趋势 - **异常检测**:自动识别数据中的异常模式 - **智能报表**:自动生成可视化分析报告 - **洞察建议**:提供基于数据的业务建议 - **用户画像**:智能分析用户行为特征 > **核心价值**:从海量数据中提取有价值的商业洞察,让数据真正驱动业务增长 ![AI 智能分析界面](https://qcloudimg.tencent-cloud.cn/image/document/22a107ad53ac7ac6d527c6630b46ce7a.png) ### 高级数据库查询支持 数据模型支持**原生 SQL 查询**,兼容 MySQL 等关系型数据库的高级查询功能。当模型 API 无法满足复杂查询需求时,您可以直接使用 SQL 语句进行精确控制。 #### 高级查询能力 - **复杂条件筛选**:支持多表联查、子查询等 - **聚合分析**:GROUP BY、HAVING、聚合函数等 - **性能优化**:索引优化、查询计划分析 - **安全防护**:参数化查询,防止 SQL 注入 #### 实际案例 查询作者联系电话以"1858"开头的记录: ```javascript const result = await models.$runSQL( "SELECT * FROM `article_table` WHERE author_tel LIKE ?;", ["1858%"] // 参数化查询,安全可靠 ); console.log("查询结果:", result); ``` #### 返回结果 ```json { "data": { "total": 1, "executeResultList": [ { "_id": "9JXU7BWFZJ", "title": "示例文章", "author_tel": "18588881111", "createdAt": 1719475245475, "region": "北京市" } ], "backendExecute": "28" }, "requestId": "0d4c98c3-a3ff-4c55-93cc-d0f5c835f82c" } ``` > **深入学习**:查看 [数据库原生查询文档](/model/db-raw-query) 了解更多高级用法 --- ## 总结 云开发数据模型为现代应用开发提供了**全方位的数据管理解决方案**: - **架构优势**:基于云原生架构,稳定可靠 - **开发效率**:自动生成 SDK,减少重复工作 - **数据安全**:内置校验和权限控制机制 - **管理便捷**:可视化管理界面,降低运营成本 - **智能分析**:AI 驱动的数据洞察能力 - **灵活扩展**:支持原生查询和自定义逻辑 **立即开始您的数据模型之旅!** 查看 [快速开始教程](/model/initialization) --- # 数据模型/快速开始 > 当前文档链接: https://docs.cloudbase.net/model/initialization ### 实际案例:博客系统 让我们通过创建一个博客系统的数据模型来演示如何使用这些字段类型: ```json { "title": "我的第一篇博客", // 文章标题 "content": "

这是富文本内容

", // 文章正文内容 "author": "张三", // 作者姓名 "email": "zhangsan@example.com", // 作者邮箱 "published": true, // 是否已发布 "viewCount": 1024, // 浏览次数 "tags": ["技术", "前端", "Vue"], // 文章标签 "metadata": { // 文章元数据 "seo": { "keywords": "Vue, 前端开发", // SEO 关键词 "description": "Vue 前端开发教程" // SEO 描述 } }, "coverImage": "https://example.com/cover.jpg", // 封面图片 "publishedAt": "2024-01-15T10:30:00Z", // 发布时间 "status": "published", // 文章状态 "articleNo": "A000001", // 文章编号 "location": { // 发布地点 "type": "Point", "coordinates": [116.4074, 39.9042] // 经纬度坐标(北京) } } ``` ### 第一步:访问数据模型管理页面 1. **访问控制台**:进入 [云开发平台](https://tcb.cloud.tencent.com/dev) 2. **选择数据库**:切换到 [云开发平台/数据库/文档型](https://tcb.cloud.tencent.com/dev?#/db/doc/model/?sourceType=internal_flexdb) 3. **创建模型**:点击「新建模型」 ### 第二步:设计模型结构 以博客系统为例,我们需要创建两个相关的数据模型: #### 文章模型(article) | 字段名 | 字段类型 | 是否必填 | 描述 | |--------|----------|----------|------| | `title` | 文本 | 是 | 文章标题 | | `content` | 富文本 | 是 | 文章内容 | | `summary` | 文本 | 否 | 文章摘要 | | `author` | 文本 | 是 | 作者姓名 | | `email` | 邮箱 | 是 | 作者邮箱 | | `coverImage` | 图片 | 否 | 封面图片 | | `tags` | 数组 | 否 | 文章标签 | | `published` | 布尔值 | 是 | 是否发布 | | `viewCount` | 数字 | 否 | 浏览次数 | | `publishedAt` | 日期时间 | 否 | 发布时间 | | `status` | 枚举 | 是 | 文章状态(草稿/已发布/已下线) | #### 评论模型(comment) | 字段名 | 字段类型 | 是否必填 | 描述 | |--------|----------|----------|------| | `content` | 文本 | 是 | 评论内容 | | `author` | 文本 | 是 | 评论者姓名 | | `email` | 邮箱 | 是 | 评论者邮箱 | | `website` | 网址 | 否 | 评论者网站 | | `article` | 文本 | 是 | 关联的文章Id | | `approved` | 布尔值 | 是 | 是否审核通过 | ## 使用 SDK 操作数据 ### 初始化 SDK ```javascript // 下载并引入 SDK 文件 const cloudbase = require("@cloudbase/wx-cloud-client-sdk"); // 初始化云开发 wx.cloud.init({ env: "your-env-id", // 替换为您的环境 ID }); // 初始化数据模型客户端 const client = cloudbase.init(wx.cloud); const models = client.models; ``` ```javascript import cloudbase from "@cloudbase/js-sdk" // 初始化应用 const app = cloudbase.init({ env: "your-env-id" // 替换为您的环境 ID }) // 获取数据模型实例 const models = app.models // 获取数据库实例(用于集合操作) const db = app.database() ``` ```javascript const cloudbase = require("@cloudbase/node-sdk") // 初始化应用 const app = cloudbase.init({ env: "your-env-id" // 替换为您的环境 ID }) // 获取数据模型实例 const models = app.models // 获取数据库实例(用于集合操作) const db = app.database() ``` ### 基本数据操作 #### 创建文章 ```javascript // 创建新文章 const { data } = await models.article.create({ data: { title: "Vue 3 入门教程", content: "

Vue 3 简介

Vue 3 是一个现代化的前端框架...

", summary: "本文介绍 Vue 3 的基本概念和使用方法", author: "张三", email: "zhangsan@example.com", tags: ["Vue", "前端", "教程"], published: true, status: "published", publishedAt: new Date().toISOString() } }); console.log("文章创建成功:", data.id); ``` #### 查询文章列表 ```javascript // 查询已发布的文章 const { data } = await models.article.list({ filter: { where: { published: { $eq: true }, status: { $eq: "published" } } }, sort: { publishedAt: 'desc' // 按发布时间倒序 }, pageSize: 10, pageNumber: 1 }); console.log("文章列表:", data.records); console.log("总数:", data.total); ``` #### 创建评论 ```javascript // 为文章创建评论 const { data } = await models.comment.create({ data: { content: "这篇文章写得很好!", author: "李四", email: "lisi@example.com", website: "https://lisi.blog", article: "文章ID", // 关联到具体文章 approved: false // 待审核 } }); console.log("评论创建成功:", data.id); ``` #### 关联查询 ```javascript // 查询文章及其评论 const { data } = await models.article.get({ filter: { where: { _id: { $eq: "文章ID" } } }, select: { $master: true, comments: true } }); console.log("文章详情:", data); console.log("评论列表:", data.comments); ``` ## 下一步 恭喜!您已经成功创建了第一个文档型数据模型。接下来可以: * [学习更多 CRUD 操作](/model/sdk-reference/model) * [掌握关联查询技巧](/model/config/relation) * [使用数据管理](/toolbox/manage-data) * [配置数据安全规则](/model/data-permission) 开始构建您的应用吧! --- # 数据模型/数据操作/字段类型 > 当前文档链接: https://docs.cloudbase.net/model/data-field 数据模型字段是数据源的表结构基础,可以理解为 Excel 表格的表头(列),而数据可以理解为 Excel 表格的行。CloudBase 数据模型支持丰富的字段类型,满足各种业务场景的数据存储需求。 ## 字段类型概览 CloudBase 数据模型提供了 **20+ 种字段类型**,涵盖基础数据、格式化验证、媒体文件、内容编辑、时间位置、特殊功能等各个方面: ## 字段类型完整列表 | 字段类型 | DB字段类型 | 使用说明 | 示例 | |---------|-----------|----------------------------------------------------------------|------| | **文本/单行** | `string` | 适合标题、姓名等短文本(最长 4000 字节) | `"张三"` | | **文本/多行** | `string` | 适合描述、备注等长文本(最长 4000 字节) | `"这是一段描述文字"` | | **布尔值** | `boolean` | true 或 false | `true` | | **数字** | `number` | 支持整数和浮点数 | `123456.78` | | **数组** | `array` | 根据数组元素类型进行校验 | `["技术", "前端", "Vue"]` | | **对象** | `object` | 嵌套的键值对结构 | `{"name": "张三", "age": 25}` | | **JSON** | `object` | 复杂的数据结构或动态属性 | `{"title": "博客", "tags": ["技术"]}` | | **邮箱** | `string` | 包含 xx\@yy.zz 格式验证 | `"email@qq.com"` | | **电话/固定号码** | `string` | 0开头的2-3位区号或7-8位号码 | `"027-1234567"` | | **电话/手机号码** | `string` | 符合手机号规范的11位字符串 | `"13812341234"` | | **网址** | `string` | 符合网址规范的字符串 | `"https://example.com"` | | **图片** | `string` | 默认从前端组件获得图片的 cloudId | `"cloud://xxx.xxx.xxx.png"` | | **多媒体/视频** | `string` | 支持 ogm、wmv、mpg、webm、ogv、mov、asx、mpeg、mp4、m4v、avi | `"cloud://xxx.xxx.xxx/video.mp4"` | | **多媒体/音频** | `string` | 支持 opus、flac、webm、weba、wav、ogg、m4a、oga、mid、mp3、aiff、wma、au | `"cloud://xxx.xxx.xxx/audio.wav"` | | **富文本** | `string` | 支持格式化、链接、图片(最长 262144 字节) | `"

标题

内容

"` | | **Markdown** | `string` | 支持 Markdown 编辑器和实时预览 | `"# 这是一个Markdown示例"` | | **日期时间** | `number` | 默认从前端组件获取的时间戳(ms),底层db文档型数据库、mysql数据库支持相应的日期时间属性,详见日期时间存储格式说明。 | `1645977600000` | | **枚举** | `string` | 所填值必须为设置的枚举值中的某一个 | `"牛奶"` | | **地理位置** | `object` | 固定格式的对象,包含地址和坐标 | `{"geopoint": {"type": "Point", "coordinates": [40.56, 5.89]}, "address": "深圳市南山区"}` | | **文件** | `string` | 默认从前端组件获得文件的 cloudId | `"cloud://xxx.xxx.xxx.pdf"` | | **自动编号** | `string` | 用户不填则后端自动补齐;用户传参则使用客户定义的值 | `"1001"` | | **地区** | `string` | 省级行政区划 | `"陕西省"` | | **关联关系** | `string` | 支持一对一、一对多、多对一 | 存储关联记录的 ID | ### 日期时间存储格式说明
DB类型 日期时间格式 日期时间存储类型 日期时间存储类型说明 日期时间DB存储示例
文档型数据库 日期时间 datetime 使用date类型存储 Tue Sep 23 2025 00:00:00 GMT+0800 (中国标准时间)
number 使用number类型存储 1758560400000
日期 number 只支持number格式 1758556800000
时间 number 只支持number格式 3600000
Mysql数据库 日期时间 datetime 使用datetime类型存储 2025-09-23 00:00:00
timestamp 使用timestamp类型存储 2025-09-23 02:00:00
number 使用bigint类型存储 1758560400000
日期 date 使用date类型存储 2025-09-23
number 使用number类型存储 1758556800000
时间 time 使用time类型存储 01:00:00
number 使用number类型存储 3600000
### 自动编号格式说明 自动编号支持三种格式类型: 1. **字符串前缀**:`{前缀}-{SEQNUM:最小位数+起始值}` - 示例:`CAR-1000, CAR-1001, CAR-1002` 2. **日期前缀**:`{DATETIMEUTC:日期格式}-{SEQNUM:最小位数+起始值}` - 示例:`2024-01-15-0001, 2024-01-15-0002` 3. **自定义格式**:支持多种格式组合 - `{SEQNUM:4}` - 连续数字:`0001, 0002` - `{DATETIMEUTC:yyyy-MM-dd}` - 时间日期:`2024-01-15` - `{RANDSTRING:4}` - 随机字符串:`AB7L` ## 字段通用配置 ### 基础配置项 | 配置项 | 说明 | 规则 | |--------|------|------| | **字段名称** | 字段的显示名称 | 支持中英文 | | **字段标识** | 字段的唯一标识符 | 不能为空,不能以数字开头,只能包含字母、数字或_ | | **数据类型** | 字段的数据类型 | 从支持的字段类型中选择 | | **是否必填** | 该字段是否为必填项 | 影响数据校验 | | **是否唯一** | 该字段的取值是否允许重复 | 设置为唯一则不允许填重复值 | ### 主展示列配置 **适用条件**:数据类型为**文本**时可选择 **功能说明**:当其他数据模型配置关联关系指向本模型时,该字段会作为主展示列显示,方便用户查看 **配置影响**: - ✅ **影响展示效果**:在关联选择时显示该字段的值 - ❌ **不影响存储**:实际存储的仍然是关联记录的 ID #### 主展示列最佳实践 - 选择具有业务意义的字段作为主展示列(如"产品名称"而非"产品ID") - 保持主展示列值的唯一性和可读性,建议设置为唯一字段 - 避免使用过长的文本作为主展示列 ### 主展示列使用示例 以学生和课程模型为例: **学生模型配置**: ```json { "name": "张三", "course": "93e4b6a0640e9139042f27941b0ab7e6" // 存储课程ID } ``` **课程模型配置**: - 课程名称字段设置为主展示列 **查询结果**: ```json { "records": [{ "name": "张三", "course": "93e4b6a0640e9139042f27941b0ab7e6", "@course": { "v1": { "primaryColumn": "courseName", // 主展示列标识 "record": { "courseName": "语文", // 主展示列的值 "_id": "93e4b6a0640e9139042f27941b0ab7e6" } } } }] } ``` **表单组件中的效果**: - 下拉选择显示:`语文`(主展示列值) - 实际提交数据:`93e4b6a0640e9139042f27941b0ab7e6`(ID值) --- # 数据模型/数据操作/增删改查 > 当前文档链接: https://docs.cloudbase.net/model/sdk-reference/model 云开发提供了多种 SDK 供开发者进行操作数据模型,包括小程序 SDK、JS SDK、Node SDK、HTTP API 等 | SDK 类型 | 适用平台 | | --------------------------------------------------------------------------------------------------------------------- | ------------ | | [小程序 ClientSDK](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/guide/model/init-sdk.html) | 小程序 | | [JS SDK](/api-reference/webv3/model/fetch) | Web 浏览器 | | [Node SDK](/api-reference/server/node-sdk/model/fetch) | Node.js 环境 | | [HTTP API](/http-api/model/get-item-by-id) | 通用 | --- # 数据模型/高级功能/关联关系详解 > 当前文档链接: https://docs.cloudbase.net/model/config/relation 关联关系是指数据模型之间的连接关系,通过关联字段将不同模型的数据关联起来,实现数据之间的逻辑连接。 新建关联关系时,系统会在两个数据模型中分别建立关联字段,用于存储关联数据的 `_id`。 > ⚠️ 注意:MySQL 数据模型中的关联关系字段是通过中间表进行关联的,因此不能直接通过 SQL 的 JOIN 语句进行查询。需要使用数据模型提供的关联查询方法来获取关联数据。 **常见的关联关系场景:** - **学生属于某个班级**(学生 → 班级) - 学生模型关联字段:所属班级 - 班级模型关联字段:学生列表 - **文章有多个评论**(文章 → 评论) - 文章模型关联字段:评论列表 - 评论模型关联字段:所属文章 - **用户有一个个人资料**(用户 → 个人资料) - 用户模型关联字段:个人资料 - 个人资料模型关联字段:所属用户 ## 支持的数据库类型 | 数据库类型 | 支持的关联关系 | | ---------------- | ------------------------------ | | 数据库(文档型) | 一对一、一对多、多对一 | | 数据库(MySQL) | 一对一、一对多、多对一、多对多 | | 自有 MySQL 数据库 | 一对一、一对多、多对一、多对多 | ## 关联关系类型 ### 一对一(1:1) 一个记录只能关联另一个模型的一个记录,双方都是唯一对应关系。 **示例:** 用户 ↔ 个人资料 ```javascript // 用户模型 { name: "张三", profile: { _id: "profile_123" } } // 个人资料模型 { _id: "profile_123", avatar: "avatar.jpg", bio: "个人简介" } ``` ### 一对多(1:N) 一个记录可以关联另一个模型的多个记录。 **示例:** 班级 ↔ 学生 ```javascript // 班级模型 { name: "一年级1班", students: [ { _id: "student_1" }, { _id: "student_2" } ] } ``` ### 多对一(N:1) 多个记录关联另一个模型的一个记录。 **示例:** 学生 ↔ 班级 ```javascript // 学生模型 { name: "小明", class: { _id: "class_123" } } ``` ### 多对多(M:N) 多个记录可以关联另一个模型的多个记录。 **示例:** 学生 ↔ 课程 ```javascript // 学生模型 { name: "小明", courses: [ { _id: "course_1" }, // 语文 { _id: "course_2" } // 数学 ] } ``` ## 关联关系操作 在使用关联关系时,需要注意数据格式和错误处理。无论是在客户端还是服务端(云函数)环境中,操作方式基本一致。 > 💡 注意:所有关联字段操作都需要使用 `{_id: "xxx"}` 格式,其中 `xxx` 为关联数据的 `_id`,无需传入其他字段。 ### 查询操作 > 💡 注意:对于 MySQL 数据库,关联查询必须使用数据模型提供的查询方法,不支持直接使用 SQL JOIN 语句。 > ⚠️ **重要限制**:关联查询目前只支持 **一层关联**,不支持多层嵌套的关联查询。 #### 关联查询层级限制说明 **支持的查询(一层关联)**: ```javascript // ✅ 正确:查询文章及其关联的作者信息(一层关联) const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { _id: true, title: true, author: { // 关联字段:一层 _id: true, name: true, email: true, profile: true // 非关联字段,可以查询 } } }); ``` **不支持的查询(多层关联)**: ```javascript // ❌ 错误:多层嵌套关联查询不生效 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, author: { // 第一层关联 name: true, profile: { // ❌ 第二层关联:不支持 address: { // ❌ 第三层关联:不支持 city: true } } } } }); // 查询结果中 profile.address 将不会返回数据 ``` **区分关联字段和普通字段**: ```javascript // ✅ 正确:在关联字段中查询普通字段(非关联字段) const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, author: { // 关联字段(一层) name: true, email: true, avatar: true, // 普通字段 bio: true // 普通字段 // 以上都是 author 模型的普通字段,不是关联字段 } } }); ``` **多层关联的替代方案**: 如果需要查询多层关联数据,需要分步查询: ```javascript // 方案1:分步查询 // 第一步:查询文章和作者 const post = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, author: { _id: true, name: true, profileId: true // 获取 profile 的 ID } } }); // 第二步:根据 profileId 查询 profile 详情 if (post.data.author.profileId) { const profile = await models.profile.get({ filter: { where: { _id: { $eq: post.data.author.profileId } } }, select: { address: { city: true, street: true } } }); // 手动组合数据 post.data.author.profile = profile.data; } ``` ```javascript // 方案2:数据冗余 // 在 author 模型中冗余常用的 profile 信息 { _id: "author_123", name: "张三", email: "zhang@example.com", profileId: "profile_456", // 关联字段 city: "北京", // 冗余字段:来自 profile.address.city bio: "个人简介" // 冗余字段:来自 profile.bio } // 这样可以在一层查询中获取所需信息 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, author: { name: true, city: true, // 冗余的 city 字段 bio: true // 冗余的 bio 字段 } } }); ``` #### 查询结果包含关联数据 ```javascript // 查询文章及其评论(一层关联) const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { _id: true, title: true, content: true, // 包含关联的评论数据(一层关联) comments: { _id: true, content: true, createdAt: true } } }); ``` #### 根据关联条件过滤 ```javascript // 查询有评论的文章 const { data } = await models.post.list({ filter: { relateWhere: { comments: { where: { content: { $nempty: true } } } } }, select: { _id: true, title: true, comments: { content: true } } }); ``` ### 创建操作 创建记录时可以同时建立关联关系: ```javascript // 创建学生并关联班级和课程 const { data } = await models.student.create({ data: { name: "小明", age: 8, // 关联班级(多对一) class: { _id: "class_123" }, // 关联多个课程(多对多) courses: [ { _id: "course_1" }, { _id: "course_2" } ] } }); ``` ### 更新操作 #### 更新一对一关联 ```javascript // 客户端/云函数:更新用户的个人资料关联 const { data } = await models.user.update({ filter: { where: { _id: { $eq: "user_123" } } }, data: { profile: { _id: "profile_456" } } }); ``` ```javascript // 云函数中使用 doc() 方法的写法 exports.main = async (event) => { const { userId, profileId } = event; try { const result = await cloudbase.model('user').doc(userId).update({ profile: { _id: profileId } }); return { success: true, result }; } catch (error) { if (error.code === 'INVALID_RELATION_FORMAT') { return { success: false, message: '关联字段格式错误,请使用 {_id: "xxx"} 格式' }; } throw error; } }; ``` #### 更新一对多关联 ```javascript // 客户端/云函数:更新班级的学生列表 const { data } = await models.class.update({ filter: { where: { _id: { $eq: "class_123" } } }, data: { students: [ { _id: "student_1" }, { _id: "student_2" }, { _id: "student_3" } ] } }); ``` ```javascript // 云函数示例:从参数批量处理 exports.main = async (event) => { const { classId, studentIds } = event; try { const result = await cloudbase.model('class').doc(classId).update({ students: studentIds.map(id => ({ _id: id })) }); return { success: true, result }; } catch (error) { if (error.code === 'INVALID_RELATION_FORMAT') { return { success: false, message: '关联字段格式错误,数组中每项需要使用 {_id: "xxx"} 格式' }; } throw error; } }; ``` #### 更新多对多关联 ```javascript // 客户端/云函数:更新学生选课 const { data } = await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { courses: [ { _id: "course_1" }, { _id: "course_2" } ] } }); ``` ```javascript // 云函数示例:更新学生选课 exports.main = async (event) => { const { studentId, courseIds } = event; try { const result = await cloudbase.model('student').doc(studentId).update({ courses: courseIds.map(id => ({ _id: id })) }); return { success: true, result }; } catch (error) { if (error.code === 'INVALID_RELATION_FORMAT') { return { success: false, message: '关联字段格式错误,请使用 {_id: "xxx"} 格式的数组' }; } throw error; } }; ``` #### 添加关联数据 向已有的关联列表中添加新的关联记录: ```javascript // 向已有学生添加新课程 // 第一步:查询当前课程列表 const student = await models.student.get({ filter: { where: { _id: { $eq: "student_123" } } }, select: { courses: true } }); const currentCourses = student.data.courses || []; // 第二步:检查是否已存在,避免重复 const courseExists = currentCourses.some(c => c._id === "course_3"); if (!courseExists) { // 第三步:更新课程列表 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { courses: [ ...currentCourses, { _id: "course_3" } ] } }); } ``` ```javascript // 云函数封装示例 exports.main = async (event) => { const { studentId, newCourseId } = event; try { // 先查询当前课程列表 const student = await cloudbase.model('student').doc(studentId).get(); const currentCourses = student.data.courses || []; // 添加新课程(避免重复) const courseExists = currentCourses.some(c => c._id === newCourseId); if (courseExists) { return { success: false, message: '课程已存在' }; } // 更新课程列表 const result = await cloudbase.model('student').doc(studentId).update({ courses: [ ...currentCourses, { _id: newCourseId } ] }); return { success: true, result }; } catch (error) { console.error('添加课程失败', error); throw error; } }; ``` #### 移除关联数据 从关联列表中移除指定的关联记录: ```javascript // 移除学生的某门课程 // 第一步:查询当前课程列表 const student = await models.student.get({ filter: { where: { _id: { $eq: "student_123" } } }, select: { courses: true } }); const currentCourses = student.data.courses || []; // 第二步:过滤掉要移除的课程 const updatedCourses = currentCourses.filter(c => c._id !== "course_2"); // 第三步:更新课程列表 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { courses: updatedCourses } }); ``` ```javascript // 云函数封装示例 exports.main = async (event) => { const { studentId, courseId } = event; try { // 先查询当前课程列表 const student = await cloudbase.model('student').doc(studentId).get(); const currentCourses = student.data.courses || []; // 过滤掉要移除的课程 const updatedCourses = currentCourses.filter(c => c._id !== courseId); // 更新课程列表 const result = await cloudbase.model('student').doc(studentId).update({ courses: updatedCourses }); return { success: true, result }; } catch (error) { console.error('移除课程失败', error); throw error; } }; ``` ### 删除操作 配置关联关系时,可以设置不同的删除行为: - **删除关联模型数据**:删除主记录时,同时删除关联的记录 - **不删除关联模型数据**:仅删除主记录,保留关联记录 - **禁止删除存在关联关系的数据**:如果存在关联记录,则禁止删除主记录 ## 关联字段限制说明 ### 字段长度限制 > ⚠️ 注意:关联字段存储的是 `_id` 值,存在以下长度限制: | 限制项 | 最大长度 | 说明 | |--------|---------|------| | 单个 `_id` | 256 字节 | 单个关联记录的 ID 长度限制 | | 一对多/多对多数组 | 1024 字节 | 存储多个 `_id` 的数组总长度限制 | | 图片 CloudID | 100-200+ 字节 | 云存储图片 ID 可能较长,需特别注意 | ### 多对多关联数量限制 对于多对多关系,由于总长度限制为 1024 字节,关联记录数量受到限制: | 单个 `_id` 长度 | 最大关联数量 | 说明 | |----------------|-------------|------| | 20 字节 | 约 50 个 | MongoDB ObjectId 标准长度 | | 36 字节 | 约 28 个 | UUID 格式 | | 10 字节 | 约 100 个 | 自定义短 ID | **建议**: - 对于需要大量关联的场景(如用户收藏的商品数量可能超过 100 个),建议使用独立的关联表 - 使用自定义短 ID 可以增加关联数量 - 评估业务场景的实际需求,选择合适的方案 ## 多对多中间表操作 对于 MySQL 数据模型的多对多关系,系统会自动创建中间表。虽然不支持直接 SQL JOIN,但可以通过以下方式操作中间表。 ### 查找中间表名称 **示例**:学生(student)和课程(course)的多对多关系 - 中间表结构: ``` course_student ├── course_id (外键) ├── student_id (外键) └── created_at (创建时间) ``` ### 查询中间表数据 虽然不支持直接 JOIN,但可以通过数据模型的关联查询获取关联数据: ```javascript // 查询学生及其选修的所有课程 const { data } = await models.student.get({ filter: { where: { _id: { $eq: "student_123" } } }, select: { _id: true, name: true, courses: { _id: true, courseName: true, credits: true } } }); // 结果包含完整的关联数据 console.log(data.courses); // [ // { _id: "course_1", courseName: "数学", credits: 4 }, // { _id: "course_2", courseName: "英语", credits: 3 } // ] ``` ### 反向查询 ```javascript // 查询课程及选修该课程的所有学生 const { data } = await models.course.get({ filter: { where: { _id: { $eq: "course_1" } } }, select: { _id: true, courseName: true, students: { _id: true, name: true, age: true } } }); console.log(data.students); // [ // { _id: "student_1", name: "张三", age: 20 }, // { _id: "student_2", name: "李四", age: 21 } // ] ``` ### 使用关联条件查询 ```javascript // 查询选修了"数学"课程的所有学生 const { data } = await models.student.list({ filter: { relateWhere: { courses: { where: { courseName: { $eq: "数学" } } } } }, select: { _id: true, name: true, courses: { courseName: true } } }); ``` ## 故障排查 ### 关联字段更新失败 #### 错误1:格式错误 **错误信息**:`Invalid relation field format` 或 `关联字段格式不正确` **原因**:未使用 `{_id: "xxx"}` 格式 **错误示例**: ```javascript // ❌ 错误:直接传字符串 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { class: "class_456" } // 错误格式 }); // ❌ 错误:传入完整对象 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { class: { _id: "class_456", name: "一年级1班" // 不需要传入其他字段 } } }); ``` **正确示例**: ```javascript // ✅ 正确:使用 {_id: "xxx"} 格式 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { class: { _id: "class_456" } } }); ``` #### 错误2:数组格式错误 **错误信息**:`Expected array of objects with _id field` **原因**:一对多或多对多关系中,数组格式不正确 **错误示例**: ```javascript // ❌ 错误:直接传字符串数组 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { courses: ["course_1", "course_2"] // 错误格式 } }); ``` **正确示例**: ```javascript // ✅ 正确:数组中每项使用 {_id: "xxx"} 格式 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { courses: [ { _id: "course_1" }, { _id: "course_2" } ] } }); ``` #### 错误3:关联数据不存在 **错误信息**:`Related record not found` 或 `关联记录不存在` **原因**:关联的 `_id` 在目标模型中不存在 **排查步骤**: 1. 检查关联的 `_id` 是否正确 2. 在目标模型中查询该记录是否存在 3. 确认 `_id` 的拼写和格式是否正确 **解决方法**: ```javascript // 先验证关联记录是否存在 const classExists = await models.class.get({ filter: { where: { _id: { $eq: "class_456" } } } }); if (classExists.data) { // 记录存在,可以安全关联 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { class: { _id: "class_456" } } }); } else { console.error("关联的班级不存在"); } ``` #### 错误4:字段长度超出限制 **错误信息**:`Relation field length exceeds limit` 或 `字段长度超出限制` **原因**: - 单个 `_id` 超过 256 字节 - 一对多/多对多数组总长度超过 1024 字节 **解决方法**:参考上文「关联字段限制说明」部分的解决方案 ### 关联查询结果为空 #### 原因1:权限配置问题 关联模型的权限配置独立于主模型,需要分别设置。 **排查步骤**: 1. 检查主模型的权限配置 2. 检查关联模型的权限配置 3. 确认当前用户是否有权限访问关联数据 **示例**: ```javascript // 查询文章及评论,但评论权限不足时,评论数据为空 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, comments: { // 如果评论模型权限不足,此处返回空数组 content: true } } }); ``` **解决方法**: - 在控制台检查关联模型的权限设置 - 确保当前用户有读取关联模型数据的权限 - 或在云函数中使用管理员权限进行查询 #### 原因2:关联数据确实不存在 **排查步骤**: 1. 在控制台直接查看主记录的关联字段值 2. 检查关联字段是否为空或包含无效的 `_id` 3. 在关联模型中查询对应的记录是否存在 **解决方法**: ```javascript // 先查询主记录,检查关联字段 const post = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { comments: true } }); console.log('关联的评论ID:', post.data.comments); // 检查是否为空数组或包含无效ID // 验证关联记录是否存在 const commentIds = post.data.comments.map(c => c._id); const comments = await models.comment.list({ filter: { where: { _id: { $in: commentIds } } } }); console.log('实际存在的评论:', comments.data); ``` #### 原因3:select 字段配置错误 **错误示例**: ```javascript // ❌ 错误:未在 select 中包含关联字段 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, content: true // 缺少 comments 字段,不会返回关联数据 } }); ``` **正确示例**: ```javascript // ✅ 正确:在 select 中明确指定关联字段 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, content: true, comments: { // 必须明确指定关联字段 _id: true, content: true } } }); ``` #### 原因4:多层关联查询不支持 关联查询目前只支持一层关联,多层嵌套关联不会返回数据。 **错误示例**: ```javascript // ❌ 错误:多层关联查询不返回数据 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, author: { // 第一层关联 ✅ name: true, profile: { // ❌ 第二层关联:不支持,不会返回数据 address: { // ❌ 第三层关联:不支持 city: true } } } } }); // 查询结果: // { // title: "文章标题", // author: { // name: "张三" // // profile 字段不会返回 // } // } ``` **正确示例**: ```javascript // ✅ 正确:只查询一层关联 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, author: { // 一层关联 ✅ _id: true, name: true, email: true, bio: true // 普通字段可以正常查询 } } }); ``` **解决方法**:参考上文「关联查询层级限制说明」中的分步查询或数据冗余方案 ### 多对多关系操作异常 #### 问题:无法找到中间表 **原因**:对于 MySQL 数据模型,中间表由系统自动创建和管理 **解决方法**: - 不需要手动创建中间表 - 使用数据模型提供的关联查询方法 - 不要尝试直接通过 SQL JOIN 查询中间表 #### 问题:中间表数据不一致 **原因**:直接修改了中间表数据,导致关联关系不一致 **解决方法**: - **始终通过数据模型的更新方法修改关联关系** - 不要直接操作中间表数据 - 如果数据不一致,通过数据模型的更新方法重新设置关联关系 ```javascript // ✅ 正确:通过数据模型更新关联关系 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { courses: [ { _id: "course_1" }, { _id: "course_2" } ] } }); // ❌ 错误:不要直接操作中间表 // await db.query("INSERT INTO course_student ..."); // 不推荐 ``` --- # 数据模型/高级功能/数据权限管理 > 当前文档链接: https://docs.cloudbase.net/model/data-permission CloudBase 提供了多层次的数据权限管理机制,确保数据安全的同时满足不同业务场景的权限控制需求。 数据模型进行读写时会以 `_openid` 字段作为数据归属判定依据 ## 权限管理体系 CloudBase 数据权限管理包含两个层次: | 权限类型 | 控制粒度 | 适用场景 | 配置复杂度 | | ---------------- | -------- | -------------- | ---------- | | **基础权限控制** | 模型级别 | 简单的权限需求 | 低 | | **角色权限** | 用户级别 | 组织架构权限 | 中 | > 💡 注意: 数据模型不支持安全规则权限配置 ### 权限优先级 不同权限类型之间的关系: - **角色权限** 和 **基础权限** 取 **并集** 为最终权限 - 建议根据业务复杂度选择合适的权限管理方式 ## 基础权限控制 ### 功能特点 基础权限控制是最简单的权限管理方式,适合大多数常见的业务场景: - **模型级别控制**:针对整个数据模型设置统一权限 - **预设权限模板**:提供常用的权限配置模板 - **简单易用**:无需编写复杂的规则表达式 ### 配置方式 在 [云开发平台](https://tcb.cloud.tencent.com/dev) 的数据模型页面,为每个模型设置对应的权限: ![基础权限配置](https://qcloudimg.tencent-cloud.cn/raw/1f732163a324fb3bd90d6ea917ecbaaf.png) ### 权限选项 从用户的身份出发,去选择对应的权限 - 所有用户包含匿名用户、外部用户、内部用户。 - 一个匿名用户的实际权限是所有用户和匿名用户的权限最大集;外部用户和内部用户也是同理。 - **最佳实践一**:只通过所有用户来管理权限;设置匿名用户、外部用户、内部用户的权限为无权限。 - **最佳实践二**:删除"所有用户"的规则,通过细分角色来管理权限。 | 权限类型 | 适用场景 | | ------------------------------ | ---------------------- | | **读取全部数据,修改本人数据** | 公开内容,如文章、商品 | | **读取和修改本人数据** | 私人数据,如用户资料 | | **读取全部数据,不可修改数据** | 配置数据,如系统设置 | | **无权限** | 敏感数据,如财务信息 | ## 自定义角色权限控制 ### 功能概述 角色权限是基于组织架构的权限管理方式,适合企业级应用中的层级权限控制。它与基础权限控制相互补充,最终权限为两者的**并集**。 **核心特点**: - **组织架构支持**:基于部门、上下级关系的权限控制 - **角色继承**:支持权限的层级继承 - **灵活组合**:与基础权限取并集,提供更灵活的权限配置 ### 配置步骤 #### 第一步:进入角色管理 访问 **[云开发平台/自定义角色页面](https://tcb.cloud.tencent.com/cloud-admin?#/settings/roleManage)**,管理组织架构和角色定义: ![角色管理入口](https://qcloudimg.tencent-cloud.cn/raw/680553d87ff54c445070a5637aeae1c2.png) #### 第二步:配置行权限 选择目标角色,点击「**行权限设置**」进行详细配置: ![行权限设置](https://qcloudimg.tencent-cloud.cn/raw/4357984844dfe01b460a3178439c6015.png) ### 权限级别说明 | 权限级别 | 数据范围 | 适用场景 | 示例 | | ---------------------- | ------------------------ | ------------ | ------------------------------ | | **查看本人** | 所有人字段值为自己的数据 | 个人数据管理 | 员工只能查看自己的考勤记录 | | **查看本人及下属** | 自己和下属的数据 | 团队管理 | 主管可以查看团队成员的工作报告 | | **查看本部门及子部门** | 本部门及子部门的数据 | 部门管理 | 部门经理查看部门内所有项目 | | **查看全部** | 所有数据 | 系统管理 | 管理员查看全公司数据 | ### 权限组合规则 #### 读写权限关系 > ⚠️ 注意:行修改权限自动包含读权限,即有修改权限就有读权限。 #### 权限并集计算 **基础权限** + **角色权限** = **最终权限** **示例场景**: ``` 基础权限:仅创建者及管理员可读写 角色权限:查看全部 + 无修改权限 ───────────────────────────────── 最终权限:可以查看所有数据,但只能修改自己创建的数据 ``` ### 实际应用案例 #### 案例 1:博客权限 **业务需求**: - 可以查看所有人的博客 - 自己更新自己的博客 **基础权限配置** 所有用户/读取全部数据,修改本人数据 #### 案例 2:项目管理系统 **业务需求**: - 项目成员只能查看参与的项目 - 项目经理可以管理负责的项目 - 部门经理可以查看部门所有项目 **基础权限配置** 所有用户/读取和修改本人数据 **角色权限配置** | 角色 | 角色数据权限 | 行修改权限 | 最终效果 | | ------------ | -------------- | ---------- | ------------------------ | | **销售员** | 查看本人 | 本人 | 只能查看和修改自己的客户 | | **销售主管** | 查看本人及下属 | 本人及下属 | 可以管理团队所有客户 | | **销售总监** | 查看全部 | 查看全部 | 可以管理所有客户 | ### 权限设计最佳实践 #### 1. 权限设计原则 - **最小权限原则**:只授予完成工作所需的最小权限 - **职责分离**:不同角色承担不同的数据责任 - **权限继承**:合理利用组织架构的层级关系 #### 2. 常见配置模式 **只读扩展模式**: ``` 基础权限:仅创建者可读写 角色权限:扩大查看范围,不给修改权限 效果:扩大数据可见性,保持修改控制 ``` **管理员模式**: ``` 基础权限:仅创建者可读写 角色权限:查看全部 + 修改全部 效果:管理员可以管理所有数据 ``` **部门隔离模式**: ``` 基础权限:仅创建者可读写 角色权限:查看本部门 + 修改本部门 效果:部门间数据隔离,部门内共享 ``` #### 3. 注意事项 - **权限测试**:在生产环境前充分测试各种角色的权限 - **权限审计**:定期检查和调整权限配置 - **文档记录**:详细记录权限设计的业务逻辑 - **变更管理**:权限变更要有审批流程 --- # 数据模型/高级功能/索引管理 > 当前文档链接: https://docs.cloudbase.net/model/data-index 索引是提升数据库查询性能的关键技术。通过为常用查询字段建立索引,可以显著提升查询速度和用户体验。 ## 管理入口 1. **访问控制台**:进入 [云开发平台](https://tcb.cloud.tencent.com/dev) 2. **选择数据库**:切换到 [云开发平台/数据库/文档型](https://tcb.cloud.tencent.com/dev?#/db/doc/model/?sourceType=internal_flexdb) 3. **进入索引管理**:选择目标集合,点击「索引管理」标签页 4. **管理索引**:新建、删除或修改索引配置 ![](https://qcloudimg.tencent-cloud.cn/raw/cd2458f2736dbf9010cc497dddee1524.png) ## 索引类型详解 ### 单字段索引 **适用场景**:针对单个字段的查询和排序操作 **特点说明**: * 支持嵌套字段索引,使用"点表示法"访问 * 可指定升序或降序排序 * 适合简单的单条件查询 **示例配置**: ```json // 原始数据结构 { "_id": "product_001", "name": "iPhone 15", "price": 5999, "style": { "color": "深空黑", "storage": "128GB" } } ``` **索引配置示例**: | 字段路径 | 索引类型 | 排序方式 | 适用查询 | |---------|----------|----------|----------| | `name` | 单字段 | 升序 | 按商品名称查询 | | `price` | 单字段 | 降序 | 按价格排序 | | `style.color` | 单字段 | 升序 | 按颜色筛选 | > 💡 注意:嵌套字段使用点表示法,如 `style.color` 表示访问 style 对象中的 color 字段。 ### 组合索引 **适用场景**:多字段联合查询和复杂排序操作 **核心概念**:一个索引包含多个字段,支持前缀匹配查询 **示例数据**: ```json { "_id": "student_001", "name": "张三", "age": 20, "score": 85, "class": "计算机科学" } ``` **索引前缀规则**: 假设创建组合索引: `name + age + score` | 索引前缀 | 能命中的查询组合 | 查询示例 | |---------|-----------------|----------| | `name` | 单独查询 name | `db.collection('students').where({name: '张三'})` | | `name, age` | 查询 name + age | `db.collection('students').where({name: '张三', age: 20})` | | `name, age, score` | 查询全部字段 | `db.collection('students').where({name: '张三', age: 20, score: 85})` | :::tip 💡 前缀匹配原理 组合索引 `(name, age, score)` 的前缀包含: * ✅ `name` - 可以命中索引 * ✅ `name, age` - 可以命中索引 * ✅ `name, age, score` - 可以命中索引 * ❌ `age` - 无法命中索引(不是前缀) * ❌ `score` - 无法命中索引(不是前缀) * ❌ `age, score` - 无法命中索引(不是前缀) ::: **组合索引的重要特性**: ### 🔄 1. 字段顺序的重要性 索引字段的顺序直接影响查询性能: | 索引定义 | 能命中的查询 | 无法命中的查询 | |---------|-------------|---------------| | `(name, age)` | ✅ `name` 查询
✅ `name + age` 查询 | ❌ 单独 `age` 查询
❌ `age + score` 查询 | | `(age, name)` | ✅ `age` 查询
✅ `age + name` 查询 | ❌ 单独 `name` 查询
❌ `name + score` 查询 | ### 📊 2. 排序方向的影响 排序查询时,索引的排序方向会影响是否能命中索引: **索引配置: `age: 升序, score: 降序` ** | 查询排序方式 | 是否命中索引 | 说明 | |-------------|-------------|------| | `age: 升序, score: 降序` | ✅ 命中 | 与索引方向完全一致 | | `age: 降序, score: 升序` | ✅ 命中 | 索引可反向使用 | | `age: 升序, score: 升序` | ❌ 未命中 | 排序方向不一致 | | `age: 降序, score: 降序` | ❌ 未命中 | 排序方向不一致 | | `score: 任意, age: 任意` | ❌ 未命中 | 不符合前缀规则 | **索引配置: `age: 升序, score: 升序` ** | 查询排序方式 | 是否命中索引 | 说明 | |-------------|-------------|------| | `age: 升序, score: 升序` | ✅ 命中 | 与索引方向完全一致 | | `age: 降序, score: 降序` | ✅ 命中 | 索引可反向使用 | | `age: 升序, score: 降序` | ❌ 未命中 | 排序方向不一致 | | `age: 降序, score: 升序` | ❌ 未命中 | 排序方向不一致 | ### 地理位置索引 **适用场景**:地理位置查询、附近搜索、区域筛选 **功能特点**: * 支持平面几何的地理位置索引 * 必须为地理位置字段建立专门的地理位置索引 * 支持点、线、面等几何图形 **数据结构示例**: ```json { "_id": "store_001", "name": "星巴克咖啡店", "location": { "type": "Point", "coordinates": [116.4074, 39.9042] // [经度, 纬度] }, "address": "北京市朝阳区" } ``` **索引创建**: 1. 在控制台选择集合 2. 进入索引管理页面 3. 选择地理位置字段(如 `location`) 4. 设置索引类型为"地理位置索引" **查询示例**: ```javascript // 查询附近 1000 米内的店铺 db.collection('stores').where({ location: db.command.geoNear({ geometry: db.Geo.Point(116.4074, 39.9042), maxDistance: 1000 }) }).get() ``` ![地理位置索引配置](https://main.qcloudimg.com/raw/f36c7f4df1cf0e929a3a0666f4677c46.png) ## 索引使用注意事项 ### 唯一性限制 创建索引时,索引属性选择**唯一**,即可添加唯一性限制。此限制会要求集合中**索引字段对应的值不能重复**。 例如,某个集合内建立了索引字段 `foo` ,且属性为“唯一”,那么在这个集合内,要求不能存在 `foo` 字段相同的文档。 :::tip 提示 需特别注意的是,假如**记录中不存在某个字段,则对索引字段来说其值默认为 null**。如果索引有唯一性限制,则不允许存在两个或以上的该字段为空 / 不存在该字段的记录。 ::: ### 大小限制 * 索引的字段大小限制**不能超过 1024 字节**。 * 添加索引时,如果集合中已有文档索引字段超过 1024 字节,添加索引时将报错。 * 已设置索引的字段,如果插入一个文档,文档中该字段超过 1024 字节将会报错。 :::tip 提示 每个英文字母(不分大小写)占一字节的空间,每个中文汉字占两字节的空间。 ::: ### 正则表达式 正则查询无法使用索引提升性能。 --- # 数据模型/高级功能/事件处理 > 当前文档链接: https://docs.cloudbase.net/model/event-handler 数据模型的「事件处理」功能相当于数据库触发器(Trigger)能力,可以基于数据的增删改查操作,自动触发审批流程,实现业务流程的自动化和规范化管理。 :::tip 审批流 ≠ 只能做审批 虽然名为"审批流",但它实际上是一个**完整的业务流程编排平台**,可以: - ✅ **审批场景**:订单审批、员工入职审批等需要人工决策的场景 - ✅ **自动化场景**:字段自动拼接、数据联动更新、自动发送通知等无需人工干预的场景 - ✅ **混合场景**:先自动处理数据,再提交审批 **本质**:事件处理 + 审批流 = 数据库触发器 + 工作流引擎 ::: ### 核心特性 | 特性 | 说明 | 价值 | | ------------ | ------------------------------ | ---------------------- | | **自动触发** | 数据变化时自动触发流程 | 减少手动操作,提升效率 | | **条件判断** | 支持前置条件表达式 | 灵活控制触发时机 | | **流程集成** | 无缝对接审批流能力 | 快速实现业务自动化 | | **多种时机** | 支持 6 种触发时机 | 覆盖完整业务场景 | | **资源调用** | 审批流中可调用云函数、数据库等 | 实现复杂业务逻辑 | :::tip 前置条件 使用事件处理功能前,请确保: - 已开通云开发企业版或以上套餐(审批流功能要求) - 已创建数据模型和审批流程 - 了解审批流基本概念,参考 [审批流使用指引](/lowcode/practices/custom-guide/zapprovflow) ::: ## 触发时机 「事件处理」支持 6 种数据操作的触发时机,可以根据业务需求灵活选择: | 触发时机 | 触发场景 | 适用案例 | | -------------- | ---------------------- | -------------------------------------------------- | | **新建后** | 创建单条记录后触发 | 新订单审批、字段自动计算、自动生成编号、发送通知 | | **更新后** | 更新单条记录后触发 | 字段自动拼接、数据联动更新、状态变更审批、日志记录 | | **删除后** | 删除单条记录后触发 | 数据删除审批、级联删除关联数据、资源回收 | | **批量新增后** | 批量创建多条记录后触发 | 批量导入后处理、批量初始化、批量审批 | | **批量更新后** | 批量更新多条记录后触发 | 批量数据同步、批量调价审批、批量状态变更 | | **批量删除后** | 批量删除多条记录后触发 | 批量清理审批、批量归档、批量下架 | > ⚠️ **注意**:触发时机在数据操作**完成后**执行,此时数据已写入数据库 ## 配置步骤 ### 第一步:创建事件处理 在数据模型的事件处理页面,点击「**新建事件处理**」按钮: 1. 访问 [云开发平台/数据模型](https://tcb.cloud.tencent.com/dev#/db/mysql/model/) 2. 选择目标数据模型 3. 进入「**事件处理**」标签页 4. 点击「**新建事件处理**」按钮 ### 第二步:配置前置条件表达式 「前置条件表达式」用于判断是否需要执行该事件处理,如都需要触发,则无需配置表达式 - **返回值要求**:必须返回布尔值(`true` 或 `false`) - **返回 `true`**:满足条件,触发审批流 - **返回 `false`**:不满足条件,跳过执行 ### 第三步:选择流程设置入参 「流程入参」用于传递数据到审批流程中,审批流程可以根据这些数据进行审批决策和展示。前往 [云开发平台/审批流](https://tcb.cloud.tencent.com/dev?#/lowcode/approval-flow/flow-design) 创建审批流 :::tip 审批流的能力边界 审批流不仅仅是简单的审批功能,它是一个完整的业务流程编排平台,支持: - **审批决策**:多级审批、条件审批、会签/或签等 - **业务逻辑**:调用云函数执行复杂计算、数据处理 - **数据操作**:读写数据库、更新数据模型 - **外部集成**:调用第三方 API、发送通知消息 - **流程控制**:条件分支、循环、并行执行等 这意味着事件处理触发的审批流,可以实现从简单审批到复杂业务自动化的各种场景。 ::: #### 入参选项 | 入参选项 | 说明 | 使用场景 | | ------------ | -------------------------- | -------------------------------------- | | **新增后值** | 传递当前数据记录的所有字段 | 审批流需要查看和处理完整的数据信息 | | **无** | 不传递任何数据 | 审批流不需要数据,仅作为通知或简单审批 | ## 使用示例 ### 示例 1:订单金额审批 **业务场景**:订单金额 `amount` 超过 5000 元时,自动触发审批流程 **配置方案**: | 配置项 | 配置值 | | ------------------ | ---------------- | | **触发时机** | 新建后 | | **前置条件表达式** | `#amount > 5000` | | **审批流程** | 大额订单审批流程 | | **流程入参** | 新增后值 | **效果**: - 创建金额 ≤ 5000 的订单:不触发审批,直接创建成功 - 创建金额 > 5000 的订单:自动触发审批流,等待审批完成 ### 示例 2:订单状态变更审批 **业务场景**:订单状态从"待支付"变更为"已完成"时,触发审批流程 **配置方案**: | 配置项 | 配置值 | | ------------------ | --------------------------------------------------------- | | **触发时机** | 更新后 | | **前置条件表达式** | `oldData.status === '待支付' && data.status === '已完成'` | | **审批流程** | 订单完成审批流程 | | **流程入参** | 新增后值 | **效果**: - 只有当订单状态从"待支付"变更为"已完成"时才触发审批 - 其他状态变更不触发审批 ### 示例 3:字段自动拼接(自动化场景) **业务场景**:字段C(文本)变动时,自动将字段B(自动编号)和字段C用破折号拼接,并更新到字段A **配置方案**: | 配置项 | 配置值 | | ------------------ | ---------------- | | **触发时机** | 更新后 | | **前置条件表达式** | 无 | | **审批流程** | 字段自动拼接流程 | | **流程入参** | 新增后值 | **审批流配置**(无需审批节点,直接调用数据模型): 1. **流程起始节点**:接收事件处理传入的数据 - 输入变量为对象,选择对应的数据模型,命名为 `data` 2. **表达式节点**:对data进行表达式计算,命名为 `newData` - 表达式:`#data.fieldB + "-" + #data.fieldC` 3. **更新记录操作节点**:更新数据记录 - 数据记录:选择 `newData`,选择提交入库 4. **流程结束** **效果**: - 当字段C的值发生变化时,自动触发流程 - 流程获取当前数据记录通过表达式计算出字段A的值,并更新到数据记录 - 无需人工审批,完全自动化执行 :::tip 审批流用于自动化 此示例展示了**审批流不仅用于审批场景,也可用于数据自动化处理**: - 可以直接调用数据模型进行 CRUD 操作 - 可以调用云函数执行复杂逻辑 - 可以调用第三方 API 实现系统集成 - 整个流程无需审批节点,即时自动执行 ::: --- # 数据模型/MySQL 数据模型/MySQL 多对多关联关系 > 当前文档链接: https://docs.cloudbase.net/model/model-relation 多对多关联关系是指两个数据模型之间存在「双向多重关联」的场景,例如: - **学生和课程**:一个学生可以选修多门课程,一门课程也可以被多个学生选修 - **用户和角色**:一个用户可以拥有多个角色,一个角色也可以分配给多个用户 - **商品和订单**:一个订单可以包含多个商品,一个商品也可以出现在多个订单中 在 **关系型数据库** 中,多对多关系无法通过单一外键实现,需要借助「中间表」来存储两个实体之间的关联关系。 ## 多对多中间表机制 云开发数据模型会自动为多对多关联关系创建「中间表」(Junction Table),您无需手动创建和维护这些表。中间表专门用于存储两个模型之间的关联关系,确保数据完整性和查询效率。 ### 中间表的作用 - **存储关联关系**:记录哪个主表记录关联了哪个从表记录 - **支持双向查询**:可以从任意一端查询关联的另一端数据 - **保证数据一致性**:通过外键约束确保关联数据的有效性 - **提升查询性能**:通过索引优化多对多关联查询 ### 查找中间表 当您需要直接操作或查看中间表数据时,可以按以下步骤定位中间表: #### 步骤 1:获取中间模型标识 进入 [云开发平台 / MySQL数据库 / 数据模型](https://tcb.cloud.tencent.com/dev?#/db/mysql/model/?sourceType=internal_mysql),在模型字段配置中查找「中间模型标识」: 模型字段配置 - 中间模型标识 > 💡 **提示**:「中间模型标识」是系统自动生成的唯一标识符,用于在数据库中定位对应的中间表。 #### 步骤 2:在数据库中搜索中间表 通过「中间模型标识」在数据库表列表中搜索,即可找到对应的中间表: 在数据库中查找中间表 > ⚠️ **环境区分**: > - 表名**以 `-preview` 结尾**:体验环境的中间表 > - 表名**不以 `-preview` 结尾**:正式环境的中间表 ### 中间表字段说明 中间表通常包含以下核心字段: | 字段名 | 说明 | 示例 | |--------|------|------| | `leftRecordId` | 主表(左表)记录的主键 ID | 学生表的 `_id`,如 `STUDENT_001` | | `rightRecordId` | 从表(右表)记录的主键 ID | 课程表的 `_id`,如 `COURSE_101` | #### 字段说明图示 中间表关联字段说明 #### 实际示例 以「学生选课」场景为例,中间表的数据结构如下: | leftRecordId | rightRecordId | 含义 | |--------------|---------------|------| | `STUDENT_001` | `COURSE_101` | 学生 001 选修了课程 101 | | `STUDENT_001` | `COURSE_102` | 学生 001 选修了课程 102 | | `STUDENT_002` | `COURSE_101` | 学生 002 选修了课程 101 | 这样的设计使得: - 通过 `STUDENT_001` 可以查询到该学生选修的所有课程(`COURSE_101`、`COURSE_102`) - 通过 `COURSE_101` 可以查询到选修该课程的所有学生(`STUDENT_001`、`STUDENT_002`) --- # 数据模型/MySQL 数据模型/SQL 操作 > 当前文档链接: https://docs.cloudbase.net/model/db-raw-query 当 **MySQL数据模型** SDK 无法满足复杂查询需求时,可以直接使用 SQL 语句进行数据库操作。 :::tip 提示 **SQL 操作** 仅支持服务端调用 - [@cloudbase/node-sdk](/api-reference/server/node-sdk/initialization) - [HTTP API](/http-api/model/mysql-command) ::: **SQL 语法支持范围** 当前支持的 SQL 语句类型: - `SELECT` - 数据查询 - `INSERT` - 数据插入 - `UPDATE` - 数据更新 - `DELETE` - 数据删除 > ⚠️ 注意:SDK 不支持事务(Transaction)、存储过程、触发器等高级数据库功能 以下介绍通过 **@cloudbase/node-sdk** 进行SQL操作: ## SDK 初始化 ### 安装依赖 ```bash npm install @cloudbase/node-sdk --save ``` ### 初始化代码 ```js import cloudbase from "@cloudbase/node-sdk" // 云函数环境下使用 require 方式 // const cloudbase = require('@cloudbase/node-sdk') // 初始化应用 const app = cloudbase.init({ env: "your-env-id" // 替换为您的环境 ID }) // 获取数据模型实例 const models = app.models ``` > 💡 注意:详细的初始化配置请参考 [SDK 初始化](/api-reference/server/node-sdk/initialization) 文档 ## 查询方法 MySQL 数据模型提供两种 SQL 查询方式: | 方法 | 说明 | 安全性 | 推荐度 | | ------------ | ---------------------- | ----------------- | ------ | | `$runSQL` | 预编译模式,参数化查询 | 高(防 SQL 注入) | ⭐⭐⭐ | | `$runSQLRaw` | 原始模式,直接执行 SQL | 低(需自行防护) | ⭐ | > ⚠️ 注意:仅支持服务端调用(云函数、云托管等),建议优先使用 `$runSQL` 预编译模式 ### 预编译模式 $runSQL 使用参数化查询,通过 Mustache 语法(`{{ }}`)绑定参数,有效防止 SQL 注入。 #### SELECT 查询示例 ```js // 条件查询 const result = await models.$runSQL( "SELECT * FROM `table_name` WHERE title = {{title}} LIMIT 10", { title: "hello" } ); // 数值比较 const result = await models.$runSQL( "SELECT * FROM `table_name` WHERE read_num > {{num}}", { num: 1000 } ); // 时间范围查询 const result = await models.$runSQL( "SELECT * FROM `table_name` WHERE updatedAt > UNIX_TIMESTAMP({{timestamp}}) * 1000", { timestamp: "2024-06-01 00:00:00" } ); // LIKE 模糊查询 const result = await models.$runSQL( "SELECT * FROM `table_name` WHERE author_tel LIKE {{tel}}", { tel: "1858%" } ); // 聚合统计 const result = await models.$runSQL( "SELECT COUNT(*) as total FROM `table_name` WHERE is_published = {{isPublished}}", { isPublished: true } ); // 指定字段查询 const result = await models.$runSQL( "SELECT read_num, title FROM `table_name` ORDER BY read_num DESC" ); ``` #### INSERT/UPDATE/DELETE 示例 > ⚠️ 注意:INSERT操作时必须传入唯一标识`_id`字段,否则数据将没有唯一标识,可能导致后续查询和更新操作出现问题 ```js // 插入数据(必须包含_id唯一标识) const insertResult = await models.$runSQL( "INSERT INTO `table_name` (_id, title, content, author) VALUES ({{id}}, {{title}}, {{content}}, {{author}})", { id: "article_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9), // 生成唯一ID title: "新文章", content: "文章内容", author: "作者名" } ); // 批量插入数据 const batchInsertResult = await models.$runSQL( "INSERT INTO `table_name` (_id, title, status) VALUES ({{id1}}, {{title1}}, {{status1}}), ({{id2}}, {{title2}}, {{status2}})", { id1: "article_" + Date.now() + "_001", title1: "文章1", status1: "published", id2: "article_" + Date.now() + "_002", title2: "文章2", status2: "draft" } ); // 更新数据 const updateResult = await models.$runSQL( "UPDATE `table_name` SET read_num = read_num + 1 WHERE _id = {{id}}", { id: "article_id_123" } ); // 删除数据 const deleteResult = await models.$runSQL( "DELETE FROM `table_name` WHERE status = {{status}} AND updatedAt < {{date}}", { status: "draft", date: "2024-01-01 00:00:00" } ); ``` > 💡 注意:`_id`字段建议使用以下方式生成唯一标识: > - 时间戳 + 随机字符串:`"prefix_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9)` > - UUID库:使用`uuid`包生成标准UUID > - 业务相关ID:结合业务逻辑生成有意义的唯一标识 #### 返回结果格式 ```js // SELECT 查询返回示例 { "data": { "total": 1, "executeResultList": [{ "_id": "9JXU7BWFZJ", "title": "hello", "read_num": 997, "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-02T00:00:00.000Z" }], "backendExecute": "27" }, "requestId": "16244844-19fe-4946-8924-d35408ced576" } // INSERT/UPDATE/DELETE 操作返回示例 { "data": { "total": 1, // 影响的行数 "executeResultList": [], "backendExecute": "15" }, "requestId": "16244844-19fe-4946-8924-d35408ced576" } ``` ### 原始模式 $runSQLRaw 用于动态表名等特殊场景,需要自行处理 SQL 注入防范。 #### 基础用法 ```js // 直接执行 SQL 语句 const result = await models.$runSQLRaw( "SELECT * FROM `table_name` WHERE title = 'hello' LIMIT 10" ); // 动态表名场景(需确保表名来源安全) const tableName = getValidTableName(); // 必须验证表名安全性 const result = await models.$runSQLRaw( `SELECT * FROM \`${tableName}\` WHERE status = 'active'` ); // 复杂查询示例 const result = await models.$runSQLRaw(` SELECT t1.title, t2.category_name, COUNT(t3.comment_id) as comment_count FROM articles t1 LEFT JOIN categories t2 ON t1.category_id = t2._id LEFT JOIN comments t3 ON t1._id = t3.article_id WHERE t1.status = 'published' GROUP BY t1._id, t2.category_name ORDER BY comment_count DESC LIMIT 20 `); ``` ## 安全注意事项 ### 预编译模式安全建议 - **优先选择**:除非必要,始终使用 `$runSQL` 预编译模式 - **参数绑定**:所有用户输入都通过参数绑定传递,避免直接拼接 - **类型验证**:确保参数类型与数据库字段类型匹配 ### 原始模式安全要求 使用 `$runSQLRaw` 时必须严格遵守: - **输入验证**:严格验证和过滤所有用户输入 - **白名单机制**:只允许预定义的安全值(如表名、字段名) - **特殊字符转义**:正确处理单引号、反引号等 SQL 特殊字符 - **错误处理**:避免向客户端暴露详细的数据库错误信息 - **权限控制**:确保执行 SQL 的账户具有最小必要权限 ```js // ❌ 危险示例 - 直接拼接用户输入 const userInput = req.body.title; const result = await models.$runSQLRaw( `SELECT * FROM articles WHERE title = '${userInput}'` ); // ✅ 安全示例 - 使用预编译模式 const result = await models.$runSQL( "SELECT * FROM articles WHERE title = {{title}}", { title: req.body.title } ); ``` --- # 数据模型/MySQL 数据模型/SQL 模板 > 当前文档链接: https://docs.cloudbase.net/model/sql-template **MySQL 数据模型** 提供配置 **SQL 模板** 功能,支持参数化查询和权限控制,可在小程序端、Web 端安全调用。 ## 基础使用 ### 创建模板 进入 [云开发平台/MySQL数据库/数据模型](https://tcb.cloud.tencent.com/dev?#/db/mysql/model/?sourceType=internal_mysql) ,选择对应数据模型,点击「SQL模版管理」标签页 ![](https://qcloudimg.tencent-cloud.cn/raw/92d00bb9518ac602e3e9310c053b6c52.png) 点击「新建」按钮,填写模板名称和 SQL 语句保存即可 ### 调用 SQL 模版 选择对应 SDK 进行调用: | SDK 类型 | 适用平台 | | ------------------------------------------------------------- | ------------ | | [JS SDK](/api-reference/webv3/model/sql-template) | Web 浏览器 | | [Node SDK](/api-reference/server/node-sdk/model/sql-template) | Node.js 环境 | ### 调用示例 以 Node SDK 为例,调用示例代码如下: ```js import cloudbase from "@cloudbase/node-sdk"; // 初始化应用 const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境 ID }); // 调用 user 表的 SQL 模版 const res = await app.models.user.runSQLTemplate({ templateName: "template_name", params: { key: "value", }, }); ``` ## SQL 语句规范 ### 支持的命令 - `SELECT` - `INSERT INTO` - `REPLACE INTO` - `UPDATE` - `DELETE` ### 基本规则 1. 每个模板仅支持一条 SQL 命令 2. 主表必须是当前模型的表 3. 联表查询时子表可以是同库下任意表 4. `INSERT`/`REPLACE` 操作必须包含 `owner` 和 `_openid` 系统字段 ### 参数语法 使用 `{{ param }}` 语法引入变量参数: ```sql SELECT * FROM {{ model.tableName() }} WHERE status = {{ status }} ``` ## 内置函数 SQL 模板支持以下内置函数,可在模板中直接使用: | 类别 | 函数 | 说明 | | ---- | ---------------------------------- | ------------------ | | 模型 | `model.tableName()` | 获取当前模型表名 | | 模型 | `model.tableName('model_name')` | 获取指定模型表名 | | 模型 | `model.dataId()` | 生成数据 ID | | 用户 | `user.userId()` | 获取用户 ID | | 用户 | `user.openId()` | 获取用户 openId | | 权限 | `auth.rowPermission()` | 获取当前模型行权限 | | 权限 | `auth.rowPermission('model_name')` | 获取指定模型行权限 | | 系统 | `system.currentEpoch()` | 获取秒级时间戳 | | 系统 | `system.currentEpochMillis()` | 获取毫秒级时间戳 | ## 使用示例 ### 表名引用 ```sql -- 使用当前模型表 SELECT * FROM {{ model.tableName() }} -- 联表查询 SELECT a.*, b.* FROM {{ model.tableName() }} a JOIN {{ model.tableName('other_model') }} b ON a.id = b.ref_id ``` ### 数据操作 ```sql -- 插入数据 INSERT INTO {{ model.tableName() }} (_id, name, owner, _openid, createBy, updatedAt) VALUES ( {{ model.dataId() }}, {{ name }}, {{ user.userId() }}, {{ user.openId() }}, {{ user.userId() }}, {{ system.currentEpochMillis() }} ) -- 更新数据 UPDATE {{ model.tableName() }} SET name = {{ name }}, updatedAt = {{ system.currentEpochMillis() }} WHERE _id = {{ id }} AND {{ auth.rowPermission() }} -- 查询数据 SELECT * FROM {{ model.tableName() }} WHERE status = {{ status }} AND {{ auth.rowPermission() }} ``` ### NULL 值处理 由于不支持 `IS NULL` 和 `IS NOT NULL`,使用以下替代语法: ```sql -- 查询空值 WHERE column <=> NULL -- 查询非空值 WHERE !(column <=> NULL) ``` --- # 数据模型/框架快速入门/未命名文档 > 当前文档链接: https://docs.cloudbase.net/database/framework-quickstarts/model/mp --- sidebar_label: "微信小程序" --- # 在微信小程序中访问云开发数据模型 学习如何创建云开发 MySQL 数据模型,添加示例数据,以及从微信小程序中查询数据。 ## 1. 创建 MySQL 数据模型并添加数据 > 注意:必须使用你的微信小程序关联的腾讯云账号登录云开发平台 在云开发平台创建名为 `todos` 的数据模型,字段信息如下: ![数据模型-todos字段定义示例](https://qcloudimg.tencent-cloud.cn/raw/8b569ec5e7b35158f6a05d0b0a33bf88.png) | 字段名称 | 字段标识 | 数据类型 | 是否必填 | 是否唯一 | | -------- | ------------ | -------------- | -------- | -------- | | 是否完成 | is_completed | 布尔值 | 否 | 否 | | 描述 | description | 文本、多行文本 | 否 | 否 | | 标题 | title | 文本、单行文本 | 否 | 否 | 创建完成后,在 `todos` 数据模型中录入示例数据。 ## 2. 创建微信小程序 ![微信开发者工具-新建小程序项目界面](https://qcloudimg.tencent-cloud.cn/raw/3bbd8b1e0e3775158c64ea98e7f873c1.png) ## 3. 安装云开发 SDK 依赖 1. 在小程序 `app.json` 所在的目录执行命令安装 npm 包。 ```bash npm install @cloudbase/wx-cloud-client-sdk ``` 2. 点击微信开发者工具工具栏上的“工具” -> “构建 npm”。 ## 4. 从小程序中查询数据 - 初始化 SDK ```javascript // app.js const { init } = require("@cloudbase/wx-cloud-client-sdk"); App({ onLaunch() { let client = null; this.globalData.getModels = async () => { if (!client) { wx.cloud.init({ env: "<云开发环境 ID>", }); client = init(wx.cloud); } return client.models; }; }, globalData: {}, }); ``` - 在页面 `js` 文件中添加以下查询代码 ```javascript // pages/index/index.js Page({ data: { todos: [], }, onLoad() { this.fetchTodos(); }, async fetchTodos() { try { const models = await getApp().globalData.getModels(); const { data } = await models.todos.list({ filter: { where: {}, }, pageSize: 10, pageNumber: 1, getCount: true, // envType: pre 体验环境, prod 正式环境 envType: "prod", }); this.setData({ todos: data.records, }); } catch (error) { console.error("获取待办事项失败:", error); } }, }); ``` - 在页面对应的 `wxml` 文件中展示数据 ```xml {{item.title}} ``` ## 5. 运行小程序 在微信开发者工具中,点击编译预览小程序。 ## 注意事项 - 将 \<云开发环境 ID\> 替换为你实际的云开发环境 ID。 --- # 数据模型/框架快速入门/未命名文档 > 当前文档链接: https://docs.cloudbase.net/database/framework-quickstarts/model/react --- sidebar_label: "React" --- # 在 React 应用中访问云开发数据模型 学习如何创建云开发 MySQL 数据模型,添加示例数据,以及从 React 应用查询数据。 ## 1. 创建 MySQL 数据模型并添加数据 在云开发平台创建名为 `todos` 的数据模型,字段信息如下: ![数据模型-todos字段定义示例](https://qcloudimg.tencent-cloud.cn/raw/8b569ec5e7b35158f6a05d0b0a33bf88.png) | 字段名称 | 字段标识 | 数据类型 | 是否必填 | 是否唯一 | | -------- | ------------ | -------------- | -------- | -------- | | 是否完成 | is_completed | 布尔值 | 否 | 否 | | 描述 | description | 文本、多行文本 | 否 | 否 | | 标题 | title | 文本、单行文本 | 否 | 否 | 创建完成后,在 `todos` 数据模型中录入示例数据。 ## 2. 创建 React 应用 > 要求 Node.js 版本 >= 20.19 ```bash npm create vite@latest todo -- --template react ``` ## 3. 安装云开发 SDK 依赖 在项目根目录下,执行以下命令: ```bash cd todo && npm install @cloudbase/js-sdk ``` ## 4. 声明云开发环境变量 创建 `.env.local` 文件,并填入云开发环境 ID ``` VITE_CLOUDBASE_ENV_ID=<云开发环境 ID> ``` ## 5. 从应用查询数据 在 `app.jsx` 中添加以下代码查询数据: ```javascript import cloudbase from "@cloudbase/js-sdk"; import { useEffect, useState } from "react"; import "./App.css"; let cachedApp = null; const getModels = async () => { if (!cachedApp) { cachedApp = cloudbase.init({ env: import.meta.env.VITE_CLOUDBASE_ENV_ID, }); const auth = cachedApp.auth({ persistence: "local", }); await auth.signInAnonymously(); } return cachedApp.models; }; function App() { const [todos, setTodos] = useState([]); useEffect(() => { fetchTodos(); }, []); const fetchTodos = async () => { try { const models = await getModels(); const { data } = await models.todos.list({ filter: { where: {}, }, pageSize: 10, pageNumber: 1, getCount: true, envType: "pre", }); setTodos(data.records); } catch (error) { console.error("获取待办事项失败:", error); } }; return ( <>
    {todos.map((todo) => (
  • {todo.title}
  • ))}
); } export default App; ``` ## 6. 启动应用 在项目根目录下执行以下命令启动应用: ```bash npm run dev ``` ## 注意事项 - 操作不同环境数据时,需设置对应的 `envType`。 - 若使用其他登录方式(参考[【登录认证】](https://docs.cloudbase.net/api-reference/webv3/authentication)),将 `auth.signInAnonymously()` 替换为对应登录方法。 --- # 数据模型/框架快速入门/未命名文档 > 当前文档链接: https://docs.cloudbase.net/database/framework-quickstarts/model/vue --- sidebar_label: "Vue" --- # 在 Vue 应用中访问云开发数据模型 学习如何创建云开发 MySQL 数据模型,添加示例数据,以及从 Vue 应用查询数据。 ## 1. 创建 MySQL 数据模型并添加数据 在云开发平台创建名为 `todos` 的数据模型,字段信息如下: ![数据模型-todos字段定义示例](https://qcloudimg.tencent-cloud.cn/raw/8b569ec5e7b35158f6a05d0b0a33bf88.png) | 字段名称 | 字段标识 | 数据类型 | 是否必填 | 是否唯一 | | -------- | ------------ | -------------- | -------- | -------- | | 是否完成 | is_completed | 布尔值 | 否 | 否 | | 描述 | description | 文本、多行文本 | 否 | 否 | | 标题 | title | 文本、单行文本 | 否 | 否 | 创建完成后,在 `todos` 数据模型中录入示例数据。 ## 2. 创建 Vue 应用 > 要求 Node.js 版本 >= 20.19 ```bash npm create vite@latest todo -- --template vue ``` ## 3. 安装云开发 SDK 依赖 在项目根目录下,执行以下命令: ```bash cd todo && npm install @cloudbase/js-sdk ``` ## 4. 声明云开发环境变量 创建 `.env.local` 文件,并填入云开发环境 ID ``` VITE_CLOUDBASE_ENV_ID=<云开发环境 ID> ``` ## 5. 从应用查询数据 在 `App.vue` 中添加以下代码查询数据: ```vue ``` ## 6. 启动应用 在项目根目录下执行以下命令启动应用: ```bash npm run dev ``` ## 注意事项 - 操作不同环境数据时,需设置对应的 `envType`。 - 若使用其他登录方式(参考[【登录认证】](https://docs.cloudbase.net/api-reference/webv3/authentication)),将 `auth.signInAnonymously()` 替换为对应登录方法。 --- # 数据模型/其他参考/使用 CLI 工具 > 当前文档链接: https://docs.cloudbase.net/model/cli 您可以使用 [CloudBase CLI 工具](/cli-v1/db/management) 来完成对数据模型的管理和配置。 ## 使用 CLI 工具管理数据模型 CloudBase CLI 工具提供了一系列的命令,帮助您高效地管理数据模型。 ### 命令概览 - `list`: 列出云端所有数据模型。 - `pull`: 从云端拉取数据模型到本地。 - `push`: 将本地数据模型推送到云端。 ### 列出云端所有数据模型 ```bash tcb db list # 如果本地没有安装 CloudBase CLI ,可使用以下命令 npx --package=@cloudbase/cli@2.5 tcb db list ``` 此命令将展示您在云端的所有数据模型列表。 ### 从云端拉取数据模型到本地 ```bash tcb db pull # 如果本地没有安装 CloudBase CLI ,可使用以下命令 npx --package=@cloudbase/cli@2.5 tcb db pull ``` 使用此命令,您可以将云端的数据模型同步到本地环境。 ### 将本地数据模型推送到云端 ```bash tcb db push # 如果本地没有安装 CloudBase CLI ,可使用以下命令 npx --package=@cloudbase/cli@2.5 tcb db push ``` 通过此命令,您可以一键创建或者更新云端的数据模型,以反映本地的更改。 ## 更多信息 要获取更多关于 CloudBase CLI 工具的信息,请访问 [CloudBase CLI 命令行工具](/cli-v1/install)。 --- # 数据模型/其他参考/同步模型定义 > 当前文档链接: https://docs.cloudbase.net/model/sync-schema 为了提高开发者的代码编写体验,可以将用户的云端数据模型同步为本地的类型定义文件,享受代码编辑器的智能类型提示和检查功能。同步后效果如下: 数据模型智能提示 ![](https://qcloudimg.tencent-cloud.cn/raw/ba70dda1d42a9c8b9850f995ff415232.png) 字段智能提示 ![](https://qcloudimg.tencent-cloud.cn/raw/0f42f0a02f707d107a0c6e4230f8cf3c.png) ## 同步流程 选择以下任一方式同步数据模型定义: > 注意: > > 1. 对于微信小程序,需要在 `app.json` 文件同目录下运行命令 > 2. 对于微信云开发的云函数,需要在 `cloudfunctions/` 目录下运行命令 > 3. 首次运行需要指定环境 ID 参数 `--envId=`,以后就不需再指定 > 4. 运行 tcb 需要登录,使用微信云开发的,请在登录时选择【小程序公众号】的登录方式 ### 方式一:以 npx 直接运行 tcb 命令 > 此方式仅适用于本地未安装 `tcb` 的情况。如果本地已安装 `tcb`,请使用[【方式二】](#方式二安装-tcb-命令行工具) 进入项目目录中(目录位置参考注意事项),然后执行以下命令 ```shell npx --package=@cloudbase/cli tcb sync-model-dts --envId= ``` ### 方式二:安装 tcb 命令行工具 > 如果本地未安装 `tcb` 或已安装的 `tcb` 版本 \<2.4,请执行以下命令进行安装或升级 ```shell npm i @cloudbase/cli -g ``` 进入项目目录中(目录位置参考注意事项),然后执行以下命令 ```shell tcb sync-model-dts --envId= ``` ## 常见问题与解决 ### 问题:代码编辑器无模型提示 1. 确保 `@cloudbase/wx-cloud-client-sdk` 模块已正确安装。 2. 确保 `tsconfig.json` 文件配置正确: ```json { "compilerOptions": { "allowJs": true } } ``` --- # 数据模型/其他参考/常见问题 > 当前文档链接: https://docs.cloudbase.net/model/faq ## 关联关系问题 ### 如何在云函数中更新关联字段? 在云函数中更新关联字段时,必须使用 `{_id: "xxx"}` 格式。 **一对一关联**: ```javascript // 更新用户的个人资料关联 await cloudbase.model('user').doc(userId).update({ profile: { _id: "profile_123" } }); ``` **一对多关联**: ```javascript // 更新班级的学生列表 await cloudbase.model('class').doc(classId).update({ students: [ { _id: "student_1" }, { _id: "student_2" } ] }); ``` 详细示例请参考 [关联关系详解 - 云函数操作关联字段](/model/config/relation#云函数操作关联字段)。 ### 多对多关系如何操作? 多对多关系由系统自动管理中间表,您无需手动创建中间表。 **创建多对多关联**: ```javascript // 学生选课(多对多) await models.student.create({ data: { name: "小明", courses: [ { _id: "course_1" }, { _id: "course_2" } ] } }); ``` **查询多对多关联数据**: ```javascript // 查询学生及其课程 const { data } = await models.student.get({ filter: { where: { _id: { $eq: "student_123" } } }, select: { name: true, courses: { courseName: true, credits: true } } }); ``` **中间表命名规则**:`数据模型A_数据模型B`(按字母顺序),例如学生(student)和课程(course)的中间表名为 `course_student`。 详细说明请参考 [关联关系详解 - 多对多中间表操作](/model/config/relation#多对多中间表操作)。 ### 关联字段更新失败,提示格式错误? 这是最常见的错误,原因是未使用正确的格式。 **错误示例**: ```javascript // ❌ 错误:直接传字符串 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { class: "class_456" } // 错误! }); // ❌ 错误:传入多余字段 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { class: { _id: "class_456", name: "一年级1班" // 不需要传其他字段 } } }); ``` **正确示例**: ```javascript // ✅ 正确:使用 {_id: "xxx"} 格式 await models.student.update({ filter: { where: { _id: { $eq: "student_123" } } }, data: { class: { _id: "class_456" } } }); ``` 详细排查请参考 [关联关系详解 - 故障排查](/model/config/relation#故障排查)。 ### 图片字段上传报错超出最大值范围? 图片字段存储的是图片的云 ID,而不是图片的二进制内容 **推荐解决方案**: **方案1:使用FileID、URL**(推荐) ```javascript // ✅ 推荐:存储图片 CloudID { avatarUrl: "cloud://env-id.xxxx/avatar/user123.jpg" } ``` ### 关联查询结果为空是什么原因? 关联查询结果为空通常有以下几种原因: **原因1:权限配置问题** 关联模型的权限配置独立于主模型,需要分别检查: - 主模型的权限配置 - 关联模型的权限配置 - 当前用户是否有权限访问关联数据 **原因2:关联数据确实不存在** 排查步骤: 1. 在控制台直接查看主记录的关联字段值 2. 检查关联字段是否为空或包含无效的 `_id` 3. 在关联模型中查询对应的记录是否存在 **原因3:select 字段配置错误** 必须在 `select` 中明确指定关联字段: ```javascript // ✅ 正确:明确指定关联字段 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, comments: { // 必须明确指定 content: true } } }); ``` **原因4:多层关联查询不支持** 关联查询目前只支持一层关联,多层嵌套关联不会返回数据: ```javascript // ❌ 错误:多层关联不支持 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, author: { name: true, profile: { // ❌ 第二层关联:不会返回数据 city: true } } } }); ``` 详细排查请参考 [关联关系详解 - 关联查询结果为空](/model/config/relation#关联查询结果为空)。 ### 为什么多层关联查询不返回数据? 关联查询目前只支持 **一层关联**,不支持多层嵌套的关联查询。 **不支持的多层关联示例**: ```javascript // ❌ 错误:多层关联查询 select: { title: true, author: { // 第一层关联 ✅ name: true, profile: { // 第二层关联 ❌ 不支持 address: { // 第三层关联 ❌ 不支持 city: true } } } } ``` **支持的一层关联示例**: ```javascript // ✅ 正确:一层关联查询 select: { title: true, author: { // 第一层关联 ✅ name: true, email: true, bio: true // 普通字段(非关联字段)可以正常查询 } } ``` **解决方案**: **方案1:分步查询** ```javascript // 第一步:查询第一层关联 const post = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, author: { _id: true, name: true, profileId: true // 获取下一层关联的 ID } } }); // 第二步:查询第二层关联 if (post.data.author.profileId) { const profile = await models.profile.get({ filter: { where: { _id: { $eq: post.data.author.profileId } } }, select: { address: { city: true } } }); post.data.author.profile = profile.data; } ``` **方案2:数据冗余** ```javascript // 在第一层模型中冗余需要的字段 // author 模型 { _id: "author_123", name: "张三", city: "北京", // 冗余字段:来自 profile.address.city bio: "个人简介" } // 这样可以在一层查询中获取所需信息 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, author: { name: true, city: true // 直接查询冗余字段 } } }); ``` 详细说明请参考 [关联关系详解 - 关联查询层级限制](/model/config/relation#关联查询层级限制说明)。 ### 如何配置关联模型的访问权限? 关联模型的权限配置独立于主模型,需要在每个模型中分别设置。 **配置步骤**: 1. 在 [云开发平台 - 数据模型](https://tcb.cloud.tencent.com/dev#/database-model) 中选择对应的数据模型 2. 点击「权限管理」标签页 3. 根据业务需求配置权限规则 **常见场景**: - 公开数据:设置为「所有用户可读」 - 私有数据:设置为「仅创建者可读写」 - 关联数据:确保关联模型的权限允许查询 **示例**: ```javascript // 如果文章(post)模型设置为「所有用户可读」 // 但评论(comment)模型设置为「仅创建者可读」 // 那么查询文章时,只能看到当前用户创建的评论 const { data } = await models.post.get({ filter: { where: { _id: { $eq: "post_123" } } }, select: { title: true, comments: { // 只返回当前用户创建的评论 content: true } } }); ``` ## 索引问题 ### 如何选择合适的索引字段? 选择索引字段需要根据实际查询场景: **原则1:为常用查询条件创建索引** - 分析业务中最常用的查询字段 - 为 `where` 条件中的字段创建索引 **原则2:为排序字段创建索引** - `orderBy` 指定的排序字段应创建索引 - 可以提升排序查询性能 **原则3:组合索引字段顺序很重要** - 最常用的查询字段放在最前面 - 选择性高的字段(值分布广)放在前面 - 例如:`(status, createTime)` 比 `(createTime, status)` 更好(status 的值有限) **原则4:避免过多索引** - 索引会占用存储空间 - 写入操作需要更新索引,影响性能 - 只为实际需要的查询创建索引 **示例**: 假设有学生模型,常见查询场景: 1. 按年龄查询:`where: { age: { $gte: 18 } }` 2. 按班级和年龄查询:`where: { classId: "xxx", age: { $gte: 18 } }` 3. 按成绩排序:`orderBy: { field: 'score', direction: 'desc' }` **推荐索引配置**: - 单字段索引:`age` - 组合索引:`(classId, age, score)` ### 索引对性能的实际影响是多少? 索引对查询性能的影响取决于数据量和查询复杂度: **有索引 vs 无索引的性能差异**: | 数据量 | 无索引查询时间 | 有索引查询时间 | 性能提升 | | ------------ | -------------- | -------------- | --------------- | | 1,000 条 | 10-50 ms | 1-5 ms | **5-10 倍** | | 10,000 条 | 100-500 ms | 5-20 ms | **10-50 倍** | | 100,000 条 | 1-5 秒 | 10-50 ms | **100-500 倍** | | 1,000,000 条 | 10-60 秒 | 20-100 ms | **500-1000 倍** | **索引的成本**: - **存储空间**:每个索引占用额外的存储空间(约为数据大小的 10-30%) - **写入性能**:插入、更新、删除操作需要更新索引,影响写入速度(约增加 10-20% 耗时) **最佳实践**: - 对于读多写少的场景:可以创建更多索引 - 对于写多读少的场景:只创建必要的索引 - 定期分析查询日志,优化索引配置 ## 使用相关问题 ### 查询条件正确但结果为空 在使用数据库查询时,如果返回空结果,通常有以下两种情况: 1. 没有符合查询条件的数据 2. 数据被权限控制过滤 #### 排查方法 1. **确认数据存在性** - 在云开发控制台直接查看集合中是否存在目标数据 - 检查数据的创建时间和字段值是否符合预期 2. **检查权限配置** - 查看集合的基础权限设置是否允许当前用户读取 - 数据库查询时会以 `_openid` 字段作为数据归属判定依据 - 如果使用安全规则,验证规则表达式是否正确 - 确认查询条件是否包含安全规则要求的必要字段 3. **验证查询条件** - 简化查询条件,逐步排查哪个条件导致结果为空 - 检查字段名称、数据类型和查询语法是否正确 ### 数据模型查询速度慢怎么办? 查询速度慢的常见原因和优化方法: **原因1:缺少索引** **排查方法**: - 检查查询条件中的字段是否创建了索引 - 在控制台查看数据模型的索引配置 **解决方法**: - 为 `where` 条件中的字段创建索引 - 为 `orderBy` 排序字段创建索引 - 使用组合索引优化多字段查询 **原因2:关联查询层级过深** **排查方法**: - 检查是否查询了多层关联关系(如 A → B → C → D) - 查看 `select` 中关联字段的嵌套层级 **解决方法**: - 减少关联层级,只查询必要的关联数据 - 使用分页查询,避免一次性加载大量关联数据 - 考虑数据冗余,将常用的关联字段冗余到主表 **原因3:查询数据量过大** **排查方法**: - 检查是否使用了分页(`pageSize` 和 `pageNo`) - 查看单次查询返回的记录数 **解决方法**: - 使用分页查询,每页 20-50 条记录 - 使用 `select` 只查询需要的字段,避免查询所有字段 - 添加合理的 `where` 条件,缩小查询范围 **示例优化**: 优化前(慢): ```javascript // ❌ 慢:查询所有字段,没有分页,关联层级深 const { data } = await models.post.list({ select: { title: true, content: true, author: { name: true, }, comments: { // 关联大量评论 content: true, } } }); ``` 优化后(快): ```javascript // ✅ 快:只查询必要字段,使用分页,减少关联层级 const { data } = await models.post.list({ filter: { where: { status: { $eq: 'published' } } // 添加过滤条件 }, select: { title: true, author: { // 只查询一层关联 name: true } // 评论数据单独查询,避免一次性加载 }, pageSize: 20, // 分页 pageNo: 1 }); ``` ### 如何优化关联查询性能? 关联查询性能优化建议: **优化1:减少关联层级** ```javascript // ❌ 避免:关联层级过深 select: { title: true, author: { profile: { address: { // 3层关联 city: true } } } } // ✅ 推荐:最多2层关联 select: { title: true, author: { name: true, city: true // 冗余字段 } } ``` **优化2:按需加载关联数据** ```javascript // 第一步:只查询主数据 const posts = await models.post.list({ select: { title: true, authorId: true } }); // 第二步:用户点击时再加载详细信息 const postDetail = await models.post.get({ filter: { where: { _id: { $eq: postId } } }, select: { title: true, content: true, author: { name: true, avatar: true } } }); ``` **优化3:使用数据冗余** ```javascript // 在主表中冗余常用字段,避免频繁关联查询 { title: "文章标题", authorId: "author_123", authorName: "张三", // 冗余字段 authorAvatar: "avatar.jpg" // 冗余字段 } ``` **优化4:批量查询关联数据** ```javascript // ❌ 避免:循环查询(N+1 问题) for (const post of posts) { const author = await models.user.get({ filter: { where: { _id: { $eq: post.authorId } } } }); } // ✅ 推荐:批量查询 const authorIds = posts.map(p => p.authorId); const authors = await models.user.list({ filter: { where: { _id: { $in: authorIds } } } }); ``` ## 关联查询问题 ### 数据模型关联查询返回数据不完整 使用数据模型进行关联查询时,返回的关联数据只有 `_id` 字段,其他指定的字段没有返回。 #### 数据模型查询 数据模型使用 `models.xxx.get(options)` 的方式,所有查询条件放在 `options` 参数中: ```javascript // 正确示例:数据模型操作 const { data } = await models.dictData.get({ filter: { where: { type_code: { $eq: finalTypeCode } } }, select: { $master: true, // 返回主表所有字段 dict_type: { // 关联表字段 _id: true, code: true, name: true, }, }, orderBy: [ { sort: 'asc' }, { createdAt: 'desc' } ], }); ``` #### 集合操作查询 如果使用文档型数据库的集合操作,需要使用 `lookup` 进行关联查询: ```javascript // 集合操作的关联查询 const result = await db.collection('dictData') .aggregate() .lookup({ from: 'dictType', localField: 'type_code', foreignField: 'code', as: 'dict_type' }) .end(); ``` #### 两种方式对比 | 特性 | 数据模型 | 集合操作 | |------|---------|---------| | API 风格 | `models.xxx.get(options)` | `db.collection('xxx').where().get()` | | 关联查询 | 内置支持,通过 `select` 指定 | 需要使用 `lookup` 聚合操作 | | 类型支持 | 有类型定义 | 无类型定义 | ### 数据模型中如何查询id列的最大值 系统默认的数据标识(_id)为字符串类型,其排序规则是根据ascii码的顺序。 如需查询id列的最大值,您需要按id列降序排列后,获取第一条数据 ```javascript const { data } = await models.post.list({ filter: { where: {}, }, orderBy: [ { _id: "desc", // 按照_id降序排列 }, ], pageSize: 1, // 分页大小,建议指定,如需设置为其它值,需要和 pageNumber 配合使用,两者同时指定才会生效 pageNumber: 1, // 第几页 getCount: true, // 开启用来获取总数 }); ``` --- # authenticationv2 Documentation > 云开发认证服务,提供多种登录方式、用户管理、权限控制、安全策略等完整身份认证解决方案 # 身份认证/身份认证 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/introduce [CloudBase 身份认证](https://tcb.cloud.tencent.com/dev?#/identity/quick-start) 为您的应用提供完整的用户身份管理和访问控制解决方案。通过内置的多种登录方式和安全机制,对用户端发起的每个请求都会进行身份验证和权限检查,帮助您快速构建可靠的用户体系。 :::tip PG 模式环境 若你创建的是 PG 模式环境,身份认证的对外接口与本文一致,但底层工作机制有专项说明,详见 [PG:身份认证](./auth-pg)。 ::: ## 核心能力 云开发身份认证体系包含两个核心部分: ### 登录与会话 解决「用户是谁」的问题,支持多种登录方式,您可以根据业务场景选择合适的方案: | 登录方式 | 适用场景 | 特点 | 文档链接 | | --- | --- | --- | --- | | 匿名登录 | 快速体验、临时用户 | 无需注册,自动生成临时身份 | [查看文档](/authentication-v2/method/anonymous) | | 用户名密码 | 传统应用 | 用户名 + 密码 | [查看文档](/authentication-v2/method/username-login) | | 短信验证码 | 移动端应用 | 手机号 + 验证码 | [查看文档](/authentication-v2/method/sms-login) | | 邮箱登录 | 企业应用、正式用户 | 邮箱 + 密码 | [查看文档](/authentication-v2/method/email-login) | | 微信授权 | 微信生态应用 | 微信公众号、开放平台 | [查看文档](/authentication-v2/method/wechat-login) | | 自定义登录 | 已有用户体系 | 与现有账号体系集成 | [查看文档](/authentication-v2/method/custom-login) | | 微信小程序 | 微信小程序 | 自动完成登录认证 | [查看文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/init.html) | :::tip 提示 在使用登录方式前,需要先在 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 中启用对应的登录方式。详见 [管理登录方式](/authentication-v2/auth/manage-login)。 ::: ### 权限控制 解决「用户能调用什么资源」的问题,通过角色和策略精细化管理资源访问权限: - **角色管理**:为不同类型的用户分配不同的角色(组织成员、注册用户、匿名用户、自定义角色等) - **策略配置**:为角色配置具体的资源访问权限(数据库、云函数、云存储等) - **成员管理**:管理用户的角色分配和权限变更 详细配置请参考 [权限控制](/authentication-v2/auth/auth-control)。 ## 用户账号体系 ### 账号唯一标识 (UID) 每个登录云开发的用户都拥有一个独立的云开发账号,作为访问数据和资源的身份凭证: - **全局唯一**:每个账号都有全局唯一的 UID,作为用户的唯一身份标识 - **持久稳定**:UID 在用户整个生命周期内保持不变 - **跨平台统一**:同一用户在不同平台上的 UID 保持一致 ### 用户信息管理 每个账号可以存储和管理丰富的用户信息: - 基本信息(昵称、头像、邮箱、手机号等) - 自定义字段(支持扩展业务所需的用户属性) - 登录记录和行为数据 您可以在 [云开发平台/身份认证/用户管理](https://tcb.cloud.tencent.com/dev?#/identity/user-management) 中可视化查看和管理用户信息。 ![用户管理界面](https://qcloudimg.tencent-cloud.cn/raw/e4184ac2ab1e553e7c74089c822a6a63.png) 详细操作请参考 [管理用户](/authentication-v2/auth/manage-users)。 ### 多账号关联 支持将多种登录方式关联到同一个账号,为用户提供更灵活的登录体验: - **统一身份**:用户可以使用不同方式登录同一账号 - **无缝切换**:在不同设备或场景下灵活切换登录方式 - **数据一致性**:确保用户数据在不同登录方式下保持一致 例如,用户可以先使用「匿名登录」快速体验应用,后续再关联「手机号登录」或「微信登录」,所有数据都会保留在同一账号下。 详细操作请参考 [账户关联](/authentication-v2/auth/account-linking)。 ## 登录状态管理 ### 状态持久化 云开发会自动管理用户的登录状态: - **Web 端**:在显式退出登录之前,身份认证状态保留 30 天 - **移动端**:根据平台特性自动管理状态持久化 ### 令牌机制 CloudBase 使用双令牌机制保障访问安全和用户体验: - **访问令牌(Access Token)** - 用途:作为访问 CloudBase 服务的身份凭证 - 有效期:默认 2 小时 - 自动管理:SDK 自动维护令牌的使用和刷新,无需开发者手动处理 - **刷新令牌(Refresh Token)** - 用途:用于获取新的访问令牌 - 有效期:默认 30 天 - 自动续期:访问令牌过期时自动使用刷新令牌获取新的访问令牌 :::tip 提示 「匿名登录」的刷新令牌会在到期后自动续期,以实现长期的匿名登录状态。 ::: ## 相关资源 - [最佳实践](/authentication-v2/auth/best-practice) - 了解身份认证的最佳实践和常见场景 - [常见问题](/authentication-v2/auth/faq) - 查看常见问题和解决方案 - [API 参考](/api-reference/webv2/authentication) - 查阅完整的 API 文档 --- # 身份认证/用户/权限/用户管理 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/manage-users ## 用户类型 云开发提供三种用户类型,分别适用于不同的业务场景: | 用户类型 | 角色 | 用户数限制 | 用户信息 | 创建方式 | 登录方式 | 适用模式 | | -------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -------- | | 组织成员 | 在组织架构内的用户
支持分配自定义角色
对应旧版用户体系「内部用户」 | 受套餐限制
参考 [计费文档](https://cloud.tencent.com/document/product/876/75213) | 系统保存 | - 前往 [组织架构](https://tcb.cloud.tencent.com/dev?#/identity/user-organization) 页面新增
- 通过 [HTTP API](/lowcode/manage/auth) 新增 | [登录方式](/authentication-v2/method/username-login) | 仅传统模式 | | 注册用户 | 通过任意注册方式注册的用户
固定「注册用户」角色
对应旧版用户体系「外部用户」 | 不限用户数 | 系统保存 | - 前往 [用户管理](https://tcb.cloud.tencent.com/dev?#/identity/user-management) 页面新增
- 用户通过 [用户注册](/authentication-v2/method/signup) 自主注册 | [登录方式](/authentication-v2/method/username-login) | 通用 | | 匿名用户 | 固定的「默认访客」角色 | 不限用户数 | 系统不保存 | 无需注册 | [匿名登录](/authentication-v2/method/anonymous) | 通用 | :::info 模式说明 **PG 模式不支持「组织成员」和「组织架构」相关功能。** PG 模式下所有登录用户均为"注册用户",且用户数据存储在数据库 `auth.users` 表中,可直接 SQL 查询。完整说明见 [PG:身份认证](./auth-pg)。 ::: ### 用户类型说明 **组织用户**(仅传统模式):适用于组织内部成员,如企业员工、团队成员等。组织用户可以分配自定义角色,拥有更细粒度的权限控制。在 [云开发平台/身份认证/组织架构](https://tcb.cloud.tencent.com/dev?#/identity/user-organization) 页面创建的用户为组织用户。 **注册用户**:适用于应用的终端用户,如 C 端客户、社区成员等。注册用户具有固定的默认注册用户角色,不受用户数限制。在 [云开发平台/身份认证/用户管理](https://tcb.cloud.tencent.com/dev?#/identity/user-management) 页面手动创建的用户为注册用户。 **匿名用户**:适用于无需身份验证的临时访问场景,系统不保存用户信息,用户退出后无法追溯身份。 ## 用户信息管理 ### 获取当前登录用户 您可以通过 `auth.currentUser` 属性或 `auth.getCurrentUser()` 方法来获取当前登录用户的信息。 #### 使用方式 ```js import cloudbase from '@cloudbase/js-sdk'; const app = cloudbase.init({ env: 'your-env-id', }); // 获取 auth 实例 const auth = app.auth(); // 用户登录后,获取当前用户信息 const user = auth.currentUser; // 或者使用异步方法 // const user = await auth.getCurrentUser() if (user) { console.log('当前用户 UID:', user.uid); } else { console.log('用户未登录'); } ``` #### 返回值 该方法返回当前登录用户的 [`User`](/api-reference/webv2/authentication#user) 实例。如果用户未登录,则返回 `null`。 > 💡 **提示**:`auth` 实例的初始化方式请参考 [SDK 初始化](/authentication-v2/method/sdk-init) ### 获取用户资料 通过 `User` 对象的属性可以获取用户的个人资料信息: ```js const user = auth.currentUser; if (user) { // 云开发唯一用户 ID const uid = user.uid; // 用户昵称 const name = user.name; // 用户性别 const gender = user.gender; // 用户创建来源 const created_from = user.created_from; console.log(`用户 ${name} (${uid}) 的性别为 ${gender}`); } ``` #### 常用用户属性 | 属性 | 类型 | 说明 | | -------------- | ------ | ------------------------------- | | `uid` | string | 云开发唯一用户 ID | | `name` | string | 用户昵称 | | `gender` | string | 用户性别(MALE/FEMALE/UNKNOWN) | | `created_from` | string | 用户创建来源 | > 💡 **提示**:更多用户属性请参考 [`User` 对象 API 文档](/api-reference/webv2/authentication#user) ### 更新用户资料 使用 `User.update()` 方法可以更新用户的个人资料信息。 #### 使用示例 ```js const user = auth.currentUser; user.update({ name: 'Tony Stark', gender: 'MALE', }) .then(() => { console.log('用户资料更新成功'); }) .catch((error) => { console.error('更新失败:', error); }); ``` #### 可更新的字段 | 字段 | 类型 | 说明 | | -------- | ------ | ------------------------------- | | `name` | string | 用户昵称 | | `gender` | string | 用户性别(MALE/FEMALE/UNKNOWN) | > ⚠️ **注意**:部分用户信息(如 `uid`、`created_from` 等)为系统字段,无法通过此方法修改 ### 刷新用户资料 在多端应用中,用户可能在某个端更新了个人资料,其他端需要同步最新信息。使用 `User.refresh()` 方法可以从服务器重新获取最新的用户信息。 #### 使用示例 ```js const user = auth.currentUser; // 刷新用户信息 user.refresh() .then(() => { // 刷新成功,获取最新的用户信息 const { name, gender } = user; console.log(`当前用户: ${name}, 性别: ${gender}`); }) .catch((error) => { console.error('刷新用户信息失败:', error); }); ``` #### 使用场景 - 用户在其他设备或平台更新了个人资料 - 管理员后台修改了用户信息 - 需要获取最新的用户状态和权限信息 > 💡 **提示**:刷新操作会从服务器同步最新数据,建议仅在必要时调用,避免频繁请求 ## 其他参考 - [存储额外的用户信息](/authentication-v2/auth/practice/store-extra-user-info) - 如何使用自定义用户表存储业务相关的扩展信息 - [为注册用户赋予管理员权限](/authentication-v2/auth/practice/grant-admin-permission) - 如何将注册用户转为组织成员并分配管理员角色(仅传统模式) ## 相关文档 - [权限控制](/authentication-v2/auth/auth-control) - 角色体系和权限配置 - [用户注册](/authentication-v2/method/signup) - 用户注册 - [PG:身份认证](./auth-pg) - PG 模式下身份认证的完整工作机制 - [最佳实践](/authentication-v2/auth/best-practice) - 身份认证和权限管理最佳实践 --- # 身份认证/用户/权限/权限控制 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/auth-control CloudBase 提供两套权限模型,分别对应不同的环境模式,**两者解决的核心问题相同**——"用户能访问哪些资源",但实现机制完全不同: | 环境模式 | 权限模型 | 配置方式 | | ---- | ---- | ---- | | **传统模式** | 角色 + 策略(RBAC) | 控制台可视化配置 | | **PG 模式** | 三种数据库角色(`anon` / `authenticated` / `service_role`) + GRANT + 行级安全策略(RLS) | SQL 语句(同时支持控制台辅助配置) | :::info 模式选择 - **PG 模式**用户:完整的权限模型说明请阅读 [PG:身份认证](./auth-pg)(含三角色、GRANT、RLS、典型场景),本页**不适用** - **传统模式**用户:继续阅读下文 ::: ## 角色 + 策略(传统模式) CloudBase 权限控制通过角色体系实现精细化的权限管理,解决"用户能访问哪些资源"的问题。 前往 [云开发平台/身份认证/权限控制](https://tcb.cloud.tencent.com/dev#/identity/auth-control) 查看权限控制。 ### 权限模型 CloudBase 权限控制基于 **角色-策略** 模型: ``` 用户 → 角色 → 网关权限 + 资源权限 + 数据权限 ``` - **角色**:权限的载体,将用户分配到不同角色 - **网关权限**:在用户尝试访问资源时的第一道防线,鉴权时间早于资源健全,[查看网关权限控制](https://docs.cloudbase.net/authentication-v2/auth/auth-gateway) - **资源权限**:该角色可以访问哪些资源(数据模型、云函数、云存储等) - **数据权限**:可以访问哪些数据(全部数据、仅自己创建的数据、特定条件数据) ### 角色体系 #### 系统角色 CloudBase 提供五种内置系统角色,满足常见的权限场景: | 角色类型 | 说明 | 典型使用场景 | | ------------ | ------------------------------------ | ------------------------------------ | | **管理员** | 拥有所有权限,可管理环境、成员、资源 | 项目负责人、技术主管 | | **所有用户** | 包含所有已登录和未登录用户 | 公开内容访问控制 | | **组织成员** | 添加到[组织架构](https://tcb.cloud.tencent.com/dev?envId=#/identity/user-organization)的用户 | 企业内部应用访问 | | **注册用户** | 除组织成员外的非匿名登录用户,在[用户管理](https://tcb.cloud.tencent.com/dev?envId=#/identity/user-management)中查看 | C 端应用用户(如电商应用、社交应用) | | **匿名用户** | 使用[匿名登录](https://docs.cloudbase.net/authentication-v2/method/anonymous)的访客 | 公开页面、营销落地页 | > ⚠️ **安全警告**:管理员角色拥有系统**最高权限**(删除数据、修改配置、管理成员等),请谨慎使用。建议为日常操作创建权限受限的自定义角色,仅在必要时使用管理员账号。 #### 自定义角色 当系统角色无法满足业务需求时,可以创建自定义角色以实现更精细的权限控制。 **创建步骤:** 1. 访问 [云开发平台/身份认证/权限控制](https://tcb.cloud.tencent.com/dev#/identity/auth-control) 页面 2. 点击「创建角色」 3. 设置角色标识和描述(标识用于代码中引用) 4. 配置该角色的资源访问权限 创建自定义角色 **常见自定义角色示例:** | 角色标识 | 角色名称 | 适用场景 | | ----------------- | -------- | -------------------------- | | `content_editor` | 内容编辑 | 管理文章、商品等内容数据 | | `data_analyst` | 数据分析 | 查看统计数据和分析报表 | | `finance_admin` | 财务管理 | 管理订单、交易等财务数据 | | `customer_support`| 客服人员 | 处理用户反馈和工单 | ##### 多角色权限合并规则 当用户拥有多个角色时,最终权限按以下规则合并: - **允许权限取并集**:用户拥有所有角色的允许权限之和 - **拒绝权限优先**:任一角色的拒绝规则会覆盖其他角色的允许规则 **示例:** ``` 用户同时拥有:内容编辑 + 数据分析 角色权限: - 内容编辑:允许访问文章表(读、写、改) - 数据分析:允许访问订单表(仅读) 最终权限: - 可以读写文章表的所有数据 - 可以查看订单表的数据(仅读) ``` ### 成员管理 将用户添加到角色,实现权限分配。 #### 添加成员的两种方式 **方式一:在角色中添加成员** 1. 访问 [云开发平台/身份认证/权限控制](https://tcb.cloud.tencent.com/dev#/identity/auth-control) 页面 2. 找到目标角色,点击「配置成员」 3. 点击「添加成员」批量选择用户 4. 确认后立即生效 **方式二:在组织架构中管理** 1. 访问 [云开发平台/身份认证/组织架构](https://tcb.cloud.tencent.com/dev#/identity/user-organization) 页面 2. 创建部门和岗位结构 3. 将用户添加到对应的组织节点 4. 为用户批量关联角色 > 💡 **提示**:组织架构方式适合企业应用,可以基于部门和岗位批量管理用户权限。 #### 修改用户权限 修改用户权限有两种途径: **修改角色权限** - 在角色的权限配置中调整资源和数据权限 - 该角色下所有用户自动继承新权限 - 适合批量调整多个用户的权限 **调整用户角色** - 将用户从某个角色移除 - 或添加用户到新角色 - 适合单个用户的权限调整 > ⚠️ **注意**:权限变更可能需要一定时间生效,建议用户退出重新登录,立即应用新权限。 ### 权限配置 为角色配置具体的访问权限,包括资源权限、数据权限两个维度。 #### 资源权限 资源权限决定角色可以访问哪些 CloudBase 资源。 **配置步骤:** 1. 在角色卡片中点击「配置权限」 2. 选择「添加自定义策略」 3. 选择要授权的资源类型和具体资源 **支持的资源类型:** | 资源类型 | 说明 | 典型应用场景 | | -------------- | -------------------- | -------------------- | | 文档型数据库 | NoSQL 数据库权限配置,[文档型数据库权限说明](/database/data-permission) | 业务数据储存 | | MySQL 数据库 | 关系型数据库权限配置,[MySQL 数据库权限说明](/database/configuration/db/tdsql/data-permission) | 复杂业务数据 | | 云函数 | 处理后端业务逻辑,[云函数权限说明](/cloud-function/security-rules) | API 调用、数据处理 | | 云存储 | 文件存储,[云存储权限管理](/storage/data-permission) | 图片、视频、文档上传 | | 微搭应用 / 数据模型 / APIs / 工作台 | 可在权限控制页面直接进行管理, 前往[云开发平台/身份认证/权限控制](https://tcb.cloud.tencent.com/dev#/identity/auth-control),进入权限策略配置窗口,如下图 | 后台应用、内容管理、工作台(即[云后台](https://tcb.cloud.tencent.com/dev?envId=#/cloud-admin))等 | **权限策略配置窗口:** 资源权限配置界面 #### 数据权限 数据权限控制角色可以访问哪些数据,支持行级和列级两种维度。 ##### 行级权限(数据行) 控制用户可以访问哪些数据行,实现数据隔离。 **常见配置方式:** | 权限类型 | 说明 | 适用场景 | | -------------- | -------------------------- | ---------------------- | | 所有数据 | 访问全部数据行 | 管理员、数据分析人员 | | 仅自己的数据 | 只能访问自己创建的数据 | 普通用户、个人数据管理 | | 特定条件数据 | 满足特定条件的数据(如部门)| 部门经理、区域负责人 | **配置示例:** 数据行权限配置 **实际应用:** - 销售人员只能查看自己负责的客户数据 - 部门经理可以查看本部门的所有数据 - 区域经理可以查看所负责区域的数据 ##### 列级权限(数据列) 控制用户可以访问哪些字段(列),隐藏敏感信息。 **配置选项:** - **可读字段**:用户可以查看的字段列表 - **隐藏字段**:对用户不可见的敏感字段 **配置示例:** 数据列权限配置 **实际应用:** - 隐藏销售人员的成本和利润字段 - 隐藏客服人员的用户手机号和身份证号 - 隐藏普通员工的薪资和绩效数据 > 💡 **提示**:行级权限和列级权限可以同时使用,实现更精细的数据访问控制。 **配置说明:** 不同资源类型支持的操作权限可能有所差异,具体配置方式请参考对应的资源文档: ### 典型应用场景 #### 场景一:个人博客网站 ##### 需求分析 个人博客需要支持游客浏览、用户评论和博主管理三种角色。 ##### 角色规划 | 角色 | 权限范围 | 使用对象 | | -------- | ---------------------------- | ---------- | | 匿名用户 | 浏览文章、查看评论 | 所有访客 | | 注册用户 | 浏览文章、发表评论、点赞 | 注册读者 | | 管理员 | 发布文章、管理评论、网站设置 | 博客所有者 | ##### 数据权限配置 **文章表(`blog_articles`):** | 角色 | 查看权限 | 创建权限 | 修改权限 | 删除权限 | | -------- | ---------------- | -------- | ------------ | ------------ | | 匿名用户 | 已发布文章 | ❌ | ❌ | ❌ | | 注册用户 | 已发布文章+草稿 | ✅ | 仅自己的草稿 | 仅自己的草稿 | | 管理员 | 所有文章 | ✅ | ✅ | ✅ | **评论表(`comments`):** | 角色 | 查看权限 | 创建权限 | 修改权限 | 删除权限 | | -------- | -------- | -------- | -------------- | -------------- | | 匿名用户 | 所有评论 | ❌ | ❌ | ❌ | | 注册用户 | 所有评论 | ✅ | 仅自己的评论 | 仅自己的评论 | | 管理员 | 所有评论 | ✅ | ✅ | ✅ | ##### 实施步骤 **1. 启用匿名登录** 在 [登录方式管理](https://tcb.cloud.tencent.com/dev#/identity/login-manage) 开启「允许匿名登入」。 **2. 配置数据库权限** 在数据库集合的权限设置中配置对应角色的访问权限,参考 [文档型数据库权限](/database/data-permission)。 **3. 前端调用匿名登录** ```javascript import cloudbase from '@cloudbase/js-sdk'; const app = cloudbase.init({ env: 'your-env-id' }); const auth = app.auth(); // 页面加载时执行匿名登录 auth.signInAnonymously().then(() => { console.log('匿名登录成功'); }); ``` #### 场景二:企业内部管理系统 ##### 需求分析 企业管理系统需要区分不同部门和岗位的访问权限,实现数据隔离和精细化管理。 ##### 角色规划 **创建自定义角色:** 1. 访问 [权限控制](https://tcb.cloud.tencent.com/dev#/identity/auth-control) 页面 2. 点击「创建角色」创建以下角色: | 角色标识 | 角色名称 | 权限范围 | | ----------------- | -------- | -------------------------- | | `content_editor` | 内容编辑 | 管理文章、产品等内容数据 | | `data_analyst` | 数据分析 | 查看统计数据和分析报表 | | `finance_admin` | 财务管理 | 管理订单、交易等财务数据 | | `dept_manager` | 部门经理 | 管理本部门的数据和成员 | ##### 数据权限配置 **行级权限示例** 配置"用户只能访问自己部门的数据": 数据行权限配置 **列级权限示例** 隐藏敏感字段(如成本、利润): 数据列权限配置 ##### 实施步骤 **1. 创建组织架构** 在 [组织架构](https://tcb.cloud.tencent.com/dev#/identity/user-organization) 中创建部门结构。 **2. 配置角色权限** 为每个角色配置资源权限和数据权限(行级+列级)。 **3. 分配用户角色** 将用户添加到对应部门,并关联相应角色。 #### 场景三:协同编辑平台 ##### 需求分析 多人协同文档编辑平台,允许用户编辑他人创建的文档,需要严格的权限控制。 ##### 角色规划 | 角色 | 权限范围 | 使用对象 | | -------- | -------------------------------------- | ------------ | | 普通用户 | 查看所有文档、编辑自己的文档 | 注册用户 | | 协作者 | 查看所有文档、编辑被授权的文档 | 被邀请的用户 | | 管理员 | 所有文档的完整权限(查看、编辑、删除) | 团队管理者 | ##### 实现方案 > ⚠️ **安全警告**:允许修改他人数据是高风险操作,必须实施严格的权限验证。 **方案一:通过云函数修改数据(推荐)** 云函数在服务端执行,可以绕过客户端权限限制,但需要在函数内部实现严格的权限验证: ```javascript // 云函数:editDocument const cloudbase = require('@cloudbase/node-sdk'); const app = cloudbase.init(); const db = app.database(); exports.main = async (event, context) => { const { documentId, newContent } = event; const { userInfo } = context; // 1. 验证用户登录状态 if (!userInfo || !userInfo.uid) { return { code: 401, message: '未登录' }; } // 2. 查询文档信息 const doc = await db.collection('documents') .doc(documentId) .get(); if (!doc.data || doc.data.length === 0) { return { code: 404, message: '文档不存在' }; } const document = doc.data[0]; // 3. 权限验证:检查是否是作者或协作者 const isOwner = document._openid === userInfo.openid; const isCollaborator = document.collaborators && document.collaborators.includes(userInfo.uid); if (!isOwner && !isCollaborator) { return { code: 403, message: '无权编辑此文档' }; } // 4. 执行更新 const result = await db.collection('documents') .doc(documentId) .update({ content: newContent, lastEditBy: userInfo.uid, lastEditAt: new Date() }); return { code: 0, message: '更新成功', data: result }; }; ``` **方案二:通过安全规则配置(精细控制)** 对于需要更复杂、更精细的权限控制场景,可以使用安全规则功能。 **CloudBase 支持安全规则的资源:** | 资源类型 | 安全规则文档 | 适用场景 | | ------------ | ------------------------------------------------ | ---------------------------- | | 文档型数据库 | [数据库安全规则](/database/security-rules) | 文档级、字段级的权限控制 | | 云存储 | [云存储安全规则](/storage/security-rules) | 文件上传、下载、删除权限控制 | | 云函数 | [云函数安全规则](/cloud-function/security-rules) | 函数调用权限控制 | **使用安全规则实现协同编辑:** ```json { // 读权限:所有登录用户都可以查看文档 "read": "auth != null", // 写权限:作者、协作者或管理员可以编辑 "write": "doc._openid == auth.openid || doc.collaborators.includes(auth.uid) || auth.role == 'admin'" } ``` > 💡 **提示**:安全规则在数据库层面生效,无需编写云函数即可实现复杂的权限控制逻辑。 ### 安全最佳实践 #### 管理员权限的谨慎使用 > ⚠️ **安全警告**:管理员角色拥有系统**最高权限**(删除数据、修改配置、管理成员等),错误操作可能导致严重后果。 **建议做法:** 1. **最小化管理员数量**:仅为必要人员授予管理员权限 2. **创建日常角色**:为日常操作创建权限受限的自定义角色 3. **分离权限**:根据职责划分不同权限级别的角色 4. **定期审查**:定期检查管理员账号列表,移除不再需要的权限 5. **启用双因素认证**:启用[多因素认证验证](https://tcb.cloud.tencent.com/dev?envId=#/identity/mfa-management),保障账号安全 #### 权限最小化原则 遵循"最小权限原则",仅授予用户完成工作所需的最小权限。 **正确做法:** ``` ✅ 销售人员只能查看和编辑自己负责的客户数据 ✅ 客服人员只能查看工单信息,但无法查看用户的支付信息 ✅ 数据分析人员只能查看数据(只读权限),无法修改或删除 ``` **应避免的做法:** ``` ❌ 销售人员可以查看所有客户数据 ❌ 客服人员拥有用户信息的完整访问权限 ❌ 给普通员工授予管理员权限以"方便操作" ``` #### 敏感数据保护 对于敏感数据,建议采用多层防护: 1. **列级权限**:隐藏敏感字段(如身份证、银行卡号) 2. **数据脱敏**:在展示时对敏感信息进行脱敏处理 3. **审计日志**:记录所有对敏感数据的访问和修改操作 4. **定期审查**:定期审查敏感数据的访问权限配置 #### 权限变更管理 **权限变更前:** 1. 评估影响范围,确认变更不会影响业务正常运行 2. 在测试环境验证权限配置的正确性 3. 通知相关用户即将进行的权限调整 **权限变更后:** 1. 验证权限配置是否生效 2. 建议用户退出重新登录以立即应用新权限 3. 监控是否有权限相关的错误或异常 ### 常见问题 #### Q1: 用户已添加权限,但仍然无法访问资源? **排查步骤:** 1. **检查角色分配** - 确认用户已被正确分配到目标角色 - 在 [用户管理](https://tcb.cloud.tencent.com/dev#/identity/user-management) 中查看用户的角色列表 2. **检查策略配置** - 确认策略已保存并关联到正确的角色 - 在 [权限控制](https://tcb.cloud.tencent.com/dev#/identity/auth-control) 中查看角色的策略配置 3. **检查权限生效时间** - 权限变更可能需要一定时间生效 - 建议用户退出重新登录以立即应用新权限 4. **检查网关策略** - 可能被网关层的限制拦截 - 检查用户关联的网关权限策略 5. **查看具体错误信息** - 查看客户端或服务端的错误日志 - 根据错误码针对性解决问题 #### Q2: 多个角色的权限如何合并? 当用户拥有多个角色时,权限合并遵循以下规则: **权限合并规则:** - **允许权限取并集**:用户拥有所有角色的允许权限之和 - **拒绝权限优先**:任一角色的拒绝规则会覆盖其他角色的允许规则 **示例:** ``` 用户同时拥有:内容编辑 + 数据分析 角色权限: - 内容编辑:允许访问文章表(读、写、改) - 数据分析:允许访问订单表(仅读)、拒绝删除文章表 最终权限: - 可以读写文章表,但不能删除文章(拒绝规则优先) - 可以查看订单表的数据(仅读) ``` #### Q3: 如何实现"用户只能查看自己部门的数据"? 使用行级权限配置实现数据隔离: **实施步骤:** 1. 在数据表中添加 `department` 字段,记录数据所属部门 2. 在用户信息中添加 `department` 字段,记录用户所属部门 3. 配置数据权限时,设置行级权限规则:`doc.department == user.department` **参考配置:** 数据行权限配置 #### Q4: 安全规则和角色权限有什么区别? **角色权限(Role-based Access Control):** - 基于用户角色的权限管理 - 适合企业级应用,支持组织架构 - 在控制台可视化配置 - 适合大部分常见场景 **安全规则(Security Rules):** > 详细说明见:[数据库安全规则](/database/security-rules) - 基于表达式的文档级权限控制 - 支持更复杂的权限逻辑(如基于数据内容的权限判断) - 需要编写规则表达式 - 适合需要精细控制的场景 **推荐做法:** - 优先使用角色权限满足基本需求 - 对于复杂场景,结合安全规则实现更精细的控制 - 角色权限和安全规则可以同时使用,最终权限为两者的交集 #### Q5: 如何批量管理用户权限? **方法一:通过组织架构批量管理** 1. 在 [组织架构](https://tcb.cloud.tencent.com/dev#/identity/user-organization) 中创建部门结构 2. 将用户添加到对应部门 3. 为部门统一配置角色,该部门的所有用户自动继承角色权限 **方法二:通过修改角色权限** 1. 找到需要调整的角色 2. 修改该角色的权限配置 3. 该角色下的所有用户自动应用新权限 **方法三:使用 HTTP API 批量操作** 对于大规模用户权限管理,可以使用 CloudBase HTTP API 进行批量操作,参考 [HTTP API 文档](/http-api/auth/登录认证接口)。 ## 相关文档 **身份认证相关:** - [身份认证概述](/authentication-v2/auth/introduce) - 了解 CloudBase 身份认证体系 - [管理用户](/authentication-v2/auth/manage-users) - 用户信息管理和账号操作 - [登录方式管理](/authentication-v2/auth/manage-login) - 配置和启用不同的登录方式 - [JS SDK (V2) 身份认证](/api-reference/webv2/authentication) - Web 端身份认证 API - [HTTP API 登录认证接口](/http-api/auth/登录认证接口) - HTTP API 身份认证 **数据权限相关:** - [文档型数据库权限](/database/data-permission) - NoSQL 数据库权限管理 - [数据库安全规则](/database/security-rules) - 文档级权限控制 - [MySQL 数据库权限](/database/configuration/db/tdsql/data-permission) - 关系型数据库权限管理 - [PG 数据权限管理](/database/configuration/db/postgresql/data-permission) - PG 模式 RLS 权限管理 **资源权限相关:** - [云函数权限控制](/cloud-function/security-rules) - 云函数调用权限管理 - [云存储权限管理](/storage/data-permission) - 文件存储权限控制 --- # 身份认证/用户/权限/网关权限控制 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/auth-gateway 「网关权限」是云开发资源访问控制的第一道防线。当携带 [云开发用户身份](/authentication-v2/auth/manage-users) 的请求通过 **[HTTP API 域名](/http-api/basic/overview)、[HTTP 访问服务域名](/service/introduce) 或 [云托管域名](/run/develop/access/client)** 访问云开发资源(云函数、云托管、云存储、AI 能力等)时,请求首先到达网关层,由网关根据配置的策略决定: - **放行(allow)**:请求通过网关,进入资源层继续鉴权 - **拒绝(deny)**:请求被网关拦截,**不会**到达资源层 **鉴权顺序** 网关鉴权 **早于** 资源鉴权。[查看资源权限文档](https://docs.cloudbase.net/authentication-v2/auth/auth-control#%E8%B5%84%E6%BA%90%E6%9D%83%E9%99%90) 以云函数为例: | 网关策略 | 执行情况 | 结果 | | ---------- | -------------------------- | --------------------- | | **未放行** | 网关直接拒绝(403/无权限) | 函数**不会被执行** | | **已放行** | 请求到达云函数 | 继续资源层/业务层鉴权 | :::info 提示 网关权限只控制"能否访问",不控制"能做什么"。细粒度的数据权限应在资源层(如数据库安全规则)或业务层实现。 ::: ### 默认策略 平台默认为每个角色关联了一定的预设策略,使不同的角色通过不同的链路访问时默认拥有不同的访问权限。具体如下: | 资源类型 | 资源标识 | 管理员 | 组织成员 | 注册用户 | 匿名用户 | | -------------------------------------------------- | ----------- | :----: | :------: | :------: | :------- | | [云存储 API](/http-api/storage/云存储) | `storages` | ✅ | ❌ | ❌ | ❌ | | [云函数 API](/http-api/functions/云函数) | `functions` | ✅ | ❌ | ❌ | ❌ | | [云托管 API](/http-api/cloudrun/云托管) | `cloudrun` | ✅ | ❌ | ❌ | ❌ | | [知识库 API](/http-api/knowledge/知识库-openapi) | `knowledge` | ✅ | ❌ | ❌ | ❌ | | [大模型接入 API](/http-api/ai-model/ai-大模型接入) | `ai` | ✅ | ✅ | ✅ | ❌ | | [AI 智能体 API](/http-api/ai-bot/ai-agent-接入) | `aibot` | ✅ | ✅ | ✅ | ✅ | | [数据模型 API](/http-api/model/数据模型-openapi) | `model` | ✅ | ✅ | ✅ | ✅ | | [MySQL API](/http-api/mysqldb/mysql-restful-api) | `rdb` | ✅ | ✅ | ✅ | ✅ | :::info 提示 - ✅ 表示该角色具备该功能的默认权限,❌ 表示该角色不具备该功能的默认权限,如需调整权限,请参考下文中的方法为对应角色关联策略。 - [数据模型 API](/http-api/model/数据模型-openapi) 可参考 [数据权限管理](/model/data-permission) 进行权限控制,[MySQL API](/http-api/mysqldb/mysql-restful-api) 可参考 [MySQL 权限管理](/database/configuration/db/tdsql/data-permission) 进行权限控制, - 平台默认策略可能会根据实际情况进行调整。 ::: :::warning 匿名用户权限生效前提 匿名用户的网关权限要实际生效,请先确认环境已开启「匿名登录」方式,否则客户端无法获取匿名身份,相应权限也不会触发。开启方式参见 [匿名登录](../method/anonymous)。 ::: 通过 [HTTP 访问服务](/service/introduce) 为云资源配置访问路径后,如路径启用了[身份认证](/service/authentication),携带云开发用户身份访问资源时会受到网关权限控制。 **启用身份认证后的默认权限:** | 资源类型 | 资源标识 | 管理员 | 组织成员 | 注册用户 | 匿名用户 | | ---------------------------------------------- | ----------- | :---: | :------: | :------: | :------: | | [云函数](/service/access-cloud-function) | `functions` | ✅ | ✅ | ✅ | ❌ | | [静态托管存储](/service/access-static-hosting) | `storages` | ✅ | ✅ | ✅ | ❌ | | [云托管](/service/access-cloudrun) | `cloudrun` | ✅ | ✅ | ✅ | ❌ | :::info 提示 - ✅ 表示该角色具备该功能的默认权限,❌ 表示该角色不具备该功能的默认权限,如需调整权限,请参考下文中的方法为对应角色关联策略。 - 平台默认策略可能会根据实际情况进行调整。 ::: :::warning 匿名用户权限生效前提 匿名用户的网关权限要实际生效,请先确认环境已开启「匿名登录」方式,否则客户端无法获取匿名身份,相应权限也不会触发。开启方式参见 [匿名登录](../method/anonymous)。 ::: ### 配置入口 进入 [云开发平台/权限控制](https://tcb.cloud.tencent.com/dev?#/identity/auth-control),选择目标角色卡片的【配置权限】 权限控制 进入角色详情页 → 点击「添加自定义策略」 权限控制 资源类型选择「网关」 网关权限配置 ## 策略配置 网关权限支持两种策略配置方式:**预设策略**和**自定义策略**。预设策略为一系列策略鉴权模板,可直接关联到用户所属角色,使角色具备相应的权限。如预设策略不满足需求,可根据语法编写自定义策略实现更灵活的权限控制。 ### 预设策略 系统提供常用的预设策略,开箱即用,适合大多数场景。 **场景 1:开发测试阶段 - 管理员全权限** - **适用场景**:个人开发、快速验证功能 - **推荐策略**:`AdministratorAccess` - **优势**:无权限阻断,快速迭代 - **注意事项**:⚠️ 生产环境不要将此策略绑定到面向公网的应用身份 **场景 2:团队协作 - 按资源类型拆分** - **适用场景**:多人协作开发,按资源域分工 - **推荐策略**: - `StoragesAccess`:云存储访问权限 - `FunctionsAccess`:云函数访问权限 - `CloudrunAccess`:云托管访问权限 - **优势**:按资源隔离权限,降低误操作风险 **场景 3:对外服务 - HTTP 访问控制** - **适用场景**:通过 HTTP API 对外提供服务 - **推荐策略**: - `FunctionsHttpApiAllow` / `FunctionsHttpServiceAllow` - `CloudrunHttpApiAllow` - `StoragesHttpServiceAllow` - **最佳实践**:⚡ 先开放最小权限集合,再根据需求逐步扩展 ### 自定义策略 当预设策略无法满足需求时,可以自定义策略实现精细化权限控制。 **策略语法说明** 策略 `policy` 由版本 `version` 和语句 `statement` 构成。每条语句 `statement` 包含以下字段: | 字段 | 是否必填 | 说明 | | ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `effect` | 必填 | 策略效果:`allow`(允许)或 `deny`(拒绝) | | `action` | 必填 | 操作类型采用**四段式**格式:`资源标识:域名:HTTP方法:请求路径`

**格式说明:**
1. **资源标识**(必填):参考下方资源标识,如 `functions`、`storages`、`cloudrun` 等
2. **域名**(可选):实际访问的自定义域名,通过HTTP API 访问时域名统一为 `tcloudbasegateway`,省略时表示 `*`(所有域名)
3. **HTTP方法**(可选):大写的 HTTP 请求方法(如 `GET`、`POST`、`PUT`、`DELETE`),省略时表示 `*`(所有方法)
4. **请求路径**(必填):HTTP 请求路径,使用 `*` 通配符匹配。`*` 表示所有路径,`/path/*` 表示 `/path/` 下的所有路径

**示例:**
- `functions:*` → 允许所有云函数访问(省略域名和方法)
- `functions:/hello` → 仅允许访问路径 `/hello`
- `functions:/api/*` → 允许访问 `/api/` 下的所有路径(如 `/api/users`、`/api/orders`)
- `cloudrun:tcloudbasegateway:POST:/v1/cloudrun/*` → 允许通过指定域名 POST 方法访问云托管 API 下的所有路径
- `storages:*.tcloudbaseapp.com:GET:*` → 允许通过静态托管域名 GET 所有存储资源 | | `resource` | 必填 | 资源范围:固定填写 `*`(表示所有资源),细粒度控制通过 `action` 字段实现 | **资源标识** 通过开放 API 访问的资源类型及其标识: | 资源标识 | 标识 | 说明 | | ------------------------------------------------- | ----------- | ---------------- | | [云存储](/http-api/storage/云存储) | `storages` | 对象存储操作 | | [云函数](/http-api/functions/云函数) | `functions` | 云函数调用 | | [云托管](/http-api/cloudrun/云托管) | `cloudrun` | 云托管服务访问 | | [大模型](/http-api/ai-model/ai-大模型接入) | `ai` | AI 大模型接入 | | [AI 智能体](/http-api/ai-bot/ai-agent-接入) | `aibot` | AI 智能体服务 | | [数据模型](/http-api/model/数据模型-openapi) | `model` | 数据模型管理 | | [知识库](/http-api/knowledge/知识库-openapi) | `knowledge` | 知识库管理 | | [MySQL 访问](/http-api/mysqldb/mysql-restful-api) | `rdb` | MySQL 数据库访问 | 通过 HTTP 访问服务访问的资源类型及其标识: | 资源标识 | 标识 | 说明 | | ---------------------------------------------- | ----------- | ------------------------ | | [云函数](/service/access-cloud-function) | `functions` | 通过 HTTP 路径访问云函数 | | [静态托管存储](/service/access-static-hosting) | `storages` | 静态网站托管、文件访问 | | [云托管](/service/access-cloudrun) | `cloudrun` | 容器服务访问 | **策略结构示例(允许访问所有云函数)** ```json { "version": "1.0", "statement": [ { "effect": "allow", "action": "functions:*", "resource": "*", } ] } ``` **策略优先级规则** 当同一请求匹配多个策略时: - ❌ `deny` **优先于** ✅ `allow` - 未匹配任何策略时,默认拒绝访问 :::warning 策略优先级 当 `allow` 语句和 `deny` 语句同时存在时,遵循 **`deny` 优先原则**。 ::: **常见自定义策略示例** import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; 允许通过 HTTP 访问路径为 `/hello` 的云函数: ```json { "version": "1.0", "statement": [ { "effect": "allow", "action": "functions:/hello", "resource": "*" } ] } ``` 禁止访问特定的敏感云函数: ```json { "version": "1.0", "statement": [ { "effect": "deny", "action": "functions:/dangerousFunction", "resource": "*" } ] } ``` 允许访问所有云存储资源: ```json { "version": "1.0", "statement": [ { "effect": "allow", "action": "storages:*", "resource": "*" } ] } ``` 允许访问所有 AI 大模型资源: ```json { "version": "1.0", "statement": [ { "effect": "allow", "action": "ai:*", "resource": "*" } ] } ``` 允许访问所有云函数,但禁止访问特定敏感函数(`deny` 优先级更高): ```json { "version": "1.0", "statement": [ { "effect": "allow", "action": "functions:*", "resource": "*" }, { "effect": "deny", "action": "functions:/admin", "resource": "*" } ] } ``` ## 最佳实践 ### 最小权限原则 遵循「最小权限原则」(Least Privilege),按实际需要授权: | 身份类型 | 推荐策略 | 说明 | | ------------- | --------------------- | ------------------------------- | | 开发者/管理员 | `AdministratorAccess` | 开发阶段可使用高权限,提升效率 | | 生产环境应用 | 自定义策略 | 仅授予必需的 `action` | | 自动化服务 | 最小权限集合 | CI/CD、定时任务等仅开放所需接口 | ### 分层防护策略 安全防护应该在多个层次实施,而非依赖单一机制: ``` ┌─────────────────────────────────────────┐ │ 网关层(网关权限) │ ← 控制"能否访问" │ • 决定请求能否进入资源层 │ └─────────────────────────────────────────┘ ↓ 放行 ┌─────────────────────────────────────────┐ │ 资源层(安全规则) │ ← 控制"能做什么" │ • 数据库安全规则 │ │ • 云存储访问控制 │ │ • 云函数认证配置 │ └─────────────────────────────────────────┘ ↓ 通过 ┌─────────────────────────────────────────┐ │ 业务层(应用鉴权) │ ← 控制"细粒度权限" │ • 用户角色校验 │ │ • 数据权限过滤 │ │ • 业务规则限制(额度、风控等) │ └─────────────────────────────────────────┘ ``` ### 策略配置建议 **1. 优先使用预设策略** - ✅ 预设策略经过充分测试,更规范、不易出错 - ⚠️ 自定义策略需充分测试后再投入生产使用 **2. 使用 deny 做全局兜底** 当需要"全局禁止某类危险操作"时,使用 `deny` 策略(因为 deny 优先级高于 allow): ```json { "effect": "deny", "action": "functions:*", "resource": "*" } ``` **3. 定期审查权限配置** - 定期检查各角色的权限配置是否合理 - 及时回收不再需要的权限 - 关注异常访问日志,调整策略 ### 故障排查路径 遇到权限问题时,按以下顺序排查: | 现象 | 排查重点 | 处理方向 | | ------------------------------------------------------------------------------- | -------------- | ---------------------------------- | | 请求返回 403,请求体内错误码为 [ACTION_FORBIDDEN](/error-code/ACTION_FORBIDDEN) | 网关策略 | 检查网关权限配置,确认策略是否放行 | | 请求进入资源,但执行失败 | 资源层安全规则 | 检查数据库安全规则、云存储权限等 | | 资源层通过,业务逻辑报错 | 业务层鉴权 | 检查应用代码中的权限校验逻辑 | :::tip 调试技巧 可以临时将角色策略改为 `AdministratorAccess`,如果问题消失,说明是网关权限配置问题;如果问题依然存在,则需要检查资源层或业务层。 ::: --- # 身份认证/配置/登录方式 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/manage-login **CloudBase** 提供多种身份认证方式,在使用前需要先开启所需的登录方式。您可以通过控制台或 SDK 两种方式来管理登录方式的开启和关闭。 ## 前提条件 - 已创建云开发环境 - 了解不同登录方式的适用场景,详见 [支持的登录方式](/authentication-v2/auth/introduce#支持的登录方式) ## 通过控制台管理 ### 开启登录方式 1. 前往 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 2. 在登录方式列表中找到需要开启的登录方式 3. 点击对应登录方式的**启用**按钮 4. 根据提示完成必要的配置(如微信登录需要配置 AppID 和 AppSecret) ![](https://qcloudimg.tencent-cloud.cn/raw/5c521ce653c2ff2246cb40d38379e250.png) ### 关闭登录方式 在登录方式列表中找到需要关闭的登录方式,点击**停用**按钮即可。 > ⚠️ 注意:关闭登录方式后,使用该方式的用户将无法登录,请谨慎操作。 ## 通过 SDK 管理 ### 适用场景 - 需要批量管理多个环境的登录方式 - 需要在自动化流程中管理登录配置 - 需要在后台服务中动态控制登录方式 ### 支持的登录方式 通过腾讯云 SDK([tencentcloud-sdk](https://cloud.tencent.com/document/sdk))管理登录方式,支持 Java、Go、JavaScript 等多种语言。 :::tip 版本说明 腾讯云 SDK 方式开启/关闭登录方式,**仅影响云开发平台身份认证 v2 的登录方式**,不会同步开启/关闭旧版控制台 v1 的登录方式。 如需管理 v1 登录方式,请前往云开发控制台手动操作。 ::: ### 参数说明 | 参数名称 | 说明 | 参数值 | 必填 | | ---------------- | ------------------ | ------------------------------------------ | ---- | | EnvId | 云开发环境 ID | 环境 ID 字符串 | 是 | | PhoneNumberLogin | 手机号验证码登录 | TRUE(开启)/ FALSE(关闭),仅支持上海地域 | 否 | | AnonymousLogin | 匿名登录 | TRUE(开启)/ FALSE(关闭) | 否 | | UsernameLogin | 用户名密码登录 | TRUE(开启)/ FALSE(关闭) | 否 | ### 请求示例 ```json { "EnvId": "your-env-id", "PhoneNumberLogin": "TRUE", "AnonymousLogin": "TRUE", "UsernameLogin": "TRUE" } ``` ### 代码示例 了解更多使用方式请访问 [腾讯云 API 调试工具](https://console.cloud.tencent.com/api/explorer?Product=tcb&Version=2018-06-08&Action=EditAuthConfig)。 ```javascript const tencentcloud = require("tencentcloud-sdk-nodejs-tcb"); const TcbClient = tencentcloud.tcb.v20180608.Client; // 实例化认证对象 // 密钥可前往 https://console.cloud.tencent.com/cam/capi 获取 const clientConfig = { credential: { secretId: "your-secret-id", // 替换为您的 SecretId secretKey: "your-secret-key", // 替换为您的 SecretKey }, region: "ap-shanghai", // 地域参数 profile: { httpProfile: { endpoint: "tcb.tencentcloudapi.com", }, }, }; // 实例化客户端 const client = new TcbClient(clientConfig); // 配置登录方式参数 const params = { EnvId: "your-env-id", // 替换为您的环境 ID PhoneNumberLogin: "TRUE", // 开启手机号验证码登录 AnonymousLogin: "TRUE", // 开启匿名登录 UsernameLogin: "TRUE", // 开启用户名密码登录 }; // 调用接口 client.EditAuthConfig(params).then( (data) => { console.log("配置成功:", data); }, (err) => { console.error("配置失败:", err); } ); ``` ```go package main import ( "fmt" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" tcb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcb/v20180608" ) func main() { // 实例化认证对象 // 密钥可前往 https://console.cloud.tencent.com/cam/capi 获取 credential := common.NewCredential( "your-secret-id", // 替换为您的 SecretId "your-secret-key", // 替换为您的 SecretKey ) // 实例化客户端配置 cpf := profile.NewClientProfile() cpf.HttpProfile.Endpoint = "tcb.tencentcloudapi.com" // 实例化客户端 client, _ := tcb.NewClient(credential, "ap-shanghai", cpf) // 创建请求对象 request := tcb.NewEditAuthConfigRequest() // 配置登录方式参数 envId := "your-env-id" // 替换为您的环境 ID phoneLogin := "TRUE" // 开启手机号验证码登录 anonymousLogin := "TRUE" // 开启匿名登录 usernameLogin := "TRUE" // 开启用户名密码登录 request.EnvId = &envId request.PhoneNumberLogin = &phoneLogin request.AnonymousLogin = &anonymousLogin request.UsernameLogin = &usernameLogin // 调用接口 response, err := client.EditAuthConfig(request) if _, ok := err.(*errors.TencentCloudSDKError); ok { fmt.Printf("配置失败: %s\n", err) return } if err != nil { panic(err) } // 输出结果 fmt.Printf("配置成功: %s\n", response.ToJsonString()) } ``` ```java package com.tencent; import com.tencentcloudapi.common.AbstractModel; import com.tencentcloudapi.common.Credential; import com.tencentcloudapi.common.profile.ClientProfile; import com.tencentcloudapi.common.profile.HttpProfile; import com.tencentcloudapi.common.exception.TencentCloudSDKException; import com.tencentcloudapi.tcb.v20180608.TcbClient; import com.tencentcloudapi.tcb.v20180608.models.*; public class Sample { public static void main(String [] args) { try { // 实例化认证对象 // 密钥可前往 https://console.cloud.tencent.com/cam/capi 获取 Credential cred = new Credential( "your-secret-id", // 替换为您的 SecretId "your-secret-key" // 替换为您的 SecretKey ); // 实例化 HTTP 配置 HttpProfile httpProfile = new HttpProfile(); httpProfile.setEndpoint("tcb.tencentcloudapi.com"); // 实例化客户端配置 ClientProfile clientProfile = new ClientProfile(); clientProfile.setHttpProfile(httpProfile); // 实例化客户端 TcbClient client = new TcbClient(cred, "ap-shanghai", clientProfile); // 创建请求对象并配置参数 EditAuthConfigRequest req = new EditAuthConfigRequest(); req.setEnvId("your-env-id"); // 替换为您的环境 ID req.setPhoneNumberLogin("TRUE"); // 开启手机号验证码登录 req.setAnonymousLogin("TRUE"); // 开启匿名登录 req.setUsernameLogin("TRUE"); // 开启用户名密码登录 // 调用接口 EditAuthConfigResponse resp = client.EditAuthConfig(req); // 输出结果 System.out.println("配置成功: " + AbstractModel.toJsonString(resp)); } catch (TencentCloudSDKException e) { System.out.println("配置失败: " + e.toString()); } } } ``` > 💡 **安全提示**:代码中的 SecretId 和 SecretKey 仅用于示例说明,实际使用时请通过环境变量或密钥管理服务等安全方式存储和使用密钥。详见 [密钥安全最佳实践](https://cloud.tencent.com/document/product/1278/85305)。 ## 注意事项 - **影响范围**:关闭登录方式后,已使用该方式登录的用户将无法再次登录,现有登录状态不受影响 - **地域限制**:手机号验证码登录目前仅支持上海地域 - **密钥安全**:使用 SDK 方式时,请妥善保管 SecretId 和 SecretKey,避免泄露 - **版本兼容**:SDK 方式管理的是云开发平台身份认证 v2 版本,与旧版控制台 v1 独立管理 ## 常见问题 **Q: 关闭登录方式后,已登录的用户会受影响吗?** A: 不会。关闭登录方式只会阻止新的登录请求,已登录用户的访问令牌在有效期内可以正常使用。 **Q: 为什么 SDK 开启的登录方式在旧版控制台看不到?** A: SDK 管理的是云开发平台身份认证 v2 的登录方式,与旧版控制台 v1 是独立的系统,需要分别管理。 **Q: 可以同时开启多种登录方式吗?** A: 可以。您可以根据业务需求同时开启多种登录方式,用户可以选择任意一种方式进行登录。 ## 相关文档 - [支持的登录方式](/authentication-v2/auth/introduce#支持的登录方式) - [管理用户](/authentication-v2/auth/manage-users) - [账户关联](/authentication-v2/auth/account-linking) - [权限控制](/authentication-v2/auth/auth-control) --- # 身份认证/配置/Token 管理 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/token 云开发身份认证基于 **Refresh Token** 和 **Access Token** 双令牌机制实现用户会话管理。您可以灵活配置令牌过期策略和会话并发数,在安全性与用户体验之间取得最佳平衡。 :::info PG 模式 本页 Token 配置(双令牌、有效期、最大会话数)适用于**两种模式下的用户登录态**。 PG 模式下还存在另外两种凭证——**Publishable Key** 与 **API Key**——它们本质都是 JWT,没有过期时间。完整说明见 [PG:身份认证 - 凭证与 JWT](./auth-pg#jwt)。 ::: ## 配置参数说明 | **参数** | **默认值** | **可配置范围** | **说明** | | :----------------------: | :--------: | :------------: | :-------------------------------------------------------------------------------: | | **Refresh Token 有效期** | 30 天 | 1 小时 - 30 天 | 超过有效期后无法获取新 Access Token,需重新登录 | | **Access Token 有效期** | 2 小时 | 1 - 24 小时 | 过期后需使用 Refresh Token 换取新令牌,无需重新登录。 | | **最大会话数** | 1 个 | 1 - 100 个 | 当会话数超过设定阈值时,系统将自动使最早的 Refresh Token 失效,有效防止账号滥用。 | ## 配置操作 前往 [云开发平台/身份认证/Token 管理](https://tcb.cloud.tencent.com/dev?envId#/identity/token-management) 调整您的令牌策略。 > 💡 **提示**:配置修改后立即生效,但不影响已颁发的令牌,只对新登录的用户生效。 ![](https://qcloudimg.tencent-cloud.cn/raw/3af3400259bec571dc101753799db882.png) ## 最佳实践建议 - **管理类应用**: 建议将 **Access Token** 有效期 设为 `1 小时`,最大会话数设为 `1`,以降低令牌泄露风险。 - **内容/社交类应用**: 建议延长 **Refresh Token** 至 `30 天`,提供无缝的长期登录体验。 --- # 身份认证/注册登录/SDK 初始化 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/sdk-init :::caution 版本说明 - **登录认证(v2)** 适用于 `@cloudbase/js-sdk@2.x` 版本 - 如您使用的 SDK 版本为 1.x,请参考 **[登录认证(v1)](/authentication/auth/introduce)** - **v2 版本暂不支持公众号登录方式**,如需使用该方式,请用 [v1 版本](/authentication/method/wechat-login) ::: 本文将以 **JS SDK(V2)** 版本进行介绍,HTTP API 请参考 [HTTP API/登录认证](/http-api/auth/auth-sign-in) ## 安装 SDK 在您的项目中引入 `@cloudbase/js-sdk@2.x` ```bash npm install --save @cloudbase/js-sdk ``` ## 初始化 SDK ```js import cloudbase from '@cloudbase/js-sdk'; const app = cloudbase.init({ env: 'your-env-id', region: 'ap-shanghai', // 不传默认为上海地域 }); // 获取 auth 实例 const auth = app.auth(); ``` ## 添加安全域名(可选) Web 应用需要将应用域名添加到 [云开发平台/安全来源列表](https://tcb.cloud.tencent.com/dev?#/env/safety-source) 中,否则将被识别为非法来源,导致跨域问题 **例子:** - 本地开发环境为 `localhost:3000` 时,需要配置安全域名 `localhost:3000` - 本地开发环境为 `127.0.0.1:8080` 时,需要配置安全域名 `127.0.0.1:8080` - 线上域名为 `www.example.com` 时,需要配置安全域名 `www.example.com` ![添加安全域名](https://cloudcache.tencent-cloud.com/qcloud/ui/static/static_source_business/69fa96ef-c310-447c-b1a4-a352163bb268.png) --- # 身份认证/注册登录/用户注册 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/signup 目前支持 **手机号验证码、邮箱验证码** 注册 两种注册方式开发只在 **发送验证码** 时有所区别,后续校验步骤均一致 > 💡 注意:注册用户均为注册用户权限,组织成员注册API请参考 [用户管理API](/lowcode/manage/auth) ### 发送验证码 :::tip 提示 `手机号验证码` 仅支持 `上海` 地域 ::: ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", region: "ap-shanghai", // 不传默认为上海地域 }); // 获取 auth 实例 const auth = app.auth(); // 发送手机号验证码 const phoneNumber = "+86 13800000000"; // 需要加区号 const verification = await auth.getVerification({ phone_number: phoneNumber, }); ``` ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", region: "ap-shanghai", // 不传默认为上海地域 }); // 获取 auth 实例 const auth = app.auth(); // 发送邮箱验证码 const email = "test@example.com"; const verification = await auth.getVerification({ email: email, }); ``` ### 验证并注册 获取到验证码后续步骤 ```js // 1. 验证码验证 // 假设这里收到用户填写的验证码"000000" const verificationCode = "000000"; // 验证验证码的正确性 const verificationTokenRes = await auth.verify({ verification_id: verification.verification_id, verification_code: verificationCode, }); // 2. 注册 // 如果该用户已经存,则登录 if (verification.is_user) { await auth.signIn({ username: phoneNumber, verification_token: verificationTokenRes.verification_token, }); } // 否则,则注册新用户,注册新用户时,可以设置密码,用户名 else { // 备注:signUp 成功后,会自动登录 await auth.signUp({ phone_number: phoneNumber, verification_code: verificationCode, verification_token: verificationTokenRes.verification_token, // 可选,设置昵称 name: "手机用户", // 可选,设置密码 password: "password", // 可选,设置登录用户名 username: "username", }); } ``` :::tip 用户名规则 1. 可以包含数字和字母,但是不允许是纯数字 2. 符号只允许出现 `-` 和 `_`,不允许这两个符号出现在开头和结尾 3. 长度范围是 `[1, 32]` ::: 注册成功,即可在 [云开发平台/身份认证/用户管理](https://tcb.cloud.tencent.com/dev?#/identity/user-management) 中可以查看和管理用户信息 ![](https://qcloudimg.tencent-cloud.cn/raw/e4184ac2ab1e553e7c74089c822a6a63.png) --- # 身份认证/注册登录/匿名登录 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/anonymous 在匿名登录状态下可正常调用 CloudBase 的资源,开发者可以配合安全规则针对匿名用户制定对应的访问限制。 ## 前置动作 ### 开启匿名登录 1. 前往 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 2. 在登录方式列表中,选择「匿名登录」方式,点击开启 ## 登录流程 `auth` 实例请参考 [SDK 初始化](./sdk-init) [`Auth.signInAnonymously`](/api-reference/webv2/authentication#authsigninanonymously) 用于 **匿名登录** ```js await auth.signInAnonymously(); const loginScope = await auth.loginScope(); // 如为匿名登录,则输出 true console.log(loginScope === "anonymous"); ``` --- # 身份认证/注册登录/账号密码登录 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/username-login 用户可以选择 `手机号`、`邮箱`、`用户名` 作为账号,使用密码进行登录 ## 前置动作 ### 开启用户名密码登录 1. 前往 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 2. 在登录方式列表中,选择「用户名密码登录」方式,点击开启 ## 注册用户 注册用户流程请参考 [用户注册](/authentication-v2/method/signup) 。 ## 登录流程 [`Auth.signIn`](/api-reference/webv2/authentication#authsignin) 用于 **账号密码登录** username 可填:`手机号`、`邮箱`、`用户名` ```js const loginState = await auth.signIn({ username: "your username", password: "your password", }); ``` 当前支持 **手机号验证码、邮箱验证码** 注册,需要在注册过程中设置用户名,后续才能使用用户名进行登录 :::tip 为什么必须先使用其他方式注册,再绑定用户名? 用户名可以是符合规则的任意字符串,为了避免您的应用被恶意者注册过多无效的用户名,CloudBase 目前不允许直接使用「用户名+密码」的形式注册用户。 ::: --- # 身份认证/注册登录/短信验证码登录 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/sms-login :::tip 提示 短信验证码登录仅支持 `上海` 地域 ::: 使用短信验证码登录,用户可以通过「手机号 + 短信验证码」的方式快速登录,无需记忆密码。 ## 前置动作 ### 开启短信验证码登录 1. 前往 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 2. 在登录方式列表中,找到「短信验证码登录」,点击开启 ## 注册用户 注册用户流程请参考 [用户注册](/authentication-v2/method/signup)。 ## 登录流程 [`Auth.signInWithSms`](/api-reference/webv2/authentication#authsigninwithsms) 方法用于**短信验证码登录**。 ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const phoneNumber = "+86 13800000000"; // 需要加区号 // 第一步:用户点击获取验证码,发送短信验证码 // 将 verificationInfo 存储到全局变量,方便第三步使用 const verificationInfo = await auth.getVerification({ phone_number: phoneNumber, }); // 第二步:等待用户在页面中输入收到的短信验证码 const verificationCode = userInputCode; // 用户输入的 6 位验证码 // 第三步:验证短信验证码并登录 await auth.signInWithSms({ verificationInfo, verificationCode, // 用户输入的短信验证码 phoneNum: phoneNumber, // 用户手机号 }); ``` :::tip 手机号格式说明 手机号需要加上区号,例如中国大陆手机号格式为 `+86 13800000000` ::: ## 使用限制及费用 ### 免费额度 - 云开发预付费环境享有首月 **100 条** 的免费额度 - 超出免费额度后,开发者可以前往 [云开发平台/资源包](https://console.cloud.tencent.com/tcb/env/resource) 购买短信资源包 ### 频率限制 短信下发存在以下频率限制: | 限制类型 | 限制规则 | 说明 | | -------------- | -------------------- | ------------------ | | 单号码发送频率 | 30 秒内最多发送 1 条 | 防止恶意刷短信 | | 单号码日发送量 | 默认最多 30 条/天 | 可在控制台修改上限 | 您可以在 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 中选择「短信验证码」进行修改。 ## 自定义短信模板 使用云开发默认短信资源包时,短信模板内容不支持修改。如果您有以下需求: - 自定义短信验证码模板内容 - 自定义短信签名 - 使用其他短信服务商 可以接入外部短信通道。 ### 接入步骤 **第 1 步:在外部短信服务平台配置短信模板和签名** 在外部短信服务平台进行模板和签名配置。 **第 2 步:接入云开发平台** 在 [云开发平台/扩展功能/新建 APIs](https://tcb.cloud.tencent.com/dev?#/apis/datasource/connector/detail) 中进行接入配置。 以接入腾讯云短信服务为例,可参考此流程指引文档:[腾讯云短信](https://cloud.tencent.com/document/product/1301/126225) **第 3 步:启用自定义通道** 配置完成后,在 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) → 短信验证码,选择「自定义短信通道」并勾选,即可使用自定义短信模板。 --- # 身份认证/注册登录/邮箱验证码登录 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/email-login 使用邮箱验证码登录,您可以让用户通过「邮箱+邮箱验证码」的方式登录 ## 前置动作 ### 开启邮箱登录 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; CloudBase 提供了 **零配置邮件服务**,无需申请第三方邮件服务或配置 SMTP 参数,1 分钟即可完成配置。 **配置步骤:** 1. 前往 [云开发平台 / 身份认证 / 登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 2. 找到「邮箱验证码」卡片,点击「配置发件邮箱」 3. 选择「**开启邮件代发**」 4. 保存配置,立即生效 配置后,用户收到的邮件效果: 如果需要使用企业自有邮件服务或自定义发件人信息,可以配置 SMTP 参数。 **配置步骤:** 1. 前往 [云开发平台 / 身份认证 / 登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 2. 找到「邮箱验证码」卡片,点击「配置发件邮箱」 3. 选择「自定义 SMTP」,填写邮件服务器配置 4. 保存配置 **常见邮箱 SMTP 配置参考:** | 邮箱 | SMTP 服务器主机 | SMTP 服务器端口 | SMTP 安全模式 | | ------------ | ------------------ | --------------- | ---------------------- | | qq 邮箱 | smtp.qq.com | 465/587 | SSL(465)/STARTTLS(587) | | 腾讯企业邮箱 | smtp.exmail.qq.com | 465 | SSL | | 163 邮箱 | smtp.163.com | 465 | SSL | | gmail | smtp.gmail.com | 465/587 | SSL(465)/STARTTLS(587) | 详细的 SMTP 配置指南请参考 [自定义 SMTP 配置指南](#自定义-smtp-配置指南) 章节。 ## 注册用户 注册用户流程请参考 [用户注册](/authentication-v2/method/signup) 。 ## 邮箱验证码登录 [`Auth.signInWithEmail`](/api-reference/webv2/authentication#authsigninwithemail) 方法用于 **邮箱验证码登录** ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const email = "test@example.com"; // 第一步:用户点击获取验证码,调用如下方法发送邮箱验证码,将 verificationInfo 存储到全局,方便第三步作为参数输入 const verificationInfo = await auth.getVerification({ email: email, }); // 第二步:等待用户在页面中输入邮箱验证码 const verificationCode = userInputCode; // 6位验证码 // 第三步:待用户输入完验证码之后,验证邮箱验证码,并登录 await auth.signInWithEmail({ verificationInfo, verificationCode, // 用户输入的邮箱验证码 email: email, // 用户邮箱 }); ``` ## 自定义 SMTP 配置指南 如果您选择使用自定义 SMTP 邮件服务,本章节将帮助您完成常见邮箱服务的配置。 ### QQ 邮箱 #### 第 1 步:登录 QQ 邮箱 进入 [QQ 邮箱首页](https://mail.qq.com/),登录您的 QQ 邮箱。 #### 第 2 步:开启 IMAP/SMTP 服务 登录邮箱后,进入「设置-账户」: 然后,在「账户」设置中,找到「开启服务」设置项,开启 IMAP/SMTP 服务: 开启成功后,请保存您的邮箱登录授权码: :::tip 注意 您也可以开启 POP3/SMTP 服务,两种服务的授权码都可以作为第 3 步的 SMTP 账号密码。 ::: #### 第 3 步:配置 QQ 邮箱作为发件人 使用 QQ 邮箱作为发件人地址和 SMTP 账号用户名,使用第 2 步的授权码作为 SMTP 账号密码。 ### Gmail 邮箱 #### 第 1 步:登录 Gmail 邮箱 Gmail 邮箱默认已开启 IMAP/SMTP 服务,此步骤验证邮箱是否可用。 #### 第 2 步:开启 Google 账号两步验证 访问 [Google 账号安全设置](https://myaccount.google.com/security?utm_source=OGB&utm_medium=app),点击「安全(Security)」→「两步验证(2-Step Verification)」进行开启。 #### 第 3 步:开启 Google 账号应用专用密码 访问 [应用专用密码设置](https://support.google.com/accounts/answer/185833?hl=zh-Hans),生成应用专用密码。 #### 第 4 步:配置云开发平台邮箱登录 SMTP | 配置项 | 配置值 | | --------------- | ------------------ | | 发件人 | 你的 Gmail 邮箱 | | SMTP 服务器主机 | smtp.gmail.com | | 端口 | 465 | | SMTP 账号用户名 | 你的 Gmail 邮箱 | | SMTP 账号密码 | 第 3 步的应用专用密码 | | SMTP 安全模式 | SSL | --- # 身份认证/注册登录/微信授权登录 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/wechat-login 经微信开放平台(普通网站应用及移动应用等)授权的应用可以直接使用微信登录 CloudBase。 ## 前置条件 ### 开启微信登录 1. 首先需要一个开放平台的注册账号,如果没有,请前往 [微信开放平台](https://open.weixin.qq.com/) 申请 2. 前往 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 3. 在登录方式列表中,选择「微信开放平台登录」方式,点击「去设置」填入 `AppId` 和 `AppSecret` ## 登录流程 ### 获取第三方平台授权页地址 填入 [`微信平台 ID`](/api-reference/webv2/authentication#系统内置三方列表)、`重定向 URI`、`自定义状态标识` 字段用于识别平台回调来源。 [`Auth.genProviderRedirectUri`](/api-reference/webv2/authentication#authgenproviderredirecturi) 用于生成第三方平台授权页 URL ```js const { uri } = await auth.genProviderRedirectUri({ provider_id: "wx_open", // 微信开放平台 provider_redirect_uri: providerUri, // 重定向 URI state: state, // 自定义状态标识 other_params: otherParams, // 其他参数 }); ``` ### 访问 URI 并进行授权后,回调至指定地址,获取第三方平台 Token 将用户重定向至 URI,例如 `location.href = uri`;待用户授权完毕后,回调至指定的 `provider_redirect_uri` 地址。 ```js // 此时 URL query 中携带授权 code,state 等参数 // 1. 检查 state 是否符合预期(如设置的 wx_open) // 2. 获取第三方平台跳转回页面时,URL param 中携带的 code 参数 const provider_code = "your_provider_code"; // 3. 如符合预期,获取第三方平台 Token const { provider_token } = await auth.grantProviderToken({ provider_id: "wx_open", provider_redirect_uri: "curpage", // 指定三方平台跳回的 URL 地址 provider_code: provider_code, // 第三方平台跳转回页面时,URL param 中携带的 code 参数 }); ``` ### 通过第三方平台 Token 登录 [`Auth.signInWithProvider`](/api-reference/webv2/authentication#authsigninwithprovider) 方法用于 **第三方平台登录** ```js await auth.signInWithProvider({ provider_token: provider_token, }); ``` ## 注册流程 使用微信授权登录时,用户在第一次使用微信登录时开发者需要使用[Auth.bindWithProvider](/api-reference/webv2/authentication#authbindwithprovider)进行用户信息创建。 具体流程如下: 1. 用户第一次使用微信登录时,调用[signInWithProvider](/authentication-v2/method/wechat-login#%E9%80%9A%E8%BF%87%E7%AC%AC%E4%B8%89%E6%96%B9%E5%B9%B3%E5%8F%B0-token-%E7%99%BB%E5%BD%95)会返回`not_found`错误码 2. 使用其他方式注册用户(例如手机验证码、用户名密码、邮箱验证码等),然后调用[Auth.bindWithProvider](/api-reference/webv2/authentication#authbindwithprovider)将用户的微信信息(如昵称、头像等)同步到 CloudBase 用户资料中,后续使用相同微信账号登录时,会直接登录到对应的 CloudBase 用户 3. 让用户再次触发[登录流程](/authentication-v2/method/wechat-login#%E7%99%BB%E5%BD%95%E6%B5%81%E7%A8%8B) ```js try { await auth.signInWithProvider({ provider_token: provider_token, }); } catch (e) { // 未关联,去绑定用户 if (e.error === "not_found") { // 使用其他方式注册用户 // 绑定用户 await auth.bindWithProvider({ provider_token: provider_token, }); // 绑定成功,重新让用户进行登录 } else { throw e; } } ``` --- # 身份认证/注册登录/Google 登录 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/google-login :::tip 平台支持 Google 社交登录目前仅支持 Web 端(PC/H5),暂不支持小程序端。 ::: 使用 Google 账号快速登录您的应用或网站,无需单独注册账号 ## 效果预览 **登录页展示** ![Google 登录页效果](https://qcloudimg.tencent-cloud.cn/raw/b202d6c371c82a24eaa420ab73a98413.png) **登录授权页** 用户点击 Google 登录后,将跳转至 Google 授权页面: ![Google 授权页效果](https://qcloudimg.tencent-cloud.cn/raw/784b415785f683a42998b3a38f473339.png) ## 配置指南 Google 登录配置需要在「云开发控制台」和「Google 开发者控制台」之间来回操作,建议按照以下步骤完成配置: ### 配置流程概览 ``` 云开发控制台(获取回调地址)→ Google 控制台(创建应用并配置回调地址)→ 云开发控制台(完成配置) ``` ### 步骤 1:在云开发控制台获取回调地址 #### 1.1 进入身份认证配置页面 1. 登录 [云开发控制台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage) 2. 找到「Google Web 端」卡片,点击「配置」按钮 ![云开发平台-身份认证-google登录](https://qcloudimg.tencent-cloud.cn/raw/4003ba16d9abfa030e69d406408e78de.png) #### 1.2 自定义登录页展示 配置身份源的显示信息,这将影响用户在登录页看到的内容: - **身份源名称**:建议填写「Google 登录」或「使用 Google 账号登录」 - **Logo 图标**:可以上传自定义图标或使用默认 Google 图标 云开发平台-身份认证-google登录-身份源名称logo配置 #### 1.3 复制回调地址 进入配置页面后,在「身份源基础配置」区域可以看到回调地址(格式为:`https://xxx.tcloudbaseauth.com/xxx`): 云开发平台-身份认证-google登录-身份源信息配置 **复制这个回调地址**,稍后需要在 Google 控制台中使用。 > ⚠️ **注意**:暂时不要关闭云开发控制台页面,稍后还需要回到这里完成配置。 ### 步骤 2:在 Google 开发者控制台创建 OAuth 应用 #### 2.1 进入 Google API Console 前往 [Google API Console Credentials](https://console.cloud.google.com/apis/credentials) 控制台。 > 💡 **提示**:如果您还没有 Google Cloud 项目,需要先创建一个项目。 #### 2.2 创建 OAuth 客户端 ID 点击「创建凭据」按钮,选择「OAuth 客户端 ID」: ![创建 OAuth 客户端 ID](https://qcloudimg.tencent-cloud.cn/raw/dac2e63889c521f859afbc1a955a405f.png) #### 2.3 配置 OAuth 客户端 填写以下配置信息: 1. **应用类型**:选择「Web 应用」(Web Application) 2. **名称**:为您的应用命名(如:我的云开发应用) 3. **已获授权的重定向 URI**:粘贴在步骤 1.2 中复制的回调地址 > ⚠️ **重要**:重定向 URI 必须与云开发控制台提供的回调地址完全一致,否则会导致登录失败。 #### 2.4 获取凭据信息 点击「创建」按钮后,Google 会生成 OAuth 客户端的凭据信息: - **Client ID**(客户端 ID) - **Client Secret**(客户端密钥) **复制并保存这两个值**,稍后需要填入云开发控制台。 ### 步骤 3:回到云开发控制台完成配置 #### 3.1 填写身份源基础配置 回到步骤 1 中打开的云开发控制台配置页面,在「身份源基础配置」中填入以下信息: - **Client ID**:填入步骤 2.4 中保存的客户端 ID - **Client Secret**:填入步骤 2.4 中保存的客户端密钥 - **授权 URL**:使用默认值 `https://accounts.google.com/o/oauth2/v2/auth` - **Scope 授权范围**:使用默认值或根据需求调整(如 `openid profile email`) 云开发平台-身份认证-google登录-身份源信息配置 > 💡 **提示**:关于 Scope 授权范围的详细说明,请参考 [Google OAuth 2.0 文档](https://developers.google.com/identity/protocols/oauth2/scopes)。 #### 3.2 保存配置 填写完成后,点击「保存」按钮完成配置。 ## 接入指南 ### 方式一:使用托管登录页(推荐) 「云开发 CloudBase」提供了开箱即用的托管登录页,无需编写登录界面代码,即可快速集成 Google 登录。 1. 进入 [云开发控制台/身份认证/登录页](https://tcb.cloud.tencent.com/dev?#/identity/land-page) 2. 在「登录方式配置」中勾选「Google 登录」 3. 点击「保存」 ![登录页配置](https://qcloudimg.tencent-cloud.cn/raw/859ceafd4f4e6f2dbaa1e502af725f82.png) ### 方式二:使用 SDK 自定义登录 如果您需要自定义开发登录界面和流程,可以使用「云开发 Web SDK」提供的 API 进行登录 #### 获取第三方平台授权页地址 [`Auth.genProviderRedirectUri`](/api-reference/webv2/authentication#authgenproviderredirecturi) 用于生成第三方平台授权页 URL ```javascript import cloudbase from "@cloudbase/cloudbase"; const app = cloudbase.init({ env: "your-env-id", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const { uri } = await auth.genProviderRedirectUri({ provider_id: "google", // google平台 provider_redirect_uri: providerUri, // 重定向 URI state: state, // 自定义状态标识 other_params: otherParams, // 其他参数 }); ``` ### 访问 URI 并进行授权后,回调至指定地址,获取第三方平台 Token 将用户重定向至 URI,例如 `location.href = uri`;待用户授权完毕后,回调至指定的 `provider_redirect_uri` 地址。 ```js // 此时 URL query 中携带授权 code,state 等参数 // 1. 检查 state 是否符合预期(如设置的参数) // 2. 获取第三方平台跳转回页面时,URL param 中携带的 code 参数 const provider_code = "your_provider_code"; // 3. 如符合预期,获取第三方平台 Token const { provider_token } = await auth.grantProviderToken({ provider_id: "google", provider_redirect_uri: "curpage", // 指定三方平台跳回的 URL 地址 provider_code: provider_code, // 第三方平台跳转回页面时,URL param 中携带的 code 参数 }); ``` ### 通过第三方平台 Token 登录 [`Auth.signInWithProvider`](/api-reference/webv2/authentication#authsigninwithprovider) 方法用于 **第三方平台登录** ```js await auth.signInWithProvider({ provider_token: provider_token, }); ``` --- # 身份认证/注册登录/自定义登录 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/custom-login 开发者可以使用**自定义登录**,在自己的服务器或者云函数内,为用户签发带有**自定义身份 ID** 的「自定义登录凭证 Ticket」,随后用户端 SDK 便可以使用 「Ticket」 登录 ## 适用场景 自定义登录适用于以下场景: - 开发者希望将自有的账号体系与云开发 CloudBase 账号进行一对一关联 - 开发者希望自行接管鉴权流程 ## 步骤概览 自定义登录需要以下几个步骤: 1. 获取 CloudBase 自定义登录私钥 2. 使用 CloudBase 服务端 SDK,通过私钥签发出 Ticket,并返回至用户端 3. 用户端 SDK 使用 Ticket 登录 CloudBase ## 前置动作 ### 获取自定义登录私钥 1. 前往 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 2. 在登录方式列表中,选择「自定义登录」方式,点击「启用」,再点击私钥下载 私钥是一份携带有 JSON 数据的文件,请将下载或复制的私钥文件保存到您的服务器或者云函数中,假设路径为 `/path/to/your/tcb_custom_login.json`。 :::tip 注意 1. 私钥文件是证明管理员身份的重要凭证,请务必妥善保存,避免泄漏 2. 每次生成私钥文件都会**使之前生成的私钥文件在 2 小时后失效** ::: ## 签发 Ticket 调用 CloudBase 服务端 SDK,在初始化时传入自定义登录私钥,随后便可以签发出 Ticket,并返回至用户端。 ```js const cloudbase = require("@cloudbase/node-sdk"); // 1. 初始化 SDK const app = cloudbase.init({ env: "your-env-id", region: "ap-shanghai", // 不传默认为上海地域 // 传入自定义登录私钥 credentials: require("/path/to/your/tcb_custom_login.json"), }); // 2. 开发者自定义的用户唯一身份标识 const customUserId = "your-customUserId"; // 3. 创建ticket const ticket = app.auth().createTicket(customUserId); // 4. 将ticket返回至客户端 return ticket; ``` :::tip 注意 `customUserId` 必须满足以下需求: - 4-32 位字符 - 字符只能是大小写英文字母、数字、以及 `_-#@(){}[]:.,<>+#~` 中的字符 ::: :::tip 提示 开发者也可以编写一个**云函数**用于生成 Ticket,并为其设置 HTTP 访问服务,随后用户端便可以通过 HTTP 请求的形式获取 Ticket,详细的方案请参阅 [使用 HTTP 访问云函数](/service/access-cloud-function) 。 ::: ## 注册流程 使用自定义登录时,用户管理完全由开发者自行处理。在 CloudBase 中,自定义登录不需要单独的注册流程,用户在第一次使用自定义登录时会自动创建对应的 CloudBase 用户。 注册流程主要包括: 1. 在您的用户系统中创建用户账号 2. 使用 CloudBase 服务端 SDK 为该用户签发 Ticket 3. 客户端使用 Ticket 完成首次登录(此时 CloudBase 会自动创建对应用户) ## 登录流程 用户端应用获取到 Ticket 之后,便可以进行登录 [`Auth.setCustomSignFunc`](/api-reference/webv2/authentication#authsetcustomsignfunc) 用于设置获取自定义登录的 ticket 函数 [`Auth.signInWithCustomTicket`](/api-reference/webv2/authentication#authsigninwithcustomticket) 用于自定义登录 ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", }); const auth = app.auth(); async function login() { const loginState = auth.hasLoginState(); // 1. 建议登录前检查当前是否已经登录 if (!loginState) { // 2. 请求开发者自有服务接口获取ticket await auth.setCustomSignFunc(() => { // 调用开发者自有服务接口获取ticket const ticket = "xxx"; // 获取 ticket 并返回 Promise return Promise.resolve(ticket); }); // 3. 登录 CloudBase await auth.signInWithCustomTicket(); } } login(); ``` 整体流程示意如下: ![自定义登录](https://main.qcloudimg.com/raw/9574ad543504b4a4a8d784a3a224cf69.png) --- # 身份认证/注册登录/Apple 登录 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/apple-login Apple 社交登录目前支持 Web 端、iOS 移动端,不支持小程序端。 Apple Identifiers ServiceId 对应的 JWT 秘钥生成指导文档 # 步骤 1:在 Apple 开发者中心进行配置 ## 获取 Team ID 1. 前往 Apple Developer Portal 的 [Membership](https://developer.apple.com/account) 页面,记录下 Team ID: ![image](https://qcloudimg.tencent-cloud.cn/raw/6e9acbf17179a15f73434d7c81359881.png) ## 创建一个 App ID 1. 在 Apple Developer Portal, Certificates, Identifiers & Profiles >[Identifiers 的 App IDs 页面](https://developer.apple.com/account/resources/identifiers/list),点击 ➕ 图标创建应用: ![image](https://qcloudimg.tencent-cloud.cn/raw/d48adf99ce63ec717a0e48fd4bc2378a.png) 2. 选择 App IDs 然后点击 Continue 按钮继续: ![image](https://qcloudimg.tencent-cloud.cn/raw/039b55c978a41c12f05ead3133cda870.png) 3. 选择应用类型,然后点击 Continue 按钮继续: ![image](https://qcloudimg.tencent-cloud.cn/raw/7ecb21543bef2e7a4714059ee50ea6cb.png) 4. 填写 Description 和 Bundle ID,并且在下面找到 Sign in with Apple 并勾选,请记录下此 **Bundle ID**: ![image](https://qcloudimg.tencent-cloud.cn/raw/e6803d423eb55b41aceaacb83ce8bbd2.png) 5. 最后点击 Continue 按钮,在打开的页面中确认信息,点击 Register 按钮创建应用。 ![image](https://qcloudimg.tencent-cloud.cn/raw/7577b8c2ebd3b7c4100c6890da569aaf.png) ## 创建一个 Service ID 1. 回到 Certificates, Identifiers & Profiles > [Identifiers 的 Services IDs 页面](https://developer.apple.com/account/resources/identifiers/list/serviceId),点击 ➕ 图标,选择 Services IDs 然后点击 Continue 按钮继续: ![image](https://qcloudimg.tencent-cloud.cn/raw/4a5053caeab10e9164ae0cd55c8c3890.png) 1. 填写 Description 和 Identifier,然后点击 Continue 按钮,在打开的确认页面点击 Register 按钮创建 Service: ![image](https://qcloudimg.tencent-cloud.cn/raw/545e99abe887ec5026587db2808fca25.png) ![image](https://qcloudimg.tencent-cloud.cn/raw/bb9ff7a702b6813b524ee19c914b6017.png) 1. 找到刚刚创建的 Service,选中 Sign In with Apple,点击 Configure: ![image](https://qcloudimg.tencent-cloud.cn/raw/d8ae123577bb44070a1d2a718b8e242a.png) 填写 Domains and Subdomains 和 Return URLs: ![image](https://qcloudimg.tencent-cloud.cn/raw/52fc7cf41d9efdd6c54d2d9bf81049c5.png) Domains and Subdomains:填入当前环境的域名,有自定义域名也添加自定义域名 ![image](https://qcloudimg.tencent-cloud.cn/raw/c5ffc5a825cd4b88eb3bf15c5c61f325.png) 1. Return URLs:请填入在[云开发控制台](https://tcb.cloud.tencent.com/dev) > 登录方式 > Apple 社交登录 获取的回调地址字段信息: 点击 Apple 登录方式,进行编辑 ![image](https://qcloudimg.tencent-cloud.cn/raw/a1e219b0e460065d8d25fec549c0490b.png) 在「2 身份源基础配置」,获取回调 URL ![image](https://qcloudimg.tencent-cloud.cn/raw/d83a24b0fed2ef4b513bd6ec6b474e3c.png) 1. 点击 Save,Continue,最后点击 Register,并记录下该 Service ID。 ![image](https://qcloudimg.tencent-cloud.cn/raw/f46c69938f185309b327805205d1db05.png) ## 配置 Signing Key 1. 回到 Certificates, Identifiers & Profiles 页面 > [Keys 页面](https://developer.apple.com/account/resources/authkeys/list),点击 ➕ 图标: ![image](https://qcloudimg.tencent-cloud.cn/raw/9ad7289d09cde1ee5c0c292eebff216f.png) 1. 输入名称并勾选上 Sign in with Apple, 点击 Configure,确保选中的 Primary App ID 是你刚刚创建的那一个: ![image](https://qcloudimg.tencent-cloud.cn/raw/331d3c09ad3c8d602dc2844842482d5a.png) ![image](https://qcloudimg.tencent-cloud.cn/raw/c80374423b2f6755a03e60d2daf217a8.png) 1. 点击 Save, Continue, 最后点击 Register. ![image](https://qcloudimg.tencent-cloud.cn/raw/757a9c60fdfce36322a131eb02d5f161.png) 创建之后,记录下 Key ID,然后点击 Download 下载该密钥: ![image](https://qcloudimg.tencent-cloud.cn/raw/757a9c60fdfce36322a131eb02d5f161.png) # 步骤 2:生成 jwt 秘钥 根据步骤一生成的 Apple 账号的信息,生成 jwt 的秘钥,秘钥最长 180 天,需要定期更新防止秘钥泄露 提供 python 的示例代码 ``` #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Apple Sign In - Client Secret Generator 生成 Apple Sign In 所需的 Client Secret JWT """ import sys # 检查依赖 try: import jwt except ImportError: print("❌ 缺少 PyJWT 库") print("请运行以下命令安装:") print(" pip3 install PyJWT") print("\n或者:") print(" pip3 install PyJWT cryptography") sys.exit(1) import time from pathlib import Path # ==================== 配置信息 ==================== # 请替换为您的实际信息 TEAM_ID = "" CLIENT_ID = "" # Service ID KEY_ID = "" PRIVATE_KEY_PATH = "" # JWT 有效期(天数),最长 180 天 EXPIRATION_DAYS = 180 # ================================================== def generate_client_secret(): """生成 Apple Client Secret JWT""" # 检查私钥文件是否存在 key_file = Path(PRIVATE_KEY_PATH) if not key_file.exists(): print(f"❌ 错误:找不到私钥文件 '{PRIVATE_KEY_PATH}'") print(f" 当前目录:{Path.cwd()}") print("\n请确保:") print("1. 已从 Apple Developer 下载 .p8 私钥文件") print("2. 将文件放在脚本同目录下,或修改 PRIVATE_KEY_PATH 为正确路径") sys.exit(1) # 读取私钥 try: with open(PRIVATE_KEY_PATH, 'r') as f: private_key = f.read() except Exception as e: print(f"❌ 读取私钥文件失败:{e}") sys.exit(1) # 检查配置是否已修改 if TEAM_ID == "YOUR_TEAM_ID" or CLIENT_ID == "com.yourcompany.service" or KEY_ID == "YOUR_KEY_ID": print("⚠️ 警告:请先修改脚本中的配置信息!") print("\n需要配置的信息:") print(f" - TEAM_ID: {TEAM_ID}") print(f" - CLIENT_ID: {CLIENT_ID}") print(f" - KEY_ID: {KEY_ID}") print(f" - PRIVATE_KEY_PATH: {PRIVATE_KEY_PATH}") print("\n这些信息可以从 Apple Developer 后台获取") sys.exit(1) # 构造 JWT Header headers = { "kid": KEY_ID, "alg": "ES256" } # 构造 JWT Payload now = int(time.time()) expiration = now + (86400 * EXPIRATION_DAYS) # 86400 秒 = 1 天 payload = { "iss": TEAM_ID, "iat": now, "exp": expiration, "aud": "https://appleid.apple.com", "sub": CLIENT_ID } # 生成 JWT try: client_secret = jwt.encode( payload, private_key, algorithm="ES256", headers=headers ) # PyJWT 2.0+ 返回字符串,旧版本返回 bytes if isinstance(client_secret, bytes): client_secret = client_secret.decode('utf-8') return client_secret, expiration except Exception as e: print(f"❌ 生成 JWT 失败:{e}") print("\n可能的原因:") print("1. 私钥格式不正确") print("2. 缺少 cryptography 库,请运行:pip3 install cryptography") sys.exit(1) def main(): print("=" * 60) print("🍎 Apple Sign In - Client Secret Generator") print("=" * 60) print() print("📋 配置信息:") print(f" Team ID: {TEAM_ID}") print(f" Client ID: {CLIENT_ID}") print(f" Key ID: {KEY_ID}") print(f" Private Key: {PRIVATE_KEY_PATH}") print(f" 有效期: {EXPIRATION_DAYS} 天") print() print("🔄 正在生成 Client Secret...") client_secret, expiration = generate_client_secret() print("✅ 生成成功!") print() print("=" * 60) print("📝 Client Secret (JWT):") print("=" * 60) print(client_secret) print("=" * 60) print() # 显示过期时间 from datetime import datetime expiration_date = datetime.fromtimestamp(expiration) print(f"⏰ 过期时间:{expiration_date.strftime('%Y-%m-%d %H:%M:%S')}") print(f" ({EXPIRATION_DAYS} 天后)") print() print("💡 使用说明:") print("1. 复制上面的 JWT 字符串") print("2. 在 Provider 配置中,将其设置为 'client_secret' 字段的值") print("3. 在过期前需要重新生成新的 Client Secret") print() if __name__ == "__main__": main() ``` # 步骤 3:云开发 Apple 社交登录配置 在云开发控制台 > 身份认证 > 登录方式 > Apple 社交登录编辑配置 ![image](https://qcloudimg.tencent-cloud.cn/raw/9605a04c7f6d249068f9cda8ca77b54b.png 点击编辑按钮进入编辑流程 ![image](https://qcloudimg.tencent-cloud.cn/raw/b98ee3b1f2c8424bc821bbd5769903f2.png) Client ID 为第一步 Apple 账号中生成的 ServiceID Client Sercet 为第二步 Apple 账号生成的 JWT 秘钥 Scope 授权范围可根据需要进行调整,默认填入`openid email name` ![image](https://qcloudimg.tencent-cloud.cn/raw/3650a44d5b50dbecac71b6ee1a30ac46.png) 进行账号关联设置,保存身份源并启用,即可进行接入测试。 ![image](https://qcloudimg.tencent-cloud.cn/raw/7d9f8a04cd112de11e9a2fdcb63fdaa8.png) --- # 身份认证/其他参考/账户关联登录 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/account-linking 每个云开发用户账号,除了最初注册时使用的登录方式外,还可以关联其他登录方式。关联后,无论用户使用哪种登录方式,均可以登录到同一个云开发账户。 ## 关联手机号密码登录 :::tip 提示 仅支持 `上海` 地域 ::: 当前用户支持密码登录时,可以为用户绑定手机号,绑定后用户可以使用「手机号+密码」完成登录: 1. 用户以任意一种登录方式登录 2. 获取 `sudo_token`,这里以密码方式获取 `sudo_token`,还可以使用邮箱验证码、手机号验证码等方式,具体请参考 [`Auth.sudo`](/api-reference/webv2/authentication#authsudo) 接口 ```javascript import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", }); // 获取 auth 实例 const auth = app.auth(); // 假设用户输入的密码为 passwd const password = "passwd"; // 获取 sudo_token,sudo_token 的过期时间默认为 10 分钟 const sudo_token = await auth.sudo({ password: password, }); ``` 3. 向用户手机发送验证短信 ```javascript // 假设用户手机号为 13800000000 const phoneNumber = "+86 13800000000"; // 发送验证码 const verification = await auth.getVerification({ phone_number: phoneNumber, }); ``` 4. 校验用户输入的验证码 ```javascript // 假设用户输入的验证码为 000000 const verificationCode = "000000"; // 校验验证码 const verificationTokenRes = await auth.verify({ verification_id: verification.verification_id, verification_code: verificationCode, }); const verification_token = verificationTokenRes.verification_token; ``` 5. 使用 `verification_token` 和 `sudo_token` 绑定手机号 ```javascript await auth.bindPhoneNumber({ sudo_token: sudo_token, phone_number: phoneNumber, verification_token: verification_token, }); ``` ## 关联邮箱密码登录 当前用户支持密码登录时,可以为用户绑定邮箱,绑定后用户可以使用「邮箱+密码」完成登录: 1. 用户以任意一种登录方式登录云开发 2. 获取 `sudo_token`,这里以密码方式获取 `sudo_token`,还可以使用邮箱验证码、手机号验证码等方式,具体请参考 [`Auth.sudo`](/api-reference/webv2/authentication#authsudo) 接口 ```javascript // 假设用户输入的密码为 passwd const password = "passwd"; // 获取 sudo_token,sudo_token 的过期时间默认为 10 分钟 const sudo_token = await auth.sudo({ password: password, }); ``` 3. 给邮箱发送验证码 ```javascript // 假设用户邮箱为 "test@example.com" const email = "test@example.com"; // 获取邮箱验证码 const verification = await auth.getVerification({ email: email, }); ``` 4. 校验用户输入的验证码 ```javascript // 假设用户输入的验证码为 000000 const verificationCode = "000000"; // 校验验证码 const verificationTokenRes = await auth.verify({ verification_id: verification.verification_id, verification_code: verificationCode, }); const verification_token = verificationTokenRes.verification_token; ``` 5. 使用 `verification_token` 和 `sudo_token` 绑定邮箱 ```javascript await auth.bindEmail({ sudo_token: sudo_token, email: email, verification_token: verification_token, }); ``` ## 关联微信登录 关联微信登录的步骤如下: 1. 用户以任意一种登录方式(除微信登录)登录云开发 2. 参考 [微信授权登录](/authentication-v2/method/wechat-login#第-2-步使用-sdk-处理登录流程) 获取微信授权 `provider_token` 3. 使用授权 `token` 关联微信登录 ```javascript const app = cloudbase.init({ env: "xxxx-yyy", }); const provider_token = "test_provider_token"; // 上一步取得的授权 token const auth = app.auth(); await auth.bindWithProvider({ provider_token, }); ``` --- # 身份认证/其他参考/自定义短信服务商配置指南 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/custom-sms-provider ## 功能概述 云开发身份认证支持手机号短信验证码登录方式。默认情况下,系统使用云开发内置短信配额发送验证码。如需使用自己的短信服务商,可通过 API 配置自定义短信通道。 > 当前已支持的服务商:**腾讯云短信**(`TENCENT_CN` 国内、`TENCENT_INTL` 国际/港澳台)。后续将陆续接入更多服务商,本指南将持续更新。 配置完成后,用户在注册/登录时触发的验证码短信,将通过您配置的短信服务商发出,您可自主控制签名、模板及每日发送上限。 ## 前提条件 在调用配置接口前,您需要在腾讯云完成以下准备工作: ### 第一步:开通腾讯云短信服务 1. 登录 [腾讯云短信控制台](https://console.cloud.tencent.com/smsv2),同意服务协议后开通短信服务。 2. 进入**应用管理 > 应用列表**,点击**创建应用**,填写应用名称,创建完成后记录 **SdkAppId**。 3. 前往 [API 密钥管理](https://console.cloud.tencent.com/cam/capi),创建或查看 **SecretId** 和 **SecretKey**。 :::warning SecretKey 属于敏感凭证,请妥善保管,避免泄露。 ::: ### 第二步:申请短信签名 短信签名将显示在发送给用户的短信开头,例如【腾讯云】。 #### 国内短信签名 1. 进入控制台左侧导航**国内短信 > 签名管理**,点击**创建签名**。 2. 根据实际情况填写: - **签名用途**:自用(本账号旗下产品)或他用(第三方产品,需上传授权委托书) - **签名类型**:公司、App、商标、政府/机关事业单位等 - **签名内容**:填写实际显示内容,无需填写【】,如 `云开发` 3. 上传对应证明文件(营业执照扫描件等),提交审核。 4. 审核通过后记录**签名内容**备用。 :::tip 官方文档:[管理国内短信签名](https://cloud.tencent.com/document/product/382/37794)。审核时间为工作日 9:00–21:00,周末 9:00–18:00,预计约 2 小时。 ::: #### 国际/港澳台短信签名(可选) 1. 进入控制台左侧导航**国际/港澳台短信 > 签名管理**,点击**创建签名**。 2. 填写签名用途、类型及内容(国际签名内容为 2–15 字,无需方括号)。 3. 证明文件为可选项(提供可提高审核通过率),提交审核。 4. 审核通过后记录**签名内容**备用。 :::tip 官方文档:[管理国际/港澳台短信签名](https://cloud.tencent.com/document/product/382/37799) ::: ### 第三步:申请短信模板 短信模板定义了验证码短信的正文内容,变量部分使用占位符 `{1}`、`{2}` 表示。 #### 国内短信模板(示例) ``` 您的验证码为:{1},{2} 分钟内有效,请勿泄露给他人。 ``` #### 国际短信模板(示例) ``` Your verification code is {1}, valid for {2} minutes. Do not share it with others. ``` **申请步骤:** 1. 进入**国内短信**或**国际/港澳台短信 > 正文模板管理**,点击**创建正文模板**。 2. 填写模板名称、模板内容,选择短信类型为**验证码**。 3. 提交审核,审核通过后记录**模板 ID(TemplateId)**备用。 :::tip 官方文档:[短信正文模板审核标准](https://cloud.tencent.com/document/product/382/39023) ::: ## 完成准备后,您应已获取以下信息 | 参数 | 说明 | 获取路径 | |------|------|----------| | `SdkAppId` | 短信应用 ID | 短信控制台 > 应用管理 | | `SecretId` | API 密钥 ID | [API 密钥管理](https://console.cloud.tencent.com/cam/capi) | | `SecretKey` | API 密钥 | [API 密钥管理](https://console.cloud.tencent.com/cam/capi) | | `SignName` | 短信签名内容 | 短信控制台 > 签名管理(审核通过后) | | `TemplateId` | 短信模板 ID | 短信控制台 > 正文模板管理(审核通过后) | ## API 接口说明 ### 接口信息 | 项目 | 说明 | |------|------| | **接口名称** | ModifyLoginConfig(修改登录策略) | | **请求域名** | `tcb.tencentcloudapi.com` | | **API 版本** | `2018-06-08` | | **HTTP 方法** | POST | | **频率限制** | 20 次/秒 | :::tip 接口文档:[ModifyLoginConfig - 修改登录策略](https://cloud.tencent.com/document/api/876/129351) ::: ### 输入参数 #### 顶层参数 | 参数名 | 类型 | 是否必填 | 说明 | |--------|------|----------|------| | `EnvId` | String | 是 | 云开发环境 ID | | `PhoneNumberLogin` | Boolean | 是 | 手机号短信登录开关 | | `EmailLogin` | Boolean | 是 | 邮箱登录开关 | | `UserNameLogin` | Boolean | 是 | 用户名密码登录开关 | | `AnonymousLogin` | Boolean | 是 | 匿名登录开关 | | `SmsVerificationConfig` | Object | 否 | 短信验证码发送通道配置,见下方详细说明 | #### SmsVerificationConfig(短信通道配置) | 参数名 | 类型 | 是否必填 | 说明 | |--------|------|----------|------| | `Type` | String | 否 | 短信发送通道类型。`default`:使用云开发内置短信;`apis`:接入自定义 API 数据源;`template`:使用自定义短信服务商模板(**腾讯云短信使用此值**) | | `Name` | String | Type 为 `apis` 时必填 | 自定义 APIs 数据源标识 | | `Method` | String | Type 为 `apis` 时必填 | 自定义 APIs 方法名 | | `SmsDayLimit` | Integer | 否 | 单个手机号每日发送上限,默认 30 次,`-1` 表示不限制 | | `TemplateProvider` | Object | Type 为 `template` 时必填 | 服务商短信模板配置,见下方 SMSProviderTemplateConfig 说明 | #### SMSProviderTemplateConfig(服务商模板配置) | 参数名 | 类型 | 是否必填 | 说明 | |--------|------|----------|------| | `Vendor` | String | 是 | 短信服务商。`TENCENT_CN`:腾讯云短信(国内);`TENCENT_INTL`:腾讯云短信(国际/港澳台) | | `TemplateId` | String | 是 | 短信模板 ID(审核通过的模板) | | `SdkAppId` | String | 否 | 腾讯云短信应用 ID | | `SignName` | String | 否 | 短信签名内容(审核通过的签名) | | `SecretId` | String | 否 | 腾讯云 API 密钥 ID | | `SecretKey` | String | 否 | 腾讯云 API 密钥 | | `SenderId` | String | 否 | 发送方 ID,国际短信部分场景需填写 | | `TemplateExtendParam` | Array of Object | 否 | 模板自定义扩展参数,见下方 SMSTemplateParams 说明 | #### SMSTemplateParams(模板扩展参数) | 参数名 | 类型 | 说明 | |--------|------|------| | `Key` | String | 参数键(使用命名占位符时填写) | | `Value` | String | 参数值 | ### 请求示例 #### 配置腾讯云国内短信(TENCENT_CN) ```json { "EnvId": "your-env-id", "PhoneNumberLogin": true, "EmailLogin": false, "UserNameLogin": false, "AnonymousLogin": false, "SmsVerificationConfig": { "Type": "template", "SmsDayLimit": 100, "TemplateProvider": { "Vendor": "TENCENT_CN", "SdkAppId": "1400000000", "SecretId": "AKIDxxxxxxxx", "SecretKey": "xxxxxxxxxxxxxxxx", "SignName": "云开发", "TemplateId": "123456" } } } ``` #### 配置腾讯云国际/港澳台短信(TENCENT_INTL) ```json { "EnvId": "your-env-id", "PhoneNumberLogin": true, "EmailLogin": false, "UserNameLogin": false, "AnonymousLogin": false, "SmsVerificationConfig": { "Type": "template", "SmsDayLimit": 100, "TemplateProvider": { "Vendor": "TENCENT_INTL", "SdkAppId": "1400000000", "SecretId": "AKIDxxxxxxxx", "SecretKey": "xxxxxxxxxxxxxxxx", "SignName": "CloudBase", "TemplateId": "789012" } } } ``` #### 恢复使用云开发内置短信 ```json { "EnvId": "your-env-id", "PhoneNumberLogin": true, "EmailLogin": false, "UserNameLogin": false, "AnonymousLogin": false, "SmsVerificationConfig": { "Type": "default", "SmsDayLimit": 30 } } ``` ### 输出参数 | 参数名 | 类型 | 说明 | |--------|------|------| | `RequestId` | String | 唯一请求 ID,用于问题定位 | #### 响应示例 ```json { "Response": { "RequestId": "70b8908d-5c99-4a20-be23-f84aff0bfd37" } } ``` ## 注意事项 1. **签名和模板需提前审核通过**:调用接口前,请确保腾讯云短信的签名和模板均已通过审核,否则短信发送将失败。 2. **国内与国际场景分开配置**:国内短信和国际/港澳台短信的 SignName、TemplateId 分别管理,请根据您的用户群体选择对应的配置。 3. **SmsDayLimit 合理设置**:建议根据业务量设置每日上限,防止异常刷量,同时避免设置过低影响正常用户登录。 4. **SecretKey 安全存储**:请勿将 SecretKey 硬编码在前端代码中,建议通过后端服务调用接口。 5. **配置即时生效**:接口调用成功后,配置立即生效,所有使用该云开发环境的用户登录时均将使用新的短信通道。 ## 参考链接 - [腾讯云短信控制台](https://console.cloud.tencent.com/smsv2) - [腾讯云短信快速入门](https://cloud.tencent.com/document/product/382/37745) - [管理国内短信签名](https://cloud.tencent.com/document/product/382/37794) - [管理国际/港澳台短信签名](https://cloud.tencent.com/document/product/382/37799) - [短信正文模板审核标准](https://cloud.tencent.com/document/product/382/39023) - [ModifyLoginConfig - 修改登录策略(API 文档)](https://cloud.tencent.com/document/api/876/129351) --- # 身份认证/其他参考/验证码处理指南 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/method/captcha 本文档介绍如何在腾讯云开发身份验证中处理验证码相关逻辑,包括触发条件、错误处理和完整的实现流程。 ## 触发条件 验证码会在以下情况下被要求输入: - 用户名密码登录失败 5 次后 - 发送手机号或邮箱验证码时达到频率限制 ## 错误信息 当需要验证码时,接口会返回相应的错误信息: - `error == captcha_required`:表示请求触发了验证码相关逻辑,需要进行机器验证 - `error == captcha_invalid`:表示验证码无效,需要重新获取验证码 :::tip 注意 验证码流程完成后,若业务接口返回 `error` 等于 `captcha_required`,表示请求需要 `captcha_token` 参数,应尽可能使用本地未过期的验证码。当 `error` 等于 `captcha_invalid` 时,表示验证码无效,需要重新获取验证码。在同一个验证流程内,`captcha_invalid` 最多尝试一次即可。 ::: ```typescript // 错误信息示例: { data: { error: "captcha_required", error_code: 4001, error_description: "captcha_token required" } } ``` ## 处理流程 ### 完整流程 验证码处理的完整流程如下: 1. 用户尝试登录或发送验证码 2. 若触发验证码要求,SDK 抛出 `captcha_required` 错误 3. 编写 SDK 适配器,捕获错误并触发 `openURIWithCallback` 4. `openURIWithCallback` 方法中获取验证码参数,并通过 `EVENT_BUS` 发送给前端展示 5. 前端展示验证码图片并等待用户输入 6. 用户输入验证码并提交 7. 系统验证并返回结果 8. 根据验证结果决定是否重试原操作 关于第三步的 SDK 适配器以及 `openURIWithCallback` 方法请参考 [适配器指引](/api-reference/webv2/adapter/) ### 适配器实现 ```typescript function genAdapter(options) { const adapter: SDKAdapterInterface = { captchaOptions: { openURIWithCallback: async (url: string) => { // 解析 URL 中的验证码参数 const { captchaData, state, token } = cloudbase.parseCaptcha(url); // 通过事件总线发送验证码数据,进行前端缓存及展示 options.EVENT_BUS.emit("CAPTCHA_DATA_CHANGE", { captchaData, // Base64 编码的验证码图片 state, // 验证码状态标识 token, // 验证码 token }); // 监听验证码校验结果 return new Promise((resolve) => { console.log("等待验证码校验结果..."); options.EVENT_BUS.once("RESOLVE_CAPTCHA_DATA", (res) => { // auth.verifyCaptchaData 的校验结果 resolve(res); }); }); }, }, }; return adapter; } ``` ### 初始化 SDK ```typescript import cloudbase from "@cloudbase/js-sdk"; // 创建事件总线实例,具体EventBus实现可参考网上示例 const EVENT_BUS = new EventBus(); // 配置并使用适配器,将 EVENT_BUS 注入给 genAdapter cloudbase.useAdapters(adapter, { EVENT_BUS }); const app = cloudbase.init({ env: "环境ID", appSign: "应用标识", appSecret: { appAccessKeyId: "应用凭证版本号", appAccessKey: "应用凭证", }, }); const auth = app.auth(); ``` ### 验证码界面实现 ```typescript // 存储当前验证码状态 let captchaState = { captchaData: "", // Base64 编码的验证码图片 state: "", // 验证码状态标识 token: "", // 验证码 token }; // 监听验证码数据变化 EVENT_BUS.on("CAPTCHA_DATA_CHANGE", ({ captchaData, state, token }) => { console.log("收到验证码数据", { captchaData, state, token }); // 更新本地验证码状态 captchaState = { captchaData, state, token }; // 在页面中显示验证码图片,例如在 Web 中使用 img 标签展示 const captchaImage = document.getElementById("captcha-image"); if (captchaImage) { captchaImage.src = captchaData; } }); // 用户点击刷新验证码时调用 const refreshCaptcha = async () => { try { // 获取最新验证码信息 const result = await auth.createCaptchaData({ state: captchaState.state, }); // 更新本地验证码状态 captchaState = { ...captchaState, captchaData: result.data, token: result.token, }; // 更新显示的验证码图片 const captchaImage = document.getElementById("captcha-image"); if (captchaImage) { captchaImage.src = result.data; } } catch (error) { console.error("刷新验证码失败", error); } }; // 用户提交验证码时调用 const verifyCaptchaData = async (userCaptcha) => { try { // 校验验证码 const verifyResult = await auth.verifyCaptchaData({ token: captchaState.token, key: userCaptcha, }); // 将校验结果通知适配器 EVENT_BUS.emit("RESOLVE_CAPTCHA_DATA", verifyResult); console.log("验证码校验成功"); } catch (error) { console.error("验证码校验失败", error); // 校验失败时可以选择刷新验证码 await refreshCaptcha(); } }; ``` ## 验证码展示效果 示例代码地址: [示例代码](https://github.com/TencentCloudBase/awesome-cloudbase-examples/blob/f38fe782c6c099612eb591866e3c35039e816bb6/universal/soul-chat/src/components/show-captcha.vue) 验证码展示效果 ## 相关 API - `auth.createCaptchaData(options)` - 创建验证码数据 - `auth.verifyCaptchaData(options)` - 验证验证码 - `cloudbase.parseCaptcha(url)` - 解析验证码 URL 参数 ## 注意事项 1. 验证码具有时效性,建议在获取后及时使用 2. 同一验证流程中,验证码校验失败最多重试一次 3. 建议在验证码校验失败后提供刷新验证码的功能 4. 确保事件总线的正确初始化和事件监听 --- # 身份认证/其他参考/为注册用户赋予管理员权限 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/practice/grant-admin-permission ## 用户类型与角色说明 **重要概念澄清**: - **用户类型**(组织成员、注册用户): 决定用户在组织架构中的归属 - **用户角色**(管理员、自定义角色): 决定用户拥有的权限 只有**组织成员**可以被赋予管理员权限或自定义角色。如果需要为**注册用户**赋予管理员权限,需要先将其转为组织成员。 ## 方式一: 在组织架构中直接为组织成员分配管理员角色 如果用户已经是组织成员,可以直接为其分配管理员角色: 1. 访问 [云开发平台/身份认证/组织架构](https://tcb.cloud.tencent.com/dev#/identity/user-organization) 2. 在组织架构树中找到目标用户 3. 点击用户右侧的「编辑」按钮 4. 在「角色」选项中选择「管理员」角色 5. 点击「确定」保存配置 ## 方式二: 将注册用户导入组织架构(转为组织成员) 如果需要将注册用户纳入企业组织架构管理并赋予管理员权限,可以将其转为组织成员: 1. 访问 [云开发平台/身份认证/组织架构](https://tcb.cloud.tencent.com/dev#/identity/user-organization) 2. 点击「导入用户」按钮 3. 选择「导入注册用户」 4. 在列表中选择需要导入的注册用户 5. 确认后,该用户将转为组织成员并加入组织架构 6. 为该用户分配管理员角色或其他自定义角色 > 💡 **提示**: 组织成员受套餐限制,转换前请确认当前套餐的用户数配额。参考 [计费文档](https://cloud.tencent.com/document/product/876/75213) ![注册用户导入组织架构页面](https://qcloudimg.tencent-cloud.cn/raw/1beea06e7abf118a194153a44ee73e8d.png) ## 验证管理员权限 用户被赋予管理员权限后,可以: - 访问云开发控制台的所有功能 - 管理环境配置、资源和成员 - 执行所有数据库、云函数、云存储操作 - 查看和管理其他用户 建议用户退出重新登录以立即应用新权限。 ## 注意事项 - **用户类型转换**: 注册用户转为组织成员后,将受到套餐用户数限制 - **权限生效时间**: 角色分配后建议用户重新登录以立即生效 - **套餐配额**: 转换前请确认当前套餐的组织成员数量配额是否充足 - **权限范围**: 管理员角色拥有环境的完整管理权限,请谨慎分配 ## 相关文档 - [用户管理](/authentication-v2/auth/manage-users) - 用户类型说明和基本管理 - [权限控制](/authentication-v2/auth/auth-control) - 角色体系和权限配置详解 - [最佳实践](/authentication-v2/auth/best-practice) - 身份认证和权限管理最佳实践 --- # 身份认证/其他参考/存储额外的用户信息 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/practice/store-extra-user-info ## 系统用户表 vs 自定义用户表 使用云开发登录认证后,用户的基础信息(如 `uid`、登录方式、基础资料等)会自动存储在系统维护的用户表中。如果需要存储业务相关的额外用户信息(如地址、邮箱、会员等级等),**推荐创建自定义用户表**。 **系统用户表**: - 由云开发自动维护 - 存储用户身份认证相关信息(`uid`、`name`、`gender`、登录方式等) - 通过 `auth.currentUser` 获取 - 不建议直接在此表添加业务字段 **自定义用户表**(推荐): - 开发者自行创建和维护(如 `users` 集合) - 存储业务相关的扩展信息(地址、邮箱、积分、会员等级等) - 通过 `uid` 与系统用户表关联 - 灵活扩展字段,满足业务需求 ## 实现方式 ### 1. 创建自定义用户表 在 [云开发平台/文档型数据库](https://tcb.cloud.tencent.com/dev#/db/doc) 创建 `users` 集合,设计以下结构: ```javascript { _id: "用户记录ID", uid: "云开发用户ID(与系统用户表关联)", address: "用户地址", email: "邮箱", phone: "手机号", vip_level: "会员等级", createTime: "创建时间" } ``` ### 2. 用户注册时创建扩展信息 ```javascript import cloudbase from '@cloudbase/js-sdk'; const app = cloudbase.init({ env: 'your-env-id', }); const auth = app.auth(); const db = app.database(); // 用户注册成功后 auth.signUp({ phone_number: '+86 13800000000', verification_code: '123456', verification_token: 'token', name: '张三', }).then(async () => { // 获取当前用户 uid const user = auth.currentUser; // 在自定义用户表中创建扩展信息 await db.collection('users').add({ uid: user.uid, // 关联系统用户表 address: '北京市朝阳区', email: 'zhangsan@example.com', phone: '+86 13800000000', vip_level: 1, createTime: new Date(), }); console.log('用户注册并创建扩展信息成功'); }); ``` ### 3. 查询用户完整信息 ```javascript // 获取系统用户信息 const user = auth.currentUser; console.log('系统用户信息:', user.uid, user.name); // 获取自定义扩展信息 const { data } = await db.collection('users').where({ uid: user.uid }).get(); console.log('用户扩展信息:', data[0].address, data[0].email); ``` ### 4. 更新用户扩展信息 ```javascript // 更新自定义用户表中的信息 await db.collection('users').where({ uid: auth.currentUser.uid }).update({ address: '上海市浦东新区', vip_level: 2, }); ``` ## 最佳实践 - **分离关注点**: 系统用户表专注于身份认证,自定义用户表专注于业务逻辑 - **使用 uid 关联**: 通过 `uid` 字段在两个表之间建立关联关系 - **合理设计字段**: 根据业务需求设计自定义用户表的字段结构 - **注意数据同步**: 当用户信息更新时,注意同步更新相关的数据表 ## 相关文档 - [用户管理](/authentication-v2/auth/manage-users) - 用户信息管理和更新 - [数据库操作](/database/introduction) - 云开发数据库操作指南 - [最佳实践](/authentication-v2/auth/best-practice) - 身份认证和权限管理最佳实践 --- # 身份认证/其他参考/TCB 登录态长期保持方案 > 当前文档链接: https://docs.cloudbase.net/faq/knowledge/tcb-login-state-refresh-token 在使用腾讯云开发(TCB)进行用户认证时,Token 有效期较短,用户长时间不使用应用后需要重新登录。本文介绍如何使用 refresh token 机制实现登录态的长期保持。 ## 问题现象 使用云开发 HTTP API 进行用户认证时遇到以下问题: - Token 有效期较短,需要频繁刷新 - 用户长时间(几天或几周)不使用应用后,Token 过期需要重新登录 - 应用在后台无法触发 Token 刷新,导致用户体验不佳 ## 解决方案 云开发提供了 **refresh token 机制**,可以在主 Token 过期后通过 refresh token 自动获取新的访问 Token。 ### Refresh Token 机制说明 | Token 类型 | 有效期 | 用途 | |-----------|-------|------| | Access Token | 2 小时(7200 秒) | 访问云资源的凭证 | | Refresh Token | 默认 31 天(可配置) | 用于刷新 Access Token | ### 使用方法 #### 1. 获取 Token(包含 refresh_token) 首次登录时,调用 `/auth/v1/token` 接口获取 Token(以密码模式为例): ```javascript const envId = 'your-env-id' const response = await fetch(`https://${envId}.ap-shanghai.tcb-api.tencentcloudapi.com/auth/v1/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'password', username: 'your-username', password: 'your-password', client_id: envId }) }) const data = await response.json() // 保存 access_token 和 refresh_token const { access_token, refresh_token, expires_in } = data ``` #### 2. 使用 Refresh Token 刷新登录态 当 Access Token 过期时,使用 Refresh Token 获取新的 Token: ```javascript const envId = 'your-env-id' const response = await fetch(`https://${envId}.ap-shanghai.tcb-api.tencentcloudapi.com/auth/v1/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: savedRefreshToken, client_id: envId }) }) const data = await response.json() // 更新保存的 token(注意:原 refresh_token 使用后立即失效) const { access_token, refresh_token, expires_in } = data ``` ### 最佳实践 1. **安全存储 Token**:将 refresh_token 存储在安全的位置(如 Keychain、EncryptedSharedPreferences) 2. **自动刷新机制**:在应用启动或从后台恢复时检查 Token 状态 ```javascript async function ensureValidToken() { const tokenExpiry = getTokenExpiry() if (Date.now() >= tokenExpiry - 60000) { // 提前 1 分钟刷新 await refreshAccessToken() } } ``` 3. **错误处理**:当 refresh_token 也过期时,引导用户重新登录 ```javascript async function refreshAccessToken() { try { const response = await refreshToken() if (response.error) { // 错误格式: { error: 'invalid_grant', error_code: 4001, error_description: '...' } if (response.error === 'invalid_grant') { // refresh_token 无效或已过期,引导用户重新登录 navigateToLogin() return } } saveTokens(response) } catch (error) { console.error('Token 刷新失败', error) } } ``` 4. **应用恢复时刷新**:在应用从后台恢复时检查并刷新 Token ```javascript // React Native 示例 import { AppState } from 'react-native' AppState.addEventListener('change', (nextAppState) => { if (nextAppState === 'active') { ensureValidToken() } }) ``` ## 注意事项 1. Refresh Token 也有有效期(默认 31 天),过期后需要用户重新登录 2. **重要**:刷新 Token 时原 refresh_token 立即失效,必须保存并使用返回的新 refresh_token 3. 不要在不安全的位置(如 localStorage)存储 refresh_token 4. 建议在 Token 即将过期前主动刷新,而不是等到过期后再刷新 5. 可在[云开发平台/身份认证/Token](https://tcb.cloud.tencent.com/dev?#/identity/token-management)修改 Refresh Token 有效期 ## 相关文档 - [HTTP API 认证文档](https://docs.cloudbase.net/http-api/auth/auth-grant-token#1-refresh-token-%E5%88%B7%E6%96%B0%E6%A8%A1%E5%BC%8F) - [身份认证概述](/authentication/introduce) --- # 身份认证/其他参考/未命名文档 > 当前文档链接: https://docs.cloudbase.net/quick-start/enterprise-identity-provider --- sidebar_position: 5 title: 企业身份源接入 --- # AI Coding 平台 SSO 身份源接入方案 > 本文档面向企业客户及平台集成方,介绍如何基于 CloudBase 身份认证,将企业现有 OAuth 2.0 身份源(如 Google、企业微信等)接入 AI Coding 平台,实现多应用统一登录(SSO)。 --- ## 适用场景 - 企业已有 OAuth 2.0 身份源(Google Workspace、企业微信等),希望员工使用已有账号直接登录 AI Coding 平台; - 平台下部署了多个子应用(如MCP 工具台、管理后台),需要 **登录一次、全部应用免登**; - 希望统一账号生命周期:身份源停用账号后,平台侧同步失效。 --- ## 方案概述 ### 核心原理 本方案以 **CloudBase 托管登录页** 作为 SSO 中枢。所有应用统一将未登录用户重定向到同一个托管登录页,由该页面完成与 OAuth 2.0 身份源的交互;身份认证通过后,CloudBase 颁发统一 Session,用户回到任意应用均已登录。 ``` App A ──┐ │ 未登录时重定向 OAuth 2.0 App B ──┼──────────────► CloudBase 托管登录页 ◄──► 企业身份源 │ │ (Google / 企业微信等) App C ──┘ │ 颁发 CloudBase Session ▼ 用户回到原应用,已登录 ✅ ``` ### 为什么选择托管登录页 | 对比项 | 托管登录页 | 自建登录页 | | -------------- | ------------------------------- | ------------------------ | | SSO Session 共享 | ✅ 自动,所有应用共用一个 CloudBase Session | ❌ 需自行实现跨应用 Session 同步 | | OAuth 2.0 接入 | ✅ 控制台配置即可,无需写代码 | ⚠️ 需调用 SDK 手动处理 OAuth 流程 | | 接入成本 | 低 | 高 | **结论:** 如需实现多应用 SSO,推荐使用托管登录页。 --- ## 支持的身份源类型 | 身份源 | 协议 | 接入方式 | | ----------------- | --------- | ----------- | | Google Workspace | OAuth 2.0 | 控制台配置,开箱即用 | | 企业微信 | OAuth 2.0 | 控制台配置,开箱即用 | | GitHub | OAuth 2.0 | 控制台配置,开箱即用 | | 自定义 OAuth 2.0 身份源 | OAuth 2.0 | 控制台-企业身份源配置 | | 更多企业身份源 | 如SAML | 控制台-企业身份源配置 | --- ## 整体架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ 用户浏览器 │ │ │ │ App A / App B / App C │ │ │ 未登录,调用 auth.toDefaultLoginPage() │ │ ▼ │ │ CloudBase 托管登录页(统一入口) │ │ │ 用户选择登录方式 │ │ ▼ │ │ OAuth 2.0 身份源 │ │ │ 认证通过,携带 code 回调 │ │ ▼ │ │ CloudBase 身份认证服务 │ │ │ 颁发 Session Token,写入浏览器 │ │ ▼ │ │ 重定向回原应用,Session 已就绪 │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 接入流程总览 整体接入分为以下 **4 个阶段**,预计总耗时 **半天至 1 个工作日**: ``` 阶段一:控制台配置身份源 阶段二:应用集成 ↓ 在 CloudBase 控制台 ↓ 集成 CloudBase JS SDK ↓ 添加 OAuth 2.0 身份源 ↓ 实现登录跳转与回调处理 ↓ 启用托管登录页展示 阶段三:多应用 SSO 配置 阶段四:联调与验证 ↓ 各应用注册回调地址 ↓ 验证单应用登录流程 ↓ 统一指向同一 CloudBase 环境 ↓ 验证多应用 SSO 免登 ``` --- ## 详细接入步骤 ### 阶段一:控制台配置身份源 1. 登录 [CloudBase 控制台](https://console.cloud.tencent.com/tcb),进入目标环境; 2. 左侧菜单进入 **「身份认证」→「登录方式」**; 3. 选择需要接入的身份源(以 Google 为例),点击「启用」; 4. 前往 [Google Cloud Console](https://console.cloud.google.com/) 创建 OAuth 2.0 客户端: - 应用类型选「Web 应用」 - 将控制台提供的「授权回调地址」填入「已获授权的重定向 URI」 5. 将 Google 生成的 **Client ID** 和 **Client Secret** 填回 CloudBase 控制台; 6. 保存并开启该身份源; 7. 进入 **「身份认证」→「托管登录页」**,在「登录方式」列表中找到刚配置的身份源,勾选「展示在登录页」并保存。 > ⚠️ **注意:** 第 7 步是必须操作。身份源保存后不会自动出现在托管登录页,需手动勾选后用户才能在登录页看到对应的登录按钮。 > **更多身份源**(企业微信、GitHub 等)配置步骤类似,均在控制台完成,无需写代码。 --- ### 阶段二:应用集成 在每个需要接入 SSO 的应用中集成 CloudBase JS SDK,按以下步骤完成集成。 **安装 SDK** ```bash npm install @cloudbase/js-sdk ``` 或直接在 HTML 中引用(建议使用本地打包版本): ```html ``` **初始化与登录跳转** ```javascript import cloudbase from '@cloudbase/js-sdk' const app = cloudbase.init({ env: 'your-env-id', // CloudBase 环境 ID region: 'ap-shanghai', // 环境所在地域 accessKey: 'your-publishable-key', // 可公开的 Publishable Key auth: { detectSessionInUrl: true } // 自动处理 OAuth 回调 }) const auth = app.auth() // 检查登录态,未登录则跳转到托管登录页 const session = await auth.getSession() if (!session) { auth.toDefaultLoginPage({ redirect_uri: window.location.href }) } ``` **获取用户信息** ```javascript const session = await auth.getSession() if (session) { const user = await auth.getUser() console.log(user.nickname, user.email) } ``` **登出** ```javascript await auth.signOut() ``` --- ### 阶段三:多应用 SSO 配置 实现多应用 SSO 的关键:**所有应用使用同一个 CloudBase 环境(同一个 env ID)**。 CloudBase Session 以 `env ID` 为维度存储在用户浏览器中,只要多个应用指向同一环境,用户在 App A 登录后,App B 调用 `auth.getSession()` 即可直接拿到已有 Session,无需再次跳转登录页。 ``` App A ──┐ ├── 同一 env ID ──► 共享 CloudBase Session ✅ App B ──┘ ``` **各应用只需额外配置:** 在 CloudBase 控制台的身份源「授权回调地址」中,将 App B 的域名/路径也加入白名单。 --- ### 阶段四:联调与验证 #### 验证单应用登录 1. 浏览器无痕模式,访问应用; 2. 触发登录跳转 → 托管登录页显示已配置的身份源; 3. 选择身份源完成认证; 4. 确认以下检查项: - [ ] 正确跳回应用的 `redirect_uri` - [ ] `auth.getSession()` 返回有效 Session - [ ] `auth.getUser()` 返回正确的用户信息(姓名、邮箱等) - [ ] 登出后 Session 清除,再次访问需重新登录 #### 验证多应用 SSO 1. 在 App A 完成登录; 2. 新标签页打开 App B(同一 CloudBase 环境); 1. App B 调用 `auth.getSession()`,应直接返回 Session,**不触发登录跳转**; 2. 如 App B 仍触发跳转,到托管登录页后也应**无需再次输入密码**(OAuth 身份源侧有已有 Session,自动回调)。 --- ## 登录时序图 ``` 用户 App A / App B CloudBase OAuth 身份源 │ │ │ │ │── 访问应用 ─────►│ │ │ │ │ │ │ │ │ getSession() → 无 │ │ │ │── toDefaultLoginPage() ───────────────► │ │ │ │ │ │◄──── 重定向到 CloudBase 托管登录页 ───│ │ │ │ │ │ │── 选择登录方式 ─────────────────────►│── 重定向到身份源 ──►│ │ │ │ │ │◄──────────────────────── 身份源携带 code 回调 ────────────│ │ │ │ │ │ │ │ 用 code 换 token │ │ │ │ 创建/更新用户 │ │ │ │ 颁发 CloudBase Session │◄──── 携带 Session 重定向回 App ──────│ │ │ │ │ │ │── 访问 App B ──►│ │ │ │ │ getSession() → 有 │ │ │◄── 直接进入,无需再次登录 ───────────│ │ ``` --- ## 账号生命周期管理 | 事件 | 触发方式 | 平台行为 | | ------------- | ----------------- | ----------------------- | | 新用户首次登录 | 通过身份源完成 OAuth 认证 | 自动在 CloudBase 创建用户账号 | | 用户信息变更 | 下次登录时 OAuth 返回新信息 | 自动同步昵称、头像、邮箱 | | 员工离职 | 在 OAuth 身份源侧禁用账号 | 下次登录时认证失败,无法获取新 Session | | 已有 Session 失效 | Session 到期或主动登出 | 需重新走登录流程 | --- ## 常见问题(FAQ) **Q:多个应用必须部署在同一个域名下吗?** A:不需要。只要多个应用使用同一个 CloudBase 环境 ID,Session 就会共享。不同域名的应用均可实现 SSO。 **Q:托管登录页的样式可以自定义吗?** A:支持在控制台对托管登录页进行基础样式配置(Logo、品牌色等)。如需完全自定义 UI,需使用自建登录页(注意:SDK不支持 企业OAuth 2.0 / SAML 身份源)。 **Q:支持同时配置多个身份源吗?** A:支持。可在控制台同时开启多个身份源(如同时开启 Google 和企业微信),托管登录页会展示所有已启用的登录方式,用户自行选择。 **Q:SDK 初始化时 `accessKey` 和 `SecretKey` 有什么区别?** A:`accessKey`(Publishable Key)是可公开的客户端密钥,用于前端 SDK 初始化,泄露无安全风险。`SecretKey` 是服务端密钥,仅用于服务端,**不可暴露在前端代码中**。 --- # 身份认证/其他参考/最佳实践 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/best-practice 本文档提供云开发身份认证与权限控制的最佳实践指南,帮助开发者快速理解和接入身份认证模块,构建安全可靠的应用系统。 ## 功能介绍 ### 核心概念 云开发身份认证体系包含两个核心部分: - **身份认证**:解决"用户是谁"的问题,支持手机号、邮箱、微信、匿名等多种登录方式 - **权限控制**:解决"用户能调用什么资源"的问题,通过角色和策略精细化管理资源访问权限 完整的身份认证接入需要理解:登录方式 → 角色体系 → 成员管理 → 权限策略 → 安全配置。 ### 登录方式 云开发支持多种登录方式,需要先在 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage) 中开启,可根据应用场景灵活选择: | 登录方式 | 适用场景 | 配置入口 | | ---------- | ---------------------- | ------------------------------------------------------------------------ | | 手机号登录 | 需要实名认证的应用 | [登录方式管理](https://tcb.cloud.tencent.com/dev#/identity/login-manage) | | 邮箱登录 | 企业应用、国际化应用 | [登录方式管理](https://tcb.cloud.tencent.com/dev#/identity/login-manage) | | 微信登录 | 微信生态应用 | [登录方式管理](https://tcb.cloud.tencent.com/dev#/identity/login-manage) | | 匿名登录 | 无需注册即可使用的功能 | [注册配置](https://tcb.cloud.tencent.com/dev#/identity/login-manage) | | 用户名密码 | 传统 Web 应用 | [登录方式管理](https://tcb.cloud.tencent.com/dev#/identity/login-manage) | > 💡 **建议**:应用可以同时开启多种登录方式,让用户自主选择。 ### 角色体系 #### 角色定义 角色是权限的载体,通过将用户分配到不同角色,实现精细化的权限管理。系统提供以下预设角色: | 角色类型 | 说明 | 典型使用场景 | | ------------ | ------------------------------------ | ------------------------------------------- | | **管理员** | 拥有所有权限,可管理环境、成员、资源 | 项目负责人、技术主管 | | **所有用户** | 包含所有已登录和未登录用户 | 公开内容访问控制 | | **组织成员** | 添加到组织架构的用户 | 企业内部应用访问 | | **外部用户** | 除组织成员外,非匿名登录的用户 | C端应用用户,需要登录进行访问,如电商应用等 | | **匿名用户** | 未登录的访客 | 公开页面、营销落地页 | > ⚠️ **重要提醒**:管理员角色拥有系统的**最高权限**,包括删除数据、修改配置、管理成员等敏感操作,请谨慎使用。 #### 自定义角色 当预设角色无法满足业务需求时,可以创建自定义角色: 1. 访问 [云开发平台/身份认证/权限控制](https://tcb.cloud.tencent.com/dev#/identity/auth-control) 页面 2. 点击「创建角色」 3. 设置角色标识和描述,例如: - `content_editor`:内容编辑 - `data_analyst`:数据分析 - `finance_admin`:财务管理 4. 为角色配置访问权限 #### 角色权限合并规则 当用户拥有多个角色时,权限合并遵循以下规则: - **允许权限取并集**:用户拥有所有角色的允许权限之和 - **拒绝权限优先**:任一角色的拒绝规则会覆盖其他角色的允许规则 **示例:** ``` 用户角色:内容编辑 + 数据分析 - 内容编辑:允许访问文章表(读、写、改) - 数据分析:允许访问订单表(仅读) - 结果:用户可以读写文章,可以查看订单 ``` ### 成员管理 #### 成员添加 将用户添加到角色,有以下两种方式: **方式一:在角色中添加成员** 1. 访问 [云开发平台/身份认证/权限控制](https://tcb.cloud.tencent.com/dev#/identity/auth-control) 页面找到目标角色 2. 点击角色的「配置成员」 3. 点击「添加成员」批量选择用户 4. 确认后立即生效 **方式二:在组织架构中管理** 1. 访问 [云开发平台/身份认证/组织架构](https://tcb.cloud.tencent.com/dev#/identity/user-organization) 页面 2. 创建部门和岗位结构 3. 将用户添加到对应的组织节点 4. 批量为用户关联角色 #### 成员权限变更 修改用户权限有两种途径: - **修改角色权限**:该角色下所有用户自动继承新权限 - **调整用户角色**:将用户从某个角色移除或添加到新角色 > 💡 **提示**:权限变更可能需要一定时间生效,建议用户退出重新登录。 ### 权限控制 #### 权限配置模型 云开发的权限控制基于以下模型: ``` 权限配置 = 资源范围 + 数据范围 + 操作类型 ``` - **资源范围**:该角色可以访问哪些资源(数据表、云函数、云存储等) - **数据范围**:可以访问哪些数据(全部数据、仅自己创建的数据、特定条件数据) - **操作类型**:可以执行什么操作(查看、创建、修改、删除) #### 数据行权限 通过行级权限控制用户可以访问哪些数据行(记录)。常见的配置方式: - **所有数据**:用户可以访问全部数据 - **仅自己的数据**:用户只能访问自己创建的数据 - **特定条件数据**:用户只能访问满足特定条件的数据(如所属部门、特定状态等) **示例:**销售人员只能查看自己负责的客户数据 #### 数据列权限 通过字段级权限控制用户可以访问哪些字段(列)。可以配置: - **可读字段**:用户可以查看的字段列表 - **隐藏字段**:对用户不可见的敏感字段 **示例:**隐藏销售人员的成本和利润字段 > 💡 **提示**:行级权限和字段级权限可以同时使用,实现更精细的数据访问控制。 #### 操作权限 为角色配置对资源的操作权限: - **查看(Read)**:读取数据 - **创建(Create)**:新增数据 - **修改(Update)**:编辑数据 - **删除(Delete)**:删除数据 不同资源类型支持的操作权限可能有所差异,具体请参考相应的资源文档: - [文档型数据库权限](/database/data-permission) - [MySQL数据库权限](/database/configuration/db/tdsql/data-permission) - [云函数权限控制](/cloud-function/security-rules) ## 典型场景实践示例 ### 场景一:个人博客网站 #### 需求描述 个人博客需要支持游客浏览、用户评论和博主管理三种场景。 #### 角色规划 | 角色 | 权限范围 | 使用对象 | | -------- | ---------------------------- | ---------- | | 匿名用户 | 浏览文章、查看评论 | 所有访客 | | 注册用户 | 浏览文章、发表评论、点赞 | 注册读者 | | 管理员 | 发布文章、管理评论、网站设置 | 博客所有者 | #### 数据权限配置 **文章表(`blog_articles`)权限设置:** - 匿名用户:仅可读取已发布文章 - 注册用户:可读取已发布文章和自己写的草稿 - 管理员:拥有所有权限 **评论表(`comments`)权限设置:** - 匿名用户:仅可查看评论 - 注册用户:可查看所有评论,可修改/删除自己的评论 - 管理员:拥有所有权限 #### 实施步骤 1. **启用匿名登录**:在 [登录方式管理](https://tcb.cloud.tencent.com/dev#/identity/login-manage) 开启「允许匿名登入」 2. **配置数据库权限**:在数据库集合的权限设置中配置对应角色的访问权限,具体请参考:[文档型数据库权限](/database/data-permission) 3. **前端默认调用匿名登录**: ```javascript import cloudbase from '@cloudbase/js-sdk'; const app = cloudbase.init({ env: 'your-env-id' }); const auth = app.auth(); // 默认执行匿名登录操作 auth.signInAnonymously(); ``` ### 场景二:企业内部管理系统 #### 需求描述 企业管理系统需要区分不同部门和岗位的访问权限,实现数据隔离。 #### 角色规划 **自定义角色创建:** 1. 在 [权限控制](https://tcb.cloud.tencent.com/dev#/identity/auth-control) 页面创建自定义角色 2. 根据岗位职责设置角色标识,例如: - `content_editor`:内容编辑 - `data_analyst`:数据分析 - `finance_admin`:财务管理 #### 数据权限配置 **行级权限配置:** 实现"用户只能访问自己部门的数据": 数据行权限配置 **字段级权限配置:** 隐藏敏感字段(如成本、利润): 数据列权限配置 ### 场景三:协同编辑平台 #### 需求描述 多人协同文档编辑平台,允许用户编辑他人创建的文档,但需要严格的权限控制。 #### 角色规划 | 角色 | 权限范围 | 使用对象 | | -------- | -------------------------------------- | ------------ | | 普通用户 | 查看所有文档、编辑自己的文档 | 注册用户 | | 协作者 | 查看所有文档、编辑被授权的文档 | 被邀请的用户 | | 管理员 | 所有文档的完整权限(查看、编辑、删除) | 团队管理者 | #### 实现方案 > ⚠️ **安全警告**:允许修改他人数据是高风险操作,必须谨慎实施权限控制。 **方案一:通过云函数修改数据(推荐)** 云函数在服务端执行,可以绕过客户端权限限制,但需要在函数内部实现严格的权限验证: ```javascript // 云函数:editDocument const cloudbase = require('@cloudbase/node-sdk'); const app = cloudbase.init(); const db = app.database(); exports.main = async (event, context) => { const { documentId, newContent } = event; const { userInfo } = context; // 1. 验证用户登录状态 if (!userInfo || !userInfo.uid) { return { code: 401, message: '未登录' }; } // 2. 查询文档信息 const doc = await db.collection('documents') .doc(documentId) .get(); if (!doc.data || doc.data.length === 0) { return { code: 404, message: '文档不存在' }; } const document = doc.data[0]; // 3. 权限验证:检查是否是作者或协作者 const isOwner = document._openid === userInfo.openid; const isCollaborator = document.collaborators && document.collaborators.includes(userInfo.uid); if (!isOwner && !isCollaborator) { return { code: 403, message: '无权编辑此文档' }; } // 4. 执行更新 const result = await db.collection('documents') .doc(documentId) .update({ content: newContent, lastEditBy: userInfo.uid, lastEditAt: new Date() }); return { code: 0, message: '更新成功', data: result }; }; ``` **方案二:通过安全规则配置(精细控制)** 对于需要更复杂、更精细的权限控制场景,可以使用 CloudBase 的**安全规则**功能。 **CloudBase 支持安全规则的资源:** | 资源类型 | 安全规则文档 | 适用场景 | | ------------ | ------------------------------------------------ | -------------------------------- | | 文档型数据库 | [数据库安全规则](/database/security-rules) | 文档级、字段级的精细权限控制 | | 云存储 | [云存储安全规则](/storage/security-rules) | 文件上传、下载、删除权限控制 | | 云函数 | [云函数安全规则](/cloud-function/security-rules) | 函数调用权限控制(限客户端 SDK) | **使用安全规则实现协同编辑:** ```javascript // 文档型数据库安全规则示例 { // 读权限:所有登录用户都可以查看文档 "read": "auth != null", // 写权限:作者、协作者或管理员可以编辑 "write": "doc._openid == auth.openid || doc.collaborators.includes(auth.uid) || auth.role == 'admin'" } ``` ## 安全配置建议 ### 管理员角色的谨慎使用 > ⚠️ **重要提醒**:管理员角色拥有系统的**最高权限**,包括删除数据、修改配置、管理成员等敏感操作。 **建议做法:** - 为日常操作创建权限受限的自定义角色 - 仅在必要时使用管理员账号 - 定期审查管理员账号列表 - 为管理员账号启用双因素认证 ### 权限最小化原则 遵循"最小权限原则",仅授予用户完成工作所需的最小权限: ``` ✓ 推荐:销售人员只能查看自己负责的客户数据 ✗ 避免:销售人员可以查看所有客户数据 ``` ## 常见问题与解决方案 ### Q1: 为什么用户已经添加了权限,但仍然无法访问? **排查步骤:** 1. **检查角色分配**:确认用户已被分配到正确的角色 2. **检查策略状态**:确认策略已保存并关联到正确的角色上 3. **检查生效时间**:权限变更可能需要一定时间生效,建议用户退出重新登录 4. **检查网关策略**:可能被网关层的限制拦截,检查用户关联的网关权限策略 5. **查看报错信息**:查看具体拒绝原因,针对性解决 ### Q2: 多个角色的权限如何合并? 当用户拥有多个角色时,权限合并遵循以下规则: - **允许权限取并集**:用户拥有所有角色的允许权限之和 - **拒绝权限优先**:任一角色的拒绝规则会覆盖其他角色的允许规则 **示例:** ``` 用户角色:内容编辑 + 数据分析 - 内容编辑:允许访问文章表(读、写、改) - 数据分析:允许访问订单表(仅读) - 结果:用户可以读写文章,可以查看订单 ``` ## 相关资源 - [JS SDK (V2)/身份认证](/api-reference/webv2/authentication) - [HTTP API/登录认证接口](/http-api/auth/登录认证接口) - [文档型数据库/权限管理](/database/data-permission) - [MySQL数据库/权限管理](/database/configuration/db/tdsql/data-permission) - [云函数/权限管理](/cloud-function/security-rules) - [云存储/权限管理](/storage/data-permission) --- # 身份认证/其他参考/常见问题 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/faq ## 匿名登录与未登录有什么区别? 从 C 端用户的角度来讲: - 匿名登录和未登录在上手使用上没有任何区别,都无需注册 - 匿名登录用户有独立的用户标识,在同设备有效期内,用户可以产生独立的私有数据 - 与未登录相比,匿名登录可以转为正式用户,匿名登录期间的私有数据会自动继承到正式用户名下 从应用开发者的角度来讲: - CloudBase 匿名登录产生的匿名用户本质上是一个有效用户,拥有唯一的用户 ID。从而可以为其创建私有的 [数据库](/database/introduce) 和 [云存储](/storage/introduce.md) 数据,以及配合 [安全规则](/rule/introduce.md) 制定个性化的访问策略 - 未登录模式是纯粹的无登录态访问,该模式下的访问都不会进入用户的追踪统计 - 未登录的用户默认权限下无法使用任何 CloudBase 的服务和资源,而匿名登录在基础权限下也可以进行对应的资源读写,也可以结合安全规则实现更细粒度的管控 **匿名用户是否会过期?** CloudBase 对匿名用户的有效期限策略是:每个设备同时只存在一个匿名用户,并且此用户永不过期。当然,如果用户手动清除了设备或浏览器的本地数据,那么匿名用户的数据便会被同步清除,再次调用 CloudBase 匿名登录 API 会产生一个新的匿名用户。 --- # storage Documentation > 云开发存储服务,提供文件上传下载、CDN加速、安全规则、权限管理等云端存储解决方案 # 云存储/未命名文档 > 当前文档链接: https://docs.cloudbase.net/storage/introduce --- sidebar_label: 概述 --- # 云存储 CloudBase 云存储提供稳定、安全、低成本、简单易用的云端存储服务,支持任意数量和形式的非结构化数据存储,如图片、文档、音频、视频、文件等。底层基于腾讯云 COS(对象存储),并集成 CDN 加速、数据万象、内容审核等能力。 ## 两种云存储模式 {#two-modes} 云存储分为**传统模式**与 **PG 模式**两种类型,取决于环境形态: | 维度 | 传统模式云存储 | PG 模式云存储 | | ---- | ---- | ---- | | 适用环境 | 传统形态环境(默认) | [PG 模式环境](../quick-start/pg-overview) | | Bucket 概念 | 与环境强绑定,主要做路径前缀 | `storage.buckets` 表的一行,支持 SQL 级控制 | | 权限模型 | 内置权限模式(`READONLY` / `PRIVATE` 等)+ JSON 安全规则 | PostgreSQL **RLS Policy**(SQL) | | 上传链路 | 客户端 SDK 拿签名后**直传 COS** | 客户端 → **Storage API** → COS,事务内协调元数据与对象 | | 元数据 | 业务侧补齐 | 由 Storage API 自动写入 `storage.objects`,可 SQL 查询 | | 选择方式 | 创建环境时默认 | 创建环境时选择 PostgreSQL 数据库 | :::tip 阅读路径 - **传统模式**:本目录大部分文档([控制台](./manage) / [SDK](./sdk) / [权限](./data-permission) / [HTTP API](../http-api/storage/云存储)) - **PG 模式**:[PG 模式云存储](./neo/introduce) + 同目录下的 [HTTP API](../http-api/storage-pg/pg-storage-api) - **CDN / 内容审核 / 图片处理 / 数据万象**:两种模式**共用**,见本目录下的"高级功能"与"数据万象"分组 下文以**传统模式**为主介绍。如果你正在使用 PG 模式环境,请直接前往 [PG 模式云存储](./neo/introduce)。 ::: ## 管理方式 {#management} CloudBase 云存储提供多种管理方式,您可以根据自己的需求选择最适合的方式: | 管理方式 | 特点 | 适用场景 | 优势 | | --- | --- | --- | --- | | [**控制台管理**](./manage) | 图形化界面,可配置权限、缓存设置 | 日常管理 | 上手快速,可视化操作 | | [**SDK 管理**](./sdk) | 可集成到应用中 | 应用程序集成 | 灵活性高,可定制化程度高 | | [**CLI 工具管理**](../cli-v1/storage) | 命令行操作,支持批量处理 | 自动化脚本、批量处理 | 高效批量操作,适合自动化部署 | | [**cosBrowser**](./cosbrowser) | 图形化界面,支持批量处理 | 频繁文件管理 | 结合图形界面便捷性与批量操作的高效性 | | [**HTTP API**](../http-api/storage/云存储) | 标准 RESTful 接口 | 服务端集成、跨语言调用 | 跨语言、跨平台,可在任意后端调用 | 根据您的具体需求和使用场景,选择合适的管理方式可以显著提高工作效率。例如,开发阶段可以使用 SDK,运维阶段可以使用 CLI 工具,而日常管理则可以使用控制台或 cosBrowser,跨语言后端集成可使用 HTTP API。 ## 主要特性 {#features} ### 默认支持 CDN 加速 云存储无需进行繁杂的配置,默认支持 CDN 加速,并提供免费的 CDN 域名。CDN 会将云存储的内容分发至最接近用户的节点,直接由服务节点快速响应,可以有效降低用户访问延迟。 ### 身份验证集成 CloudBase 云存储与 CloudBase 用户身份验证无缝集成,您可以结合用户身份认证和[安全规则](./security-rules)对云存储里的文件设置访问权限——例如云存储的文件只对已登录用户公开或仅限使用微信登录的用户访问等。 在 **PG 模式**下,身份认证与权限均由 PostgreSQL 的 `auth` schema 与 RLS 策略统一承载,详见 [PG 模式云存储 - 权限管理](./neo/data-permission)。 ### 高可扩展性 CloudBase 云存储可以与 CloudBase 其它多种服务配合使用,例如: - 将文件的 fileID 存储到数据库 - 配合 CloudBase 扩展能力使用,如图像安全审核、图像处理、图像标签、图像盲水印等 ## HTTP API {#http-api} 云存储提供完整的 RESTful HTTP API,可在任意后端语言中调用: - [**传统模式 HTTP API**](../http-api/storage/云存储):对象上传 / 下载 / 删除 / 复制等基础操作 - [**PG 模式 HTTP API**](../http-api/storage-pg/pg-storage-api):完整的 Bucket / Object CRUD、签名 URL、对象元信息、分片上传等,权限由 RLS 控制 --- # 云存储/文件管理/控制台管理文件 > 当前文档链接: https://docs.cloudbase.net/storage/manage 本文档将指导您如何通过云开发控制台对云存储进行全面管理,包括文件管理、权限设置和缓存配置等核心功能。 ## 文件管理 进入 [云开发平台/云存储](https://tcb.cloud.tencent.com/dev?#/storage) 文件管理功能,您可以方便地查看和管理云存储中的所有文件: 1. 进入**文件管理**页面,您可以查看云存储空间中的所有文件列表 2. 单击文件名或「详情」按钮,即可查看文件的详细信息 ![文件管理界面](https://qcloudimg.tencent-cloud.cn/raw/6776fc43907bfc49c91deb61f676f47f.png) ## 权限设置 云存储权限设置允许您控制谁可以访问您的文件: 1. 访问 [云开发平台/云存储/权限设置](https://tcb.cloud.tencent.com/dev#/storage?tab=auth) 页面 2. 根据您的业务需求,选择合适的存储权限并保存 3. 您还可以通过 [自定义安全规则](/storage/security-rules) 实现更精细的权限控制 - 当设置「所有用户可读」时,**临时访问链接**则可永久访问 - 当设置非「所有用户可读」时,**访问链接** 可配置有效时间 ![权限设置界面](https://qcloudimg.tencent-cloud.cn/raw/29f12cda7344ba59d4ce3dc1a3a7e83d.png) ## 缓存配置 ### 缓存配置概述 云存储的缓存配置功能可以帮助您优化文件访问性能和节省带宽成本: - 云存储内的文件默认启用 CDN 加速,您可以通过缓存配置控制 CDN 的过期规则 - 合理配置缓存时间可以有效提升命中率,降低回源率,节省带宽成本 - CDN 缓存资源的工作原理: - **未过期资源**:用户请求直接从 CDN 节点返回,提高访问速度 - **已过期资源**:CDN 节点需要向源站重新获取内容,然后缓存并返回给用户 ### 配置缓存规则 按照以下步骤配置云存储的缓存规则: 1. 访问 [缓存配置](https://tcb.cloud.tencent.com/dev?#/storage?tab=cache) 页面 2. 单击「编辑缓存配置」按钮,在默认配置基础上添加自定义缓存规则 > 默认配置为:所有文件缓存 2 分钟 3. 选择以下三种配置方式之一: | 配置方式 | 说明 | 示例 | | -------------- | -------------------------------------- | ---------------------- | | **按文件类型** | 针对特定后缀名的文件设置缓存时间 | `.png`、`.jpg`、`.php` | | **按文件夹** | 针对特定文件夹下的所有文件设置缓存时间 | `/images/`、`/static/` | | **按文件** | 针对特定文件或文件模式设置缓存时间 | `/test/abc/*.jpg` | 4. 配置规则说明: - 可填入多项配置,每项用分号(`;`)隔开 - 内容区分大小写 - 文件类型必须以`.`开头,文件夹必须以`/`开头 - 缓存时间设置为 0 时表示不缓存,所有请求将转发至源站 - 缓存时间最大值不能超过 365 天 5. 单击「保存」按钮完成配置,部署过程需等待约 5 分钟 :::tip 重要提示 配置部署过程中,若再次编辑缓存配置,将覆盖之前的配置。系统以最后一次部署结果为准。 ::: ### 缓存策略优先级 在配置多条缓存策略时,需要注意以下优先级规则: - 配置列表中**底部**的策略优先级高于**顶部**的策略 - 您可以通过拖动列表前的移动图标调整各策略的优先级 - 当多条策略同时匹配一个资源时,将以最后一次匹配的策略为准 #### 优先级示例 假设某域名配置了以下缓存规则: | 优先级 | 目标 | 缓存时长 | | ------- | --------------- | -------- | | 5(最低) | 所有文件 | 2 分钟 | | 4 | .php .jsp .aspx | 0 秒 | | 3 | .jpg .png .gif | 300 秒 | | 2 | /test/*.jpg | 400 秒 | | 1(最高) | /test/abc.jpg | 200 秒 | 当访问资源 `www.test.com/test/abc.jpg` 时,匹配过程如下: 1. 匹配"所有文件"规则 → 命中,缓存时间为 2 分钟 2. 匹配".php .jsp .aspx"规则 → 未命中 3. 匹配".jpg .png .gif"规则 → 命中,缓存时间更新为 300 秒 4. 匹配"/test/*.jpg"规则 → 命中,缓存时间更新为 400 秒 5. 匹配"/test/abc.jpg"规则 → 命中,缓存时间最终确定为 200 秒 **最终结果**:该资源的缓存时间为 200 秒,因为最后一次匹配的规则优先级最高。 --- # 云存储/文件管理/SDK 管理文件 > 当前文档链接: https://docs.cloudbase.net/storage/sdk CloudBase 云存储提供了完整的文件管理功能,包括上传、下载、删除、复制文件以及获取临时链接等操作。本文将详细介绍如何使用这些功能 :::tip PG 模式 SDK 调用方式与传统模式完全一致,但**底层链路改走 Storage API**(不再客户端直传 COS),权限由 RLS 控制。如需了解差异,参见 [PG 模式云存储 - 概述](./neo/introduce#diff-upload-path);服务端 / 跨语言调用见 [HTTP API](../http-api/storage-pg/pg-storage-api)。 ::: | SDK 类型 | 适用平台 | | ------------------------------------------------------------------------------------------------------------ | ------------ | | [小程序 SDK](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/guide/storage/api.html) | 小程序 | | [JS SDK](/api-reference/webv3/storage) | Web 浏览器 | | [Node SDK](/api-reference/server/node-sdk/storage) | Node.js 环境 | | [HTTP API(传统模式)](../http-api/storage/云存储) | 通用 | | [HTTP API(PG 模式)](../http-api/storage-pg/pg-storage-api) | 通用 | ## 上传文件 您可以上传任意数量、格式的文件至 CloudBase 云存储,也可以自定义文件、目录的路径和名字。 默认情况下,只有通过了 [CloudBase 身份验证](/authentication-v2/auth/introduce) 的用户才可以向云存储空间上传/删除文件,因此在用户端上传文件时需先进行登录认证。 :::tip 提示 您也可以使用 [自定义安全规则](/storage/security-rules),为云存储设置更宽松或更严格的读写权限。 ::: 使用 SDK 可以向云存储空间上传文件,并返回该文件全局唯一标识 **fileID**。 ```js import tcb from "@cloudbase/js-sdk"; const app = tcb.init({ env: "your-env-id", }); app .uploadFile({ // 云存储的路径 cloudPath: "dirname/filename", // 需要上传的文件,File 类型 filePath: document.getElementById("file").files[0], }) .then((res) => { // 返回文件 ID console.log(res.fileID); }); ``` ```js wx.cloud .uploadFile({ cloudPath: "example.png", // 上传至云端的路径 filePath: "", // 小程序临时文件路径,需结合小程序相关 API 获取 }) .then((res) => { // 返回文件 ID console.log(res.fileID); }); ``` ```js const tcb = require("@cloudbase/node-sdk"); const fs = require("fs"); const app = tcb.init(); app .uploadFile({ cloudPath: "path/test.jpg", fileContent: fs.createReadStream("test.jpg"), }) .then((res) => { // 返回文件 ID console.log(res.fileID); }); ``` ```bash # 第一步:获取上传信息 curl -L 'https://your-envId.api.tcloudbasegateway.com/v1/storages/get-objects-upload-info' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ -d '[ { "objectId": "dirname/filename" } ]' # 第二步:使用返回的上传信息上传文件 curl -X PUT '' \ -H 'Authorization: ' \ -H 'X-Cos-Security-Token: ' \ -H 'X-Cos-Meta-Fileid: ' \ --data-binary @your-file ``` :::tip 提示 - 上传时文件名需要符合 [文件名规范](#文件名命名限制); - cloudPath 为云存储文件或文件夹的相对根目录的路径,为 **目录/文件名** 的形式,cloudPath 不需要以 `/` 开头; - 如果将文件上传至同一路径则是覆盖写,默认情况下,不允许 A 用户覆盖写 B 用户的文件。 ::: ## 下载文件 默认情况下,CloudBase 云存储内的文件对所有用户公开可读。 :::tip 提示 您也可以使用 [自定义安全规则](/storage/security-rules),为云存储设置更宽松或更严格的读写权限。 ::: 使用 SDK 可以下载云存储空间里的文件,调用时只需传入云存储文件全网唯一的 fileID 。 ```js import tcb from "@cloudbase/js-sdk"; const app = tcb.init({ env: "your-env-id", }); app .downloadFile({ fileID: "cloud://a/b/c", }) .then((res) => { console.log(res); }); ``` ```js wx.cloud .downloadFile({ fileID: "cloud://a/b/c", // 文件 ID }) .then((res) => { // 返回临时文件路径 console.log(res.tempFilePath); }); ``` ```js const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "your-env-id", }); app .downloadFile({ fileID: "cloud://a/b/c", }) .then((res) => { // fileContent 类型为 Buffer console.log(res.fileContent); }); ``` ```bash # 获取下载链接 curl -L 'https://your-envId.api.tcloudbasegateway.com/v1/storages/get-objects-download-info' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ -d '[ { "cloudObjectId": "cloud://a/b/c" } ]' # 使用返回的下载链接下载文件 curl -L '' -o downloaded-file ``` :::tip 提示 如果您需要在浏览器中可以直接下载云存储里的文件,或将云存储作为图床,可以使用文件的**下载地址**或[获取文件临时链接](#获取临时链接)。 ::: ## 删除文件 默认情况下,只有通过了 [CloudBase 身份验证](/authentication-v2/auth/introduce) 的用户才可以向云存储空间上传/删除文件,因此在用户端删除文件时需先进行登录认证。 :::tip 提示 - 单次调用最多可以删除 50 个文件,更多需分批处理; - 默认情况下,只有该文件的上传创建者和管理员才有权删除相应的文件,不允许 A 用户删除 B 用户的文件; - 您也可以使用 [自定义安全规则](/storage/security-rules),为云存储设置更宽松或更严格的读写权限。 ::: 使用 SDK 调用 deleteFile 方法可以删除云存储空间单个或多个指定文件。 ```js import tcb from "@cloudbase/js-sdk"; const app = tcb.init({ env: "your-env-id", }); const auth = app.auth({ persistence: "local", //用户显式退出或更改密码之前的30天一直有效 }); app .deleteFile({ fileList: ["cloud://a/b/c", "cloud://d/e/f"], }) .then((res) => { console.log(res.fileList); }); ``` ```js wx.cloud .deleteFile({ fileList: ["cloud://a/b/c", "cloud://d/e/f"], }) .then((res) => { console.log(res.fileList); }); ``` ```js const tcb = require("@cloudbase/node-sdk"); const app = tcb.init(); app .deleteFile({ fileList: ["cloud://a/b/c", "cloud://d/e/f"], }) .then((res) => { console.log(res.fileList); }); ``` ```bash curl -L 'https://your-envId.api.tcloudbasegateway.com/v1/storages/delete-objects' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ -d '[ { "cloudObjectId": "cloud://your-envId.bucket/not-exist" }, { "cloudObjectId": "cloud://your-envId.bucket/file.jpg" } ]' ``` ## 复制文件 您可以通过 Node SDK 和 Open API 将云存储空间中的文件复制到指定路径,并通过参数设置达到移动文件的效果,该 API 支持批量复制。API 文档参考: - [Node SDK 复制文件](https://docs.cloudbase.net/api-reference/server/node-sdk/storage#copyfile) - [Open API 复制文件](https://docs.cloudbase.net/api-reference/openapi/storage#batchcopyfile) :::tip 说明 - 目标文件路径不能和源文件路径一致 - 不支持复制的过程中重命名,即复制前后文件名应保持一致 - 复制操作的执行受到存储桶权限、源文件和路径权限、目标文件和路径权限影响,执行复制操作不会改变文件原有权限 - 单次操作的文件数量不超过 50 个,更多需分批处理 ::: ```js const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "xxx", // 填入环境 ID }); const path = "a.png"; // 填入源文件路径 const fileList = [ { srcPath: path, dstPath: `dst/${path}`, // 填入目标文件路径 removeOriginal: true, // 复制后删除源文件,等效为移动文件 }, ]; const result = await app.copyFile({ fileList, }); ``` ## 获取临时链接 您可以使用 fileID 换取云存储空间指定文件的 https 链接(云存储提供免费的 CDN 域名)。 ```js import tcb from "@cloudbase/js-sdk"; const app = tcb.init({ env: "your-env-id", }); const auth = app.auth({ persistence: "local", //用户显式退出或更改密码之前的30天一直有效 }); app .getTempFileURL({ fileList: ["cloud://a/b/c", "cloud://d/e/f"], }) .then((res) => { console.log(res.fileList); }); ``` ```js wx.cloud .getTempFileURL({ fileList: ["cloud://a/b/c", "cloud://d/e/f"], }) .then((res) => { console.log(res.fileList); }); ``` ```js const tcb = require("@cloudbase/node-sdk"); const app = tcb.init(); app .getTempFileURL({ fileList: ["cloud://a/b/c", "cloud://d/e/f"], }) .then((res) => { console.log(res.fileList); }); ``` :::tip 提示 - 公有读的文件获取的 https 链接不会过期,例如默认情况下的权限就是公有读,获取的链接永久有效; - 私有读的文件获取的 https 链接为临时链接,例如您可以结合用户身份认证和安全规则设置文件的权限为仅文件的上传创建者或管理员可读,此时只有通过了云开发身份验证的用户才有权限换取临时链接; - 有效期可以动态设置,超过有效期再请求临时链接时会被拒绝,保证了文件的安全; - 一次最多可以取 50 个,更多需分批处理。 ::: ### 设置公有读步骤 1. 前往[云开发平台](https://tcb.cloud.tencent.com/dev) - 云存储 - 权限设置 ![](https://qcloudimg.tencent-cloud.cn/raw/096c1ced3992bf1251f00e678276b9ec.png) ## 文件名命名限制 在使用 CloudBase 云存储时,文件名需要遵循以下规则: - 不能为空 - 不能以/开头 - 不能出现连续/ - 编码长度最大为 850 个字节 - 推荐使用大小写英文字母、数字,即[a-z,A-Z,0-9]和符号 -,!,\_,.,\* 及其组合 - 不支持 ASCII 控制字符中的字符上(↑),字符下(↓),字符右(→),字符左(←),分别对应 CAN(24),EM(25),SUB(26),ESC(27) - 如果用户上传的文件或文件夹的名字带有中文,在访问和请求这个文件或文件夹时,中文部分将按照 URL Encode 规则转化为百分号编码。 - 不建议使用的特殊字符: ` ^ " \ { } [ ] ~ % # \ > < 及 ASCII 128-255 十进制 - 可能需特殊处理后再使用的特殊字符: , : ; = & \$ @ + ?(空格)及 ASCII 字符范围:00-1F 十六进制(0-31 十进制)以及 7F(127 十进制) --- # 云存储/文件管理/云存储 > 当前文档链接: https://docs.cloudbase.net/cli-v1/storage 云存储是云开发为用户提供的文件存储能力,用户可以通过云开发提供的 CLI 工具、SDK 对存储进行操作,如上传、下载文件。存储在云存储中的文件默认提供 CDN 加速访问,用户可以快速访问云存储中的文件。 ## 路径说明 - `localPath` 为本地文件或文件夹的路径,为 `目录/文件名` 的形式,如 `./index.js`、`static/css/index.css` 等。 - `cloudPath` 为云存储文件或文件夹的相对根目录的路径,为 `目录/文件名` 的形式,如 `index.js`、`static/css/index.js` 等。 :::caution ⚠️ 注意事项 Windows 系统中 localPath 为本地路径形式,是系统可以识别的路径,通常使用 `\` 分隔符。`cloudPath` 是云端文件路径,均需要使用 `/` 分隔符。 ::: ## 上传文件 您可以使用下面的命令上传文件/文件夹,当 CLI 检测到 localPath 为文件夹时,会自动上传文件内的所有文件。 ```bash tcb storage upload localPath cloudPath # 指定上传重试次数(0-10,默认 1) tcb storage upload localPath cloudPath --times 3 # 指定重试间隔(ms,默认 500) tcb storage upload localPath cloudPath --interval 1000 ``` ## 下载文件 您可以使用下面的命令下载文件/文件夹,需要下载文件夹时,需要指定 `--dir` 参数。 ```bash # 下载文件 tcb storage download cloudPath localPath # 下载文件夹 tcb storage download cloudPath localPath --dir ``` ### 下载全部文件 当指定 cloudPath 为 `/` 时,即代表下载云存储中的全部文件: ```bash tcb storage download / localPath --dir ``` ## 删除文件 :::tip 版本提示 `tcb storage rm` 命令自 v3.0.0 起新增,是推荐的删除命令,功能更强大。 ::: :::tip 命令变更 旧命令 `tcb storage delete` 已废弃,保留向后兼容,请使用新命令 `tcb storage rm`。 ::: ```bash # 删除单个文件 tcb storage rm cloudPath # 删除文件夹(递归删除) tcb storage rm cloudPath --dir # 强制删除,不提示确认 tcb storage rm cloudPath --force # 模拟运行,只显示将要删除的文件,不实际执行 tcb storage rm cloudPath --dry-run ``` **命令参数**: | 参数 | 说明 | |------|------| | `--dir` | 删除目标是文件夹(递归删除) | | `--recursive` | 同 `--dir` | | `--force` | 强制删除,不提示确认 | | `--dry-run` | 模拟运行,只显示将要删除的文件,不实际删除 | ## 列出文件列表 列出文件夹下的文件: ```bash tcb storage list [cloudPath] ``` ## 复制/移动文件 :::tip 版本提示 自 v3.0.0 起新增此功能。 ::: 在云端复制或移动文件(仅支持文件级别,不支持文件夹): ```bash # 复制文件 tcb storage cp # 移动文件(复制后删除源文件) tcb storage cp --move # 强制覆盖已存在的目标文件 tcb storage cp --force # 跳过已存在的目标文件 tcb storage cp --skip ``` **命令参数**: | 参数 | 说明 | |------|------| | `--move` | 移动文件(复制后删除源文件) | | `--force` | 强制覆盖已存在的文件 | | `--skip` | 跳过已存在的文件 | | `-e, --env-id ` | 环境 ID | ## 搜索文件 :::tip 版本提示 自 v3.0.0 起新增此功能。 ::: 按文件名模式搜索云端文件: ```bash # 基本搜索(支持通配符,如 *.jpg) tcb storage search # 在指定目录下搜索 tcb storage search --dir images/ # 按文件类型过滤(如 jpg、png、pdf) tcb storage search --type jpg # 将 pattern 作为正则表达式处理 tcb storage search --regex ``` **命令参数**: | 参数 | 说明 | |------|------| | `-e, --env-id ` | 环境 ID | | `--dir ` | 指定搜索目录(不指定则搜索根目录) | | `--type ` | 按文件类型过滤(如 `jpg`、`png`、`pdf`) | | `--regex` | 将 pattern 作为正则表达式处理 | ## 获取文件访问链接 获取文件的临时访问链接: ```bash tcb storage url cloudPath # 指定链接有效期(秒,默认 3600) tcb storage url cloudPath --expire 7200 ``` ## 获取文件信息 获取文件的简单信息: ```bash tcb storage detail cloudPath ``` ## 权限管理 :::warning 已废弃(v3.0.0+) `tcb storage get-acl`、`tcb storage set-acl`、`tcb storage rules get`、`tcb storage rules update` 等存储权限命令自 **v3.0.0** 起已废弃。 请迁移到 `tcb permission` 命令进行资源权限管理,详见 [权限管理](./permission/management.md)。 ::: ### 获取访问权限 ```bash tcb storage get-acl ``` ### 设置访问权限 ```bash tcb storage set-acl --acl <权限值> ``` **可选权限值**:`READONLY`、`PRIVATE`、`ADMINWRITE`、`ADMINONLY` ### 获取权限规则 ```bash tcb storage rules get ``` ### 更新权限规则 ```bash # 使用预设权限 tcb storage rules update --acl READONLY # 使用自定义规则(JSON 格式) tcb storage rules update --acl CUSTOM --rules '[{"role":"anyone","action":"read"}]' ``` **可选权限值**:`READONLY`、`PRIVATE`、`ADMINWRITE`、`ADMINONLY`、`CUSTOM`(自定义规则时使用) --- ## 废弃命令说明 以下旧格式命令(冒号分隔)已废弃,保留向后兼容,请迁移到新命令: | 旧命令 | 新命令 | |--------|--------| | `tcb storage:upload` | `tcb storage upload` | | `tcb storage:download` | `tcb storage download` | | `tcb storage:delete` | `tcb storage rm` | | `tcb storage:list` | `tcb storage list` | | `tcb storage:url` | `tcb storage url` | | `tcb storage:detail` | `tcb storage detail` | | `tcb storage:get-acl` | `tcb permission` | | `tcb storage:set-acl` | `tcb permission` | ## 命令速查 | 命令 | 说明 | |------|------| | `tcb storage upload ` | 上传文件/文件夹 | | `tcb storage download ` | 下载文件/文件夹 | | `tcb storage rm ` | 删除文件/文件夹(推荐) | | `tcb storage list [path]` | 列出文件列表 | | `tcb storage cp ` | 复制/移动文件 | | `tcb storage search ` | 搜索文件 | | `tcb storage url ` | 获取临时访问链接 | | `tcb storage detail ` | 获取文件信息 | | `tcb storage get-acl` | 获取访问权限(已废弃,使用 `tcb permission`) | | `tcb storage set-acl` | 设置访问权限(已废弃,使用 `tcb permission`) | | `tcb storage rules get` | 获取权限规则(已废弃,使用 `tcb permission`) | | `tcb storage rules update` | 更新权限规则(已废弃,使用 `tcb permission`) | --- ## PostgreSQL 环境 :::tip 版本提示 以下命令面向 **PostgreSQL 架构** 的云开发环境,自 `@cloudbase/cli@3.4.0` 起提供。 - 这些命令对应 SDK 的 PG Storage HTTP API(参见 [`cloudbase-manager-node` PG 接口](../api-reference/manager/node/storage.md#postgresql-环境-http-api))。 - 在 PG 环境下,**本页前面的传统 `tcb storage` 平铺命令(`upload` / `download` / `delete` / `rm` / `list` / `url` / `detail` / `cp` / `search` 等)会被拒绝执行**,CLI 会在提示中给出对应的 PG 子命令替代项。 - 创建 PG 环境可使用:`tcb env create --alias --package --postgresql --yes`。 - 查询某个环境是否为 PG 环境,可运行 `tcb env list` 查看类型。 ::: ### 命令分组速览 | 命令组 | 功能 | | --- | --- | | `tcb storage buckets` | 管理 Storage bucket(PG 元信息) | | `tcb storage policies` | 查看 storage schema 下的 RLS 策略 | | `tcb storage objects` | 对象 CRUD、签名、复制、移动等 | PG 环境下,`tcb db execute` 也支持执行 PostgreSQL SQL,包括 bucket / 策略管理 SQL(详见 [关系型数据库(MySQL)](./db/mysql/management.md#tcb-db-execute) 中的 PostgreSQL 扩展说明)。 --- ### `tcb storage buckets list` 列出当前 PG 环境下所有 Storage bucket。 ```bash tcb storage buckets list [-e ] [--json] ``` - `--json`:输出原始 `PGExecuteResult`(含 `Columns` / `Rows`)。 - 表格输出列:`Id` / `Name` / `Public` / `Size Limit` / `MIME Types` / `Created` / `Updated`。 > Bucket 操作通过 `tcb db execute` 执行 SQL 完成,例如: > ```bash > # 创建 bucket > tcb db execute -e --sql "INSERT INTO storage.buckets (id, name, public, created_at, updated_at) VALUES ('avatars', 'avatars', false, now(), now())" > ``` --- ### `tcb storage policies list` 列出 storage schema 下 `buckets` / `objects` 表上的 RLS 策略。 ```bash tcb storage policies list [--table buckets|objects] [-e ] [--json] ``` | 参数 | 说明 | | --- | --- | | `--table ` | 按表过滤,可选 `buckets` / `objects`;不传则同时返回两表的策略 | | `--json` | 输出原始 `PGExecuteResult` | > RLS 策略变更通过 `tcb db execute --role postgres` 执行 `CREATE POLICY` / `DROP POLICY`。 --- ### `tcb storage objects upload` 上传本地文件到 Storage bucket。 ```bash tcb storage objects upload -b [options] ``` | 参数 | 说明 | | --- | --- | | `` | 本地文件绝对/相对路径,必须是文件 | | `` | 远端对象名(不能以 `/` 开头) | | `-b, --bucket ` | Bucket ID,可用环境变量 `TCB_BUCKET_ID` 替代 | | `--content-type ` | 显式 MIME,不传则按 `扩展名 → magic bytes` 推断 | | `--cache-control ` | 整数自动转 `max-age=`,字符串原样作为 `Cache-Control` | | `--metadata ` | 自定义 metadata JSON,内部 Base64 编码到 `X-Metadata` | | `--robots-tag ` | `X-Robots-Tag` 头 | | `--upsert` | POST 模式下允许覆盖已存在对象 | | `--use-put` | 使用 PUT 方法(始终覆盖) | | `--json` | 输出 SDK 原始返回(`{ Id, Key }`) | 示例: ```bash tcb storage objects upload ./hello.txt test/hello.txt -b my-bucket tcb storage objects upload ./hello.txt test/hello.txt -b my-bucket --content-type text/plain --upsert ``` --- ### `tcb storage objects download` 从 Storage bucket 下载对象到本地。 ```bash tcb storage objects download [localPath] -b [options] ``` | 参数 | 说明 | | --- | --- | | `` | 对象名 | | `[localPath]` | 本地路径;省略时输出到 stdout;指向已存在目录会自动拼上 `key` 的 basename | | `-b, --bucket ` | Bucket ID | | `-m, --method ` | `GET`(默认)/ `HEAD` | | `--download ` | 触发浏览器下载(`?download=`) | | `--if-none-match ` | `If-None-Match` 头(仅认证下载路径生效) | | `--if-modified-since ` | `If-Modified-Since` 头 | | `--range ` | `Range` 头(断点续传) | | `--overwrite` | 当 `localPath` 已存在时允许覆盖 | | `--json` | 仅返回元信息(`{ status, headers }`),不落盘 | 行为说明: - **已登录** → 走认证下载路径(`downloadAuthenticatedObject`),可访问 private/public bucket; - **未登录** → 回退到 `downloadPublicObject`,仅能访问 public bucket; - `--method HEAD` 或 `--json` 模式下不会写本地文件。 --- ### `tcb storage objects stat` 查询对象元信息。 ```bash tcb storage objects stat -b [options] ``` | 参数 | 说明 | | --- | --- | | `-m, --method ` | `GET`(默认)/ `HEAD`,HEAD 仅认证路径支持 | | `--json` | 输出 `{ status, headers, body }`,`body` 即 `IObjectInfoHttpPayload` | 表格输出按以下字段顺序展示:`id` / `name` / `bucket_id` / `version` / `size` / `content_type` / `cache_control` / `etag` / `last_modified` / `created_at` / `metadata`。 --- ### `tcb storage objects list` 列出 bucket 中的对象,支持前缀过滤、分页与排序。 ```bash tcb storage objects list -b [options] ``` | 参数 | 说明 | | --- | --- | | `--prefix ` | 路径前缀过滤 | | `--limit ` | 分页大小,1~1000,默认 20,超出范围会以 `INVALID_PARAM`(exit 3)拒绝 | | `--cursor ` | 翻页游标(上一次响应的 `nextCursor`) | | `--with-delimiter` | 使用 `/` 分隔文件夹 | | `--sort-by ` | 排序列,可选 `name` / `created_at` / `updated_at` | | `--sort-order ` | 排序方向,可选 `asc`(默认)/ `desc` | | `--json` | 输出 SDK 原始返回(含 `folders` / `objects` / `hasNext` / `nextCursor`) | > `list` 接口需要登录态,未登录时会以 `AUTH_FAILED` 失败。 --- ### `tcb storage objects rm` 删除对象,单次最多 20 个。 ```bash tcb storage objects rm -b [-y] [--json] ``` | 参数 | 说明 | | --- | --- | | `` | 1~20 个对象名;超过 20 个会以 `INVALID_PARAM`(exit 3)拒绝 | | `-y, --yes` | 跳过删除前的确认提示 | | `--json` | 单 key 走 `deleteObject`,输出 `{ message }`;多 key 走 `deleteObjects`,输出 `IDeletedObjectInfo[]` | 表格输出列:`Key` / `Status`,`Status` 为 `DELETED` / `NOT_FOUND`(批量删除中不存在的对象会被服务端静默跳过)。 --- ### `tcb storage objects cp` 复制对象到新位置。 ```bash tcb storage objects cp -b [options] ``` | 参数 | 说明 | | --- | --- | | `-b, --bucket ` | 源 Bucket ID | | `--dst-bucket ` | 目标 Bucket ID,默认与源 Bucket 相同 | | `--copy-metadata ` | 是否复制源对象 metadata,默认 `true`;`false` 时 `--metadata` / `--user-metadata` 才生效 | | `--metadata ` | 系统 metadata JSON,与源 metadata 合并;COS 仅识别 `cacheControl` / `mimetype` / `contentType` | | `--user-metadata ` | 用户自定义 metadata JSON,**整体替换** 目标 user_metadata | | `--upsert` | 允许覆盖目标位置已有对象 | | `--json` | 输出 SDK 原始返回(`ICopyObjectHttpResult`) | 校验细节: - 同 bucket 下源 key 与目标 key 相同且未传 `--upsert` → `INVALID_PARAM`; - `--metadata` / `--user-metadata` 仅在 `--copy-metadata false` 时生效,其他情况下传入会被 CLI 直接拒绝。 --- ### `tcb storage objects mv` 移动(rename)对象到新位置;服务端一次性完成「复制 + 删除源对象」。 ```bash tcb storage objects mv -b [--dst-bucket ] [--json] ``` > 同 bucket 下源 key 与目标 key 完全相同会被本地拦截为 `INVALID_PARAM`。 --- ### `tcb storage objects sign` 为对象生成签名 URL,**默认下载签名**,加 `--upload` 切换为上传签名。 ```bash # 下载签名 tcb storage objects sign -b [--expires-in ] [--json] # 上传签名 tcb storage objects sign --upload -b [--upsert] [--json] ``` | 参数 | 说明 | | --- | --- | | `` | 下载模式 1~500 个;上传模式仅允许 1 个 | | `--upload` | 切换到上传签名模式 | | `--expires-in ` | 过期时间(秒),>= 1,默认 600;**仅下载模式** | | `--upsert` | 允许覆盖(`x-upsert: true`),**仅上传模式** | | `--json` | 下载模式输出 `[{ path, signedURL, error }]`;上传模式输出 `{ url, token }` | 互斥校验: - `--upload` 与 `--expires-in` 互斥; - 下载模式下传 `--upsert` 会被拒绝; - `--upload` 一次只能签 1 个 key。 --- ## 退出码 | 退出码 | 含义 | | --- | --- | | `3 INVALID_INPUT` | 包含 `INVALID_PARAM` / `MISSING_PARAM` / `FLAT_CMD_NOT_AVAILABLE_IN_PG` / `ENV_NOT_PG` 等 | 详见 [exit-codes](./exit-codes.md)。 --- # 云存储/文件管理/COSBrowser 管理文件 > 当前文档链接: https://docs.cloudbase.net/storage/cosbrowser ### 工具介绍 COSBrowser 是一款可视化界面工具,支持用户通过简单的界面操作进行数据上传、下载、生成访问链接等功能。 #### 适用场景 - ✅ 中小数据量迁移(< 10GB) - ✅ 需要可视化操作界面 - ✅ 批量上传下载文件 - ✅ 管理云存储文件 #### 操作步骤 **1. 下载并安装 COSBrowser** 访问 [COSBrowser 下载页面](https://cloud.tencent.com/document/product/436/11366) 下载并安装客户端。 **2. 使用密钥登录** :::warning 重要说明 必须使用 **密钥登录** 方式,并指定存储桶名和地域。使用腾讯云账号直接登录无法管理 CloudBase 云存储桶。 ::: 登录步骤: 1. 打开 COSBrowser 工具 2. 选择「密钥登录」 3. 填入以下信息: - **SecretId / SecretKey**:在 [访问密钥管理](https://console.cloud.tencent.com/cam/capi) 中获取 - **存储桶名称**:[云开发/云存储](https://tcb.cloud.tencent.com/dev?#/storage) 获取存储桶名称 ![](https://qcloudimg.tencent-cloud.cn/raw/713d70fa79f35c0e6ac8ad8dbeb89d14.png) - **所属地域**:选择您的环境所在地域(云存储这里固定为: `ap-shanghai`) **3. 文件管理** 登录成功后,详细使用方法请参考:[COSBrowser 使用文档](https://cloud.tencent.com/document/product/436/38103) --- --- # 云存储/文件管理/批量删除任务机制 > 当前文档链接: https://docs.cloudbase.net/storage/batch-delete-task 当您在 [云开发控制台 - 云存储 - 文件管理](https://tcb.cloud.tencent.com/dev?#/storage) 中执行**删除文件**或**删除文件夹**等批量操作时,系统会改用**异步删除任务**方式处理,避免大批量删除阻塞页面。本文档介绍异步删除任务的机制与查看方式。 ## 什么是批量删除任务机制 批量删除任务机制是云存储提供的一种**异步批量删除能力**。当您删除的文件 / 文件夹数量较多时,系统会在后台异步执行删除操作,您可以继续在控制台进行其他操作,无需等待删除完成。 相较于原来的同步删除,异步任务机制带来以下变化: | 变更点 | 说明 | | --- | --- | | 执行方式 | 同步删除 → **异步删除任务** | | 操作提示 | 触发后弹出"任务管理"对话框,显示当前删除任务的进度 | | 任务保留 | 删除任务完成后,会在"任务管理"中保留 **3 天**,方便您回溯历史操作 | ## 触发场景 以下操作会触发批量删除任务: - 在文件列表中**勾选多个文件**后,单击「删除」按钮 - 在文件列表中**勾选多个文件夹**后,单击「删除」按钮 - 对单个文件夹发起「删除文件夹」操作(若文件夹下文件较多) ## 查看任务进度与历史 1. 在「文件管理」页面右上角找到「**任务管理**」入口 2. 单击后右侧会滑出任务管理面板,显示当前正在执行的删除任务与历史任务 3. 任务记录保留 **3 天**,过期后自动清理 ![任务管理面板](https://qcloudimg.tencent-cloud.cn/raw/c237a9c0f8582ade12bca325315f3558.png) ## 注意事项 - **处理存在延迟**:异步删除任务在后台执行,文件不会立即从存储桶中消失。若您对删除的即时性有强诉求(例如删除后立刻重建同名文件),请在任务完成后再次确认状态。 - **谨慎操作**:删除操作不可恢复,请确认勾选范围无误后再单击「删除」。 - **任务保留时间**:删除任务的执行记录仅保留 3 天,如需长期审计,建议结合 [CDN 日志分析](/storage/log-analyze) 或服务端日志自行归档。 - **删除限频**:触发异步删除任务后,过短时间内连续触发新的批量删除可能会被系统限流,请稍候再试。 ## 相关参考 - [控制台管理文件](/storage/manage) - [SDK 管理文件](/storage/sdk) - [CLI 管理文件](/cli-v1/storage) - [常见问题](/storage/faq) --- # 云存储/权限管理/基础权限 > 当前文档链接: https://docs.cloudbase.net/storage/data-permission CloudBase 提供了多层次的存储权限管理机制,确保文件安全的同时满足不同业务场景的权限控制需求。 云存储进行读写时会以 `_openid` 字段作为文件归属判定依据 :::tip PG 模式 本文描述的是**传统模式**的权限模型。如果你使用的是 PG 模式环境,权限完全由 PostgreSQL 的 **RLS Policy** 表达,详见 [PG 模式云存储 - 权限管理](./neo/data-permission) 与 [RLS 策略模式库](./neo/rls-patterns)。 ::: ## 权限管理体系 CloudBase 云存储权限管理包含两个层次: | 权限类型 | 控制粒度 | 适用场景 | 配置复杂度 | | ---------------- | -------- | -------------- | ---------- | | **基础权限控制** | 集合级别 | 简单的权限需求 | 低 | | **安全规则权限** | 文档级别 | 复杂的业务逻辑 | 高 | ## 基础权限控制 ### 配置方式 在 [云开发平台/云存储/权限设置](https://tcb.cloud.tencent.com/dev?#/storage?tab=auth) 页面,为云存储设置对应的权限: ![云存储-基础权限配置界面](https://qcloudimg.tencent-cloud.cn/raw/59eaa4a19a6f15a48a66bad0b2e9ce76.png) ### 权限选项 基础权限控制提供四种预设权限类型,根据用户身份和文件特性选择: #### 1. 所有用户可读,仅创建者及管理员可写(公共读写) **权限说明:** - ✅ **读取**:所有人(包括未登录用户)都可以访问文件 - 🔒 **写入**:仅文件创建者本人和管理员可以修改/删除 **适用场景:** - 用户头像、用户发布的公开内容 - 用户评论、论坛帖子 - 社交分享的图片和视频 **使用建议:** - ✅ 适合内容展示类应用(博客、论坛、社交应用) - ✅ 文件可以通过 URL 直接在浏览器/小程序中展示 - ⚠️ 任何人都可以访问文件,注意不要上传敏感信息 --- #### 2. 仅创建者及管理员可读写(私有读写) **权限说明:** - 🔒 **读取**:仅文件创建者本人和管理员可以访问 - 🔒 **写入**:仅文件创建者本人和管理员可以修改/删除 **适用场景:** - 用户个人资料、身份证照片 - 用户订单文件、支付凭证 - 私人相册、个人文档 **使用建议:** - ✅ 适合个人信息管理和隐私保护 - ⚠️ **重要:选择此权限后,文件 URL 不能直接在前端使用** - ⚠️ 如果在网页/小程序中直接引用图片 URL,会导致**图片无法显示**(403 权限错误) --- #### 3. 所有用户可读,仅管理员可写(公共只读) **权限说明:** - ✅ **读取**:所有人(包括未登录用户)都可以访问文件 - 🔒 **写入**:仅管理员可以上传/修改/删除文件 **适用场景:** - 商品图片、轮播图、活动海报 - 静态资源(Logo、图标、背景图) - 公告文件、下载资源 **使用建议:** - ✅ 适合只读配置和参考数据 - ✅ 文件可以通过 URL 直接在浏览器/小程序中展示 - ✅ 防止普通用户误删或篡改重要文件 --- #### 4. 仅管理员可读写(完全私有) **权限说明:** - 🔒 **读取**:仅管理员可以访问 - 🔒 **写入**:仅管理员可以上传/修改/删除 **适用场景:** - 后台日志文件、系统备份 - 敏感数据导出文件 - 财务报表、内部文档 **使用建议:** - ✅ 适合需要服务端处理的敏感数据 - ⚠️ **前端应用完全无法访问这些文件** - ⚠️ 通过云函数(管理员权限)访问 ### 权限对比速查表 | 权限类型 | 匿名访问 | 普通用户读 | 创建者读 | 管理员读 | 普通用户写 | 创建者写 | 管理员写 | 直接使用 URL | |---------|---------|----------|---------|---------|----------|---------|---------|-------------| | **公共读写** | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ 可以 | | **私有读写** | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ 需要临时链接 | | **公共只读** | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ 可以 | | **完全私有** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ 完全不可 | --- ## ⚠️ 常见问题 #### 问题 1:图片/文件无法在前端显示(403 错误) **原因:** 文件设置了私有读权限("仅创建者及管理员可读写" 或 "仅管理员可读写") **解决方案:** 1. **方案 A(推荐)**:改用公共读权限("所有用户可读,仅创建者及管理员可写") 2. **方案 B**:使用[临时访问链接](/storage/sdk#%E8%8E%B7%E5%8F%96%E4%B8%B4%E6%97%B6%E9%93%BE%E6%8E%A5) #### 问题 2:用户无法上传文件 **原因:** 文件设置了"仅管理员可写"权限 **解决方案:** - 改用 "所有用户可读,仅创建者及管理员可写" 权限 - 或在云函数中使用管理员权限上传 #### 问题 3:如何切换权限? **步骤:** 1. 登录 [云开发平台/云存储/权限设置](https://tcb.cloud.tencent.com/dev?#/storage?tab=auth) 2. 选择新的权限类型 3. 点击「保存」 4. ⚠️ **权限修改后立即生效,可能影响现有文件的访问** ## 安全规则权限 安全规则权限是 CloudBase 数据库提供的**文档级权限控制**能力,相比基础权限控制具有更高的灵活性和精确度。 详情请参考 [云存储安全规则](/storage/security-rules) --- # 云存储/权限管理/安全规则 > 当前文档链接: https://docs.cloudbase.net/storage/security-rules **安全规则** 是 CloudBase 云存储的 **文件级权限控制** 功能,通过自定义表达式精确控制每个文件的读写权限。 :::tip PG 模式 本文为**传统模式**专有功能。在 PG 模式下,文件级权限由 PostgreSQL 的 **RLS Policy** 表达,能力更强,参见 [PG 模式云存储 - RLS 策略模式库](./neo/rls-patterns)。 ::: ## 配置入口 在 [云开发平台/云存储](https://tcb.cloud.tencent.com/dev?#/storage) ,点击「权限设置」,选择「自定义安全规则」即可配置安全规则。 > ⚠️ 注意: > > - 云开发控制台和服务端始终有所有文件读写权限,安全规则仅对客户端(小程序端、Web 等)发起的请求有效 > - 修改安全规则后,权限生效需要 1-3 分钟,请耐心等待 > - 发布前务必评估规则的安全性,避免 `public` 规则导致数据泄露 ## 基本语法 ```json { "read": "表达式", // 读取权限控制表达式 "write": "表达式" // 写入权限控制表达式 } ``` ### 常用模板 ```json { "read": true, "write": "resource.openid == auth.openid || resource.openid == auth.uid" } ``` ```json { "read": "resource.openid == auth.openid || resource.openid == auth.uid", "write": "resource.openid == auth.openid || resource.openid == auth.uid" } ``` ```json { "read": true, "write": false } ``` ```json { "read": false, "write": false } ``` > 💡 注意:此配置下所有客户端请求均被拒绝,仅管理员可通过云开发控制台或服务端 SDK 访问。 ```json { "read": "auth != null", "write": "auth != null && auth.loginType != 'ANONYMOUS'" } ``` ## 表达式语法 ### 内置变量 | 变量名 | 说明 | 示例 | | ------------ | ------------ | ------------------------- | | **auth** | 用户登录信息 | `auth.openid`、`auth.uid` | | **resource** | 文件资源信息 | `resource.openid` | #### auth 对象属性 | 属性 | 说明 | 类型 | 示例值 | | ------------- | ------------------------ | ------ | ----------- | | **uid** | 用户唯一标识(Web 端) | string | `"abc123"` | | **openid** | 用户唯一标识(小程序端) | string | `"oABC123"` | | **loginType** | 登录方式 | string | `"WECHAT"` | ##### LoginType 枚举值 | 枚举值 | 登录方式 | | ------------- | ------------ | | WECHAT_PUBLIC | 微信公众号 | | WECHAT_OPEN | 微信开放平台 | | ANONYMOUS | 匿名登录 | | EMAIL | 邮箱登录 | | CUSTOM | 自定义登录 | #### resource 对象属性 | 属性 | 说明 | 类型 | 示例值 | | ---------- | ---------------------------------------------------------------- | ------ | ------------------ | | **openid** | 文件创建者唯一标识 | string | `"oABC123"` | | **path** | 文件在云存储中的相对路径(不包含桶名),格式为 path/filename.jpg | string | `photos/photo.jpg` | ### 运算符 | 运算符 | 说明 | 示例 | | -------- | -------- | ----------------------------------------------- | | **==** | 等于 | `auth.uid == resource.openid` | | **!=** | 不等于 | `auth.loginType != 'ANONYMOUS'` | | **\>** | 大于 | `now \> resource.metadata.expireTime` | | **\<** | 小于 | `now \< resource.metadata.publishTime` | | **\>=** | 大于等于 | `auth.vipLevel \>= 3` | | **\<=** | 小于等于 | `resource.size \<= 10485760` | | **&&** | 逻辑与 | `auth != null && auth.loginType == 'WECHAT'` | | **\|\|** | 逻辑或 | `auth.uid == resource.openid \|\| auth.isAdmin` | ### 操作类型 | 操作 | 说明 | 默认值 | | --------- | -------------------------- | ------- | | **read** | 读取文件(下载、获取 URL) | `false` | | **write** | 写入文件(上传、删除) | `false` | ## 正则表达式支持 安全规则支持使用正则表达式来匹配文件路径,但仅支持 `.test()` 方法,不支持常见的 JavaScript 字符串方法。 ### 支持的写法 ```json { "read": "/test/.test(resource.path)", "write": "false" } ``` 匹配路径中包含 `test` 的所有文件。 ```json { "read": "/^test\\//.test(resource.path)", "write": "false" } ``` 匹配以 `test/` 开头的所有文件。 > 💡 注意:路径中的斜杠 `/` 必须使用双反斜杠 `\\/` 进行转义。 ```json { "read": "/.*\\.png$/.test(resource.path)", "write": "false" } ``` 匹配所有 `.png` 文件。 > 💡 注意:点号 `.` 需要转义为 `\\.`,`$` 表示路径结尾。 ```json { "read": "/test|uploads/.test(resource.path)", "write": "false" } ``` 匹配路径中包含 `test` 或 `uploads` 的文件。 ```json { "read": "/public/.test(resource.path) && auth.uid != null", "write": "/public/.test(resource.path) && auth.loginType != 'ANONYMOUS'" } ``` 允许登录用户读取 `public` 路径的文件,非匿名用户可写入。 ### 不支持的写法 以下方法在安全规则中 **不可用**: | 方法 | 示例 | 替代方案 | | -------------- | --------------------------------------- | ------------------------------------- | | `startsWith()` | `resource.path.startsWith('test/')` | 使用 `/^test\\//.test(resource.path)` | | `includes()` | `resource.path.includes('test/')` | 使用 `/test/.test(resource.path)` | | `indexOf()` | `resource.path.indexOf('test/') === 0` | 使用 `/^test\\//.test(resource.path)` | | `match()` | `resource.path.match(/^test\//)` | 使用 `/^test\\//.test(resource.path)` | | `substr()` | `resource.path.substr(0, 5) == 'test/'` | 使用正则表达式或字符串相等判断 | ### 正则表达式语法规则 | 规则 | 说明 | 示例 | | ---------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | **必须使用 `.test()`** | 正则表达式必须通过 `.test()` 方法调用 | `/^test/.test(resource.path)` | | **斜杠转义** | 路径分隔符 `/` 需要写成 `\\/` | `/^uploads\\//.test(resource.path)` | | **特殊字符转义** | 正则特殊字符需要转义,如 `.` 写成 `\\.` | `/.*\\.jpg$/.test(resource.path)` | | **支持或运算** | 可使用竖线进行简单或运算 | `/test\|uploads/.test(resource.path)` | | **不支持分组** | 不能使用括号进行复杂分组 | 不支持 `/^(test\|uploads)\\//.test(...)` 或 `/\.(png\|jpg)$/.test(...)`,需使用 `/^test\\//.test(...) \|\| /^uploads\\//.test(...)` | | **支持锚点** | 支持 `^`(开头)和 `$`(结尾) | `/^public/.test(...)` 或 `/\\.png$/.test(...)` | ### 实用示例 #### 多个公开目录 ```json { "read": "/^test\\//.test(resource.path) || /^uploads\\//.test(resource.path) || /^public\\//.test(resource.path)", "write": "resource.openid == auth.openid" } ``` 允许所有用户读取 `test/`、`uploads/`、`public/` 目录下的文件,但只有创建者可写入。 #### 特定类型文件公开 ```json { "read": "/\\.png$/.test(resource.path) || /\\.jpg$/.test(resource.path) || /\\.jpeg$/.test(resource.path) || /\\.gif$/.test(resource.path)", "write": "auth != null" } ``` 公开所有图片文件的读取权限,登录用户可上传。 > 💡 注意:由于不支持括号分组,需要使用多个正则表达式通过 `||` 连接。 #### 分层权限控制 ```json { "read": "/^public\\//.test(resource.path) || (auth != null && /^private\\//.test(resource.path) && resource.openid == auth.uid)", "write": "resource.openid == auth.openid || resource.openid == auth.uid" } ``` - `public/` 目录所有人可读 - `private/` 目录仅文件创建者可读 - 所有文件仅创建者可写 ## 应用场景 ### 个人相册 **需求**:用户只能查看和管理自己上传的照片。 ```json { "read": "resource.openid == auth.openid || resource.openid == auth.uid", "write": "resource.openid == auth.openid || resource.openid == auth.uid" } ``` ```js // 小程序端 - 上传照片 wx.cloud.uploadFile({ cloudPath: `photos/${Date.now()}.jpg`, filePath: tempFilePath, }); // Web SDK - 获取自己的照片列表(需配合云函数查询) app.callFunction({ name: "getMyPhotos", }); ``` ### 公共相册 **需求**:只可读取公共文件夹 `public` 的文件 ```json { "read": "/^public\\//.test(resource.path)", "write": "auth.uid == resource.openid || auth.openid == resource.openid" } ``` ## 跨平台兼容 由于小程序端使用 `auth.openid`,Web 端使用 `auth.uid`,建议在安全规则中同时判断两者: ```json { "read": "resource.openid == auth.openid || resource.openid == auth.uid", "write": "resource.openid == auth.openid || resource.openid == auth.uid" } ``` --- # 云存储/高级功能/自定义 CDN > 当前文档链接: https://docs.cloudbase.net/storage/cdn 云存储支持配置自定义 CDN,将静态资源通过 CDN 网络快速分发到全球用户,实现更低延迟、更高性价比的资源加速。 ## 前置条件 - 已有完成备案的自定义域名 - 已开通 [腾讯云 CDN 服务](https://console.cloud.tencent.com/cdn) ## 配置步骤 ### 1. 进入云存储文件管理 进入 [云开发平台 - 云存储文件管理](https://tcb.cloud.tencent.com/dev?#/storage?tab=file) ### 2. 添加自定义 CDN 点击「添加自定义 CDN」,填入加速域名(您的自定义域名),选择 SSL 证书。如无证书可前往 [SSL 证书控制台](https://console.cloud.tencent.com/ssl) 申请。 ### 3. 完成授权配置 点击「添加」完成授权配置。 ### 4. 启用 CDN 配置 启用 CDN 配置。 ### 5. 管理 CDN 域名 可在「自定义 CDN」中查看已授权的 CDN 域名,并可对其进行管理。 ### 6. 访问云存储文件 通过自定义 CDN 域名访问云存储中的文件。 ## 重要配置说明 :::tip 注意事项 ### 鉴权配置 开启自定义 CDN 后,文件访问需经过云存储鉴权和 CDN 鉴权。若云存储权限为公有读,需前往 [CDN 控制台 - 域名管理 - 访问控制](https://console.cloud.tencent.com/cdn/domains),关闭 **防盗链** 和 **鉴权配置**。 ### CNAME 解析 在 CDN 控制台完成自定义域名 CNAME 解析及等待部署完成。 ::: ### 公有读场景配置示例 **场景说明:** ![](https://cloudcache.tencent-cloud.com/qcloud/ui/static/static_source_business/48dea873-f32a-4080-887f-b9768a04a196.png) **关闭 CDN 防盗链和鉴权:**
## 常见问题 ### CNAME 配置失败 CNAME 校验未通过时无法开启自定义 CDN,请参考 [配置 CNAME](https://cloud.tencent.com/document/product/228/3121) 完成域名解析。 ### 计费说明 配置自定义 CDN 后,相关费用由腾讯云 CDN 收取,详见 [计费概述](https://cloud.tencent.com/document/product/228/2949)。 ### 小程序域名配置 小程序使用自定义 CDN 需配置 request 合法域名: 1. 登录微信公众平台,进入小程序管理后台 2. 在「开发管理」中找到「服务器域名」 3. 在「request合法域名」中添加自定义CDN域名 4. 保存设置 ![](https://cloudcache.tencent-cloud.com/qcloud/ui/static/static_source_business/0906ac62-6978-4f9e-b234-0464c14e6e64.png) --- # 云存储/高级功能/云调用扩展 > 当前文档链接: https://docs.cloudbase.net/storage/extension 云存储可以与云开发提供的云调用扩展服务联动,提供「存储+处理」一体化解决方案。您可以根据实际需求安装相应的扩展能力,实现图像处理、图像标签识别、图像盲水印、图像安全审核等功能。 ## 处理方式 云开发提供两种图片处理方式: - **URL 拼接参数**:适用于单次处理,直接在图片 URL 后拼接处理参数 - **持久化处理**:适用于批量处理或需要保存处理结果的场景,使用 SDK 进行处理 ## 方式一:URL 拼接参数 此方式适用于单次图片处理,无需安装 SDK,直接在图片 URL 后拼接处理参数即可。处理后的链接可在小程序、Web 网页中使用,也可用于图床。 ### 使用方法 在云存储图片的下载地址或临时链接后,使用 `&` 符号拼接处理参数。 **示例**:将图片等比例缩小到原来的 20% ```javascript // 原始图片地址 https://786c-xly-xrlur-1300446086.tcb.qcloud.la/hehe.jpg?sign=b8ac757538940ead8eed4786449b4cd7&t=1591752049 // 拼接缩放参数后 https://786c-xly-xrlur-1300446086.tcb.qcloud.la/hehe.jpg?sign=b8ac757538940ead8eed4786449b4cd7&t=1591752049&imageMogr2/thumbnail/!20p ``` ### 常用处理参数 ```js const downloadUrl = "https://786c-xly-xrlur-1300446086.tcb.qcloud.la/hehe.jpg?sign=xxx&t=xxx"; // 等比缩放 downloadUrl + "&imageMogr2/thumbnail/!20p" // 缩放到原图的 20% downloadUrl + "&imageMogr2/thumbnail/!50px" // 宽度缩放到 50%,高度不变 downloadUrl + "&imageMogr2/thumbnail/!x50p" // 高度缩放到 50%,宽度不变 // 指定尺寸 downloadUrl + "&imageMogr2/thumbnail/640x" // 宽度 640px,高度等比缩放 downloadUrl + "&imageMogr2/thumbnail/x355" // 高度 355px,宽度等比缩放 downloadUrl + "&imageMogr2/thumbnail/640x355" // 限定最大宽高,等比缩放 downloadUrl + "&imageMogr2/thumbnail/640x355r" // 限定最小宽高,等比缩放 downloadUrl + "&imageMogr2/thumbnail/640x355!" // 强制缩放到指定尺寸(可能变形) downloadUrl + "&imageMogr2/thumbnail/150000@" // 限制总像素数不超过 150000 // 裁剪效果 downloadUrl + "&imageMogr2/iradius/300" // 内切圆裁剪,半径 300px downloadUrl + "&imageMogr2/rradius/100" // 圆角裁剪,半径 100px // 其他效果 downloadUrl + "&imageMogr2/rotate/90" // 顺时针旋转 90 度 downloadUrl + "&imageMogr2/format/png" // 转换为 PNG 格式 downloadUrl + "&imageMogr2/blur/8x5" // 高斯模糊(半径 8,sigma 5) // 获取图片信息 downloadUrl + "&imageInfo" // 返回图片基础信息(JSON 格式) ``` > 💡 **提示**: > - 参数中 `p` 表示百分比,`x` 表示像素值 > - 多个处理参数可以叠加使用,用 `|` 符号分隔 > - 更多参数说明请参考 [图像处理文档](https://cloud.tencent.com/document/product/460/6924) ## 方式二:持久化图像处理 此方式适用于需要批量处理或保存处理结果的场景,通过 SDK 调用云调用扩展能力。 ### 安装扩展 SDK ```bash npm install --save @cloudbase/extension-ci@latest ``` ```bash npm install --save @cloudbase/extension-ci-wxmp@latest ``` ```bash npm install --save @cloudbase/extension-ci@latest ``` > ⚠️ **微信小程序注意事项**: > - 需要将图片处理域名 `*.pic.ap-shanghai.myqcloud.com` 添加到小程序的 request 合法域名列表中 > - 配置路径:微信公众平台 > 开发 > 开发管理 > 开发设置 > 服务器域名 ### 代码示例 ```js import tcb from 'tcb-js-sdk'; import extCi from '@cloudbase/extension-ci'; // 初始化 SDK const app = tcb.init({ env: 'your-env-id' }); // 注册扩展 tcb.registerExtension(extCi); // 读取文件内容 const readFile = (file) => { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => { const arrayBuffer = new Uint8Array(e.target.result); resolve(arrayBuffer); }; reader.readAsArrayBuffer(file); }); }; // 处理图片 async function processImage() { try { // 获取用户选择的文件 const file = document.getElementById('selectFile').files[0]; const fileContent = await readFile(file); // 调用图像处理扩展 const result = await app.invokeExtension('CloudInfinite', { action: 'ImageProcess', // 操作类型:ImageProcess cloudPath: 'processed/demo.png', // 处理后保存的路径 fileContent: fileContent, // 文件内容 operations: { rules: [ { fileid: '/processed/demo.png', // 输出文件路径 rule: 'imageMogr2/format/png' // 处理规则:转换为 PNG } ] } }); console.log('处理结果:', result); return result; } catch (err) { console.error('图像处理失败:', err); throw err; } } ``` ```js import extCi from '@cloudbase/extension-ci-wxmp'; // 初始化云开发 wx.cloud.init({ env: 'your-env-id' }); // 注册扩展 wx.cloud.registerExtension(extCi); // 处理图片 async function processImage() { try { // 选择图片 const { tempFilePaths } = await wx.chooseImage({ count: 1, sizeType: ['original'] }); // 上传文件到云存储 const uploadResult = await wx.cloud.uploadFile({ cloudPath: `temp/${Date.now()}.jpg`, filePath: tempFilePaths[0] }); // 调用图像处理扩展 const result = await wx.cloud.invokeExtension('CloudInfinite', { action: 'ImageProcess', // 操作类型 cloudPath: uploadResult.fileID, // 源文件 ID operations: { rules: [ { fileid: '/processed/demo.png', // 输出文件路径 rule: 'imageMogr2/format/png|imageMogr2/thumbnail/!50p' // 转 PNG 并缩放 } ] } }); console.log('处理结果:', result); return result; } catch (err) { console.error('图像处理失败:', err); wx.showToast({ title: '处理失败', icon: 'none' }); } } ``` ```js const tcb = require('@cloudbase/node-sdk'); const extCi = require('@cloudbase/extension-ci'); const fs = require('fs'); // 初始化 SDK const app = tcb.init({ env: 'your-env-id' }); // 注册扩展 tcb.registerExtension(extCi); // 处理图片 async function processImage() { try { // 读取本地文件 const fileContent = fs.readFileSync('./demo.jpg'); // 调用图像处理扩展 const result = await app.invokeExtension('CloudInfinite', { action: 'ImageProcess', // 操作类型 cloudPath: 'demo.jpg', // 源文件路径 fileContent: fileContent, // 文件内容 operations: { rules: [ { fileid: '/processed/demo.png', // 输出文件路径 rule: 'imageMogr2/format/png' // 处理规则 } ] } }); console.log('处理结果:', result); return result; } catch (err) { console.error('图像处理失败:', err); throw err; } } // 执行处理 processImage(); ``` ### 参数说明 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | action | String | 是 | 操作类型。可选值:
- `ImageProcess`:图像处理
- `DetectType`:图片安全审核
- `WaterMark`:图片盲水印
- `DetectLabel`:图像标签识别 | | cloudPath | String | 是 | 云存储中的文件路径或文件 ID | | fileContent | Buffer/Uint8Array | 否 | 文件内容(上传新文件时需要) | | operations | Object | 是 | 处理参数,具体格式见下方说明 | **operations 对象结构**: ```js { rules: [ { fileid: '/output/path.png', // 输出文件路径 rule: 'imageMogr2/format/png' // 处理规则(与 URL 拼接参数相同) } ] } ``` ### 常见错误处理 ```js async function processImageWithErrorHandling() { try { const result = await app.invokeExtension('CloudInfinite', { // ... 参数配置 }); return result; } catch (err) { // 处理常见错误 if (err.code === 'RESOURCE_NOT_FOUND') { console.error('源文件不存在,请检查 cloudPath 参数'); } else if (err.code === 'PERMISSION_DENIED') { console.error('权限不足,请检查安全规则配置'); } else if (err.code === 'EXTENSION_NOT_INSTALLED') { console.error('未安装图像处理扩展,请前往云开发平台安装'); } else { console.error('图像处理失败:', err.message); } throw err; } } ``` ## 相关文档 - [图像处理扩展能力文档](https://cloud.tencent.com/document/product/460/6924) --- # 云存储/高级功能/CDN 日志分析 > 当前文档链接: https://docs.cloudbase.net/storage/log-analyze 本文档提供 CDN 日志分析脚本,帮助您快速统计域名流量、客户端访问和地域分布等关键数据。 ## 功能概述 CDN 日志分析脚本支持以下功能: - 统计「域名+请求路径」流量 TOP10 - 统计「客户端 IP」流量 TOP10 - 统计「省份/地区」流量 TOP10 - 自动解析 `.tar.gz` 格式的日志文件 - 生成可读的分析报告文件 ## 使用步骤 ### 1. 准备日志文件 您可以前往 [腾讯云工单系统](https://console.cloud.tencent.com/workorder/category?source=14) 提交工单进行获取您当前环境的CDN日志 将 CDN 日志文件组织为以下目录结构: ``` your-workspace/ ├── 20251110/ # 待分析的日期目录 │ ├── 00/ │ │ └── log.tar.gz │ ├── 01/ │ └── ... └── analyze.py # 分析脚本 ``` ### 2. 运行分析脚本 将以下脚本保存为 `analyze.py`:
示例脚本 ```python #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ CDN日志分析脚本 统计域名+请求路径、客户端IP、省份/地区编号的流量TOP10 """ import os import sys import tarfile from collections import defaultdict import time # 境内省份映射表 PROVINCE_MAP = { '22': '北京', '86': '内蒙古', '146': '山西', '1208': '甘肃', '1467': '青海', '145': '黑龙江', '1464': '辽宁', '120': '江苏', '122': '山东', '1442': '浙江', '1135': '湖北', '1069': '河北', '1177': '天津', '119': '宁夏', '152': '陕西', '1468': '新疆', '1445': '吉林', '2': '福建', '121': '安徽', '1050': '上海', '182': '河南', '1465': '江西', '1466': '湖南', '118': '贵州', '153': '云南', '1051': '重庆', '1068': '四川', '1155': '西藏', '4': '广东', '173': '广西', '1441': '海南', '0': '其他', '1': '港澳台', '-1': '境外' } # 境外地区映射表 REGION_MAP = { # 服务地区 '2000000001': '亚太一区', '2000000002': '亚太二区', '2000000003': '亚太三区', '2000000004': '中东', '2000000005': '北美', '2000000006': '欧洲', '2000000007': '南美', '2000000008': '非洲', # 客户端地区 '-20': '亚洲', '-21': '南美洲', '-22': '北美洲', '-23': '欧洲', '-24': '非洲', '-25': '大洋洲', # 其他地区 '-15': '亚洲其他', '-14': '南美洲其他', '-13': '北美洲其他', '-12': '欧洲其他', '-11': '非洲其他', '-10': '大洋洲其他', '-2': '境外其他', # 具体国家/地区 '35': '尼泊尔', '57': '泰国', '73': '印度', '144': '越南', '192': '法国', '207': '英国', '208': '瑞典', '209': '德国', '213': '意大利', '214': '西班牙', '386': '阿联酋', '391': '以色列', '397': '乌克兰', '417': '哈萨克斯坦', '428': '葡萄牙', '443': '希腊', '471': '沙特阿拉伯', '529': '丹麦', '565': '伊朗', '578': '挪威', '669': '美国', '692': '叙利亚', '704': '塞浦路斯', '706': '捷克', '707': '瑞士', '708': '伊拉克', '714': '荷兰', '717': '罗马尼亚', '721': '黎巴嫩', '725': '匈牙利', '726': '格鲁吉亚', '731': '阿塞拜疆', '734': '奥地利', '736': '巴勒斯坦', '737': '土耳其', '759': '立陶宛', '763': '阿曼', '765': '斯洛伐克', '766': '塞尔维亚', '770': '芬兰', '773': '比利时', '809': '保加利亚', '811': '斯洛文尼亚', '812': '摩尔多瓦', '813': '马其顿', '824': '爱沙尼亚', '835': '克罗地亚', '837': '波兰', '852': '拉脱维亚', '857': '约旦', '884': '吉尔吉斯斯坦', '896': '爱尔兰', '901': '利比亚', '904': '亚美尼亚', '921': '也门', '971': '卢森堡', '1036': '新西兰', '1044': '日本', '1066': '巴基斯坦', '1070': '马耳他', '1091': '巴哈马', '1129': '阿根廷', '1134': '孟加拉', '1158': '柬埔寨', '1159': '中国澳门', '1176': '新加坡', '1179': '马尔代夫', '1180': '阿富汗', '1185': '斐济', '1186': '蒙古', '1195': '印度尼西亚', '1200': '中国香港', '1233': '卡塔尔', '1255': '冰岛', '1289': '阿尔巴尼亚', '1353': '乌兹别克斯坦', '1407': '圣马力诺', '1416': '科威特', '1417': '黑山', '1493': '塔吉克斯坦', '1501': '巴林', '1543': '智利', '1559': '南非', '1567': '埃及', '1590': '肯尼亚', '1592': '尼日利亚', '1598': '坦桑尼亚', '1611': '马达加斯加', '1613': '安哥拉', '1617': '科特迪瓦', '1620': '苏丹', '1681': '毛里求斯', '1693': '摩洛哥', '1695': '阿尔及利亚', '1698': '几内亚', '1730': '塞内加尔', '1864': '突尼斯', '1909': '乌拉圭', '1916': '格陵兰', '2026': '中国台湾', '2083': '缅甸', '2087': '文莱', '2094': '斯里兰卡', '2150': '巴拿马', '2175': '哥伦比亚', '2273': '摩纳哥', '2343': '安道尔', '2421': '土库曼斯坦', '2435': '老挝', '2488': '东帝汶', '2490': '汤加', '2588': '菲律宾', '2609': '委内瑞拉', '2612': '玻利维亚', '2613': '巴西', '2623': '哥斯达黎加', '2626': '墨西哥', '2639': '洪都拉斯', '2645': '萨尔瓦多', '2647': '巴拉圭', '2661': '秘鲁', '2728': '尼加拉瓜', '2734': '厄瓜多尔', '2768': '危地马拉', '2999': '阿鲁巴', '3058': '埃塞俄比亚', '3144': '波黑', '3216': '多米尼加', '3379': '韩国', '3701': '马来西亚', '3839': '加拿大', '4450': '澳大利亚', '4460': '中国港澳台' } # 运营商映射表 ISP_MAP = { '2': '中国电信', '26': '中国联通', '38': '教育网', '43': '长城宽带', '1046': '中国移动', '3947': '中国铁通', '0': '其它运营商', '-1': '境外运营商' } def get_province_name(code): """根据省份编号获取名称,优先查省份表,再查地区表""" code_str = str(code) if code_str in PROVINCE_MAP: return PROVINCE_MAP[code_str] if code_str in REGION_MAP: return REGION_MAP[code_str] return f"未知({code})" def get_region_name(code): """根据地区编号获取名称,优先查省份表,再查地区表""" code_str = str(code) if code_str in PROVINCE_MAP: return PROVINCE_MAP[code_str] if code_str in REGION_MAP: return REGION_MAP[code_str] return f"未知地区({code})" def format_bytes(bytes_size): """将字节数格式化为可读的单位""" for unit in ['B', 'KB', 'MB', 'GB', 'TB']: if bytes_size < 1024: return f"{bytes_size:.2f}{unit}" bytes_size /= 1024 return f"{bytes_size:.2f}PB" def parse_cdn_log_line(line): """ 解析CDN日志行(17字段格式,比腾讯云标准多1个字段) 字段顺序: 1. 请求时间 2. 客户端IP 3. 域名 4. 请求路径 5. 访问字节数 6. 省份编号 ← 额外字段 7. 地区编号 8. 运营商ID 9. HTTP状态码 10. Referer 11. 响应时间 12. User-Agent 13. Range参数 14. HTTP Method 15. 协议标识 16. 缓存HIT/MISS 17. 客户端端口 """ parts = line.split() if len(parts) < 11: return None try: client_ip = parts[1] # 客户端IP domain = parts[2] # 域名 request_path = parts[3] # 请求路径 bytes_size = int(parts[4]) # 字节数 province_code = parts[5] # 省份编号(额外字段) region_code = parts[6] # 地区编号 return { 'client_ip': client_ip, 'domain': domain, 'request_path': request_path, 'bytes_size': bytes_size, 'province_code': province_code, 'region_code': region_code } except (IndexError, ValueError): return None def analyze_tar_gz_file(file_path, domain_path_traffic, client_ip_traffic, province_traffic, region_traffic): """分析单个tar.gz文件""" total_bytes = 0 parsed_lines = 0 try: with tarfile.open(file_path, 'r:gz') as tar: for member in tar.getmembers(): if member.isfile() and member.name.endswith('.log'): log_file = tar.extractfile(member) if log_file: content = log_file.read().decode('utf-8', errors='ignore') lines = content.strip().split('\n') for line in lines: if not line.strip(): continue parsed_data = parse_cdn_log_line(line) if parsed_data: bytes_size = parsed_data['bytes_size'] # 统计域名+请求路径流量 domain_path = f"{parsed_data['domain']}{parsed_data['request_path']}" domain_path_traffic[domain_path] += bytes_size # 统计客户端IP流量 client_ip_traffic[parsed_data['client_ip']] += bytes_size # 统计省份编号流量 province_traffic[parsed_data['province_code']] += bytes_size # 统计地区编号流量 region_traffic[parsed_data['region_code']] += bytes_size total_bytes += bytes_size parsed_lines += 1 except Exception as e: print(f"处理文件 {file_path} 时出错: {e}") return total_bytes, parsed_lines def analyze_all_logs(log_dir='20251110'): """分析所有CDN日志文件""" print(f"开始分析 {log_dir} 目录下的CDN日志...") domain_path_traffic = defaultdict(int) client_ip_traffic = defaultdict(int) province_traffic = defaultdict(int) region_traffic = defaultdict(int) total_bytes = 0 total_parsed = 0 # 遍历所有小时目录 for hour_dir in sorted(os.listdir(log_dir)): hour_path = os.path.join(log_dir, hour_dir) if not os.path.isdir(hour_path): continue print(f"处理 {hour_dir} 小时的日志...") for filename in os.listdir(hour_path): if filename.endswith('.tar.gz'): file_path = os.path.join(hour_path, filename) bytes_count, parsed = analyze_tar_gz_file( file_path, domain_path_traffic, client_ip_traffic, province_traffic, region_traffic ) total_bytes += bytes_count total_parsed += parsed print(f"\n处理完成: 总记录数 {total_parsed:,}, 总流量 {format_bytes(total_bytes)}") return domain_path_traffic, client_ip_traffic, province_traffic, region_traffic, total_bytes def get_top10(traffic_dict): """获取流量TOP10""" sorted_items = sorted(traffic_dict.items(), key=lambda x: x[1], reverse=True) return sorted_items[:10] def write_top10_results(domain_path_traffic, client_ip_traffic, province_traffic, region_traffic, total_bytes, log_date): """写入TOP10结果到文件""" # 创建输出目录 output_dir = f"output_{log_date}" os.makedirs(output_dir, exist_ok=True) def calc_percentage(bytes_size): return f"{(bytes_size / total_bytes * 100):.2f}%" if total_bytes > 0 else "0.00%" # TOP10 域名+请求路径 with open(os.path.join(output_dir, "top10_domain_path.txt"), 'w', encoding='utf-8') as f: f.write("域名+请求路径\tCDN流量\t占比\n") for domain_path, bytes_size in get_top10(domain_path_traffic): f.write(f"{domain_path}\t{format_bytes(bytes_size)}\t{calc_percentage(bytes_size)}\n") # TOP10 客户端IP with open(os.path.join(output_dir, "top10_client_ip.txt"), 'w', encoding='utf-8') as f: f.write("客户端IP\tCDN流量\t占比\n") for client_ip, bytes_size in get_top10(client_ip_traffic): f.write(f"{client_ip}\t{format_bytes(bytes_size)}\t{calc_percentage(bytes_size)}\n") # TOP10 省份 with open(os.path.join(output_dir, "top10_province.txt"), 'w', encoding='utf-8') as f: f.write("省份\t编号\tCDN流量\t占比\n") for province_code, bytes_size in get_top10(province_traffic): province_name = get_province_name(province_code) f.write(f"{province_name}\t{province_code}\t{format_bytes(bytes_size)}\t{calc_percentage(bytes_size)}\n") # TOP10 地区 with open(os.path.join(output_dir, "top10_region.txt"), 'w', encoding='utf-8') as f: f.write("地区\t编号\tCDN流量\t占比\n") for region_code, bytes_size in get_top10(region_traffic): region_name = get_region_name(region_code) f.write(f"{region_name}\t{region_code}\t{format_bytes(bytes_size)}\t{calc_percentage(bytes_size)}\n") print(f"\n结果文件已生成到目录: {output_dir}/") print(f"- {output_dir}/top10_domain_path.txt") print(f"- {output_dir}/top10_client_ip.txt") print(f"- {output_dir}/top10_province.txt") print(f"- {output_dir}/top10_region.txt") def print_preview(domain_path_traffic, client_ip_traffic, province_traffic, region_traffic, total_bytes): """打印TOP10预览""" def calc_percentage(bytes_size): return f"{(bytes_size / total_bytes * 100):.2f}%" if total_bytes > 0 else "0.00%" print("\n" + "="*80) print("TOP10 域名+请求路径:") print("="*80) for i, (domain_path, bytes_size) in enumerate(get_top10(domain_path_traffic), 1): display = domain_path[:60] + "..." if len(domain_path) > 60 else domain_path print(f"{i:2d}. {display:<63} {format_bytes(bytes_size):>10} ({calc_percentage(bytes_size)})") print("\n" + "="*60) print("TOP10 客户端IP:") print("="*60) for i, (client_ip, bytes_size) in enumerate(get_top10(client_ip_traffic), 1): print(f"{i:2d}. {client_ip:<15} {format_bytes(bytes_size):>10} ({calc_percentage(bytes_size)})") print("\n" + "="*60) print("TOP10 省份:") print("="*60) for i, (province_code, bytes_size) in enumerate(get_top10(province_traffic), 1): province_name = get_province_name(province_code) print(f"{i:2d}. {province_name:<10} ({province_code:<6}) {format_bytes(bytes_size):>10} ({calc_percentage(bytes_size)})") print("\n" + "="*60) print("TOP10 地区:") print("="*60) for i, (region_code, bytes_size) in enumerate(get_top10(region_traffic), 1): region_name = get_region_name(region_code) print(f"{i:2d}. {region_name:<12} ({region_code:<6}) {format_bytes(bytes_size):>10} ({calc_percentage(bytes_size)})") def select_log_directory(): """让用户选择要分析的日志目录""" # 查找当前目录下所有可能的日志目录(以数字开头的目录) log_dirs = [] for name in sorted(os.listdir('.')): if os.path.isdir(name) and name.isdigit() and len(name) == 8: log_dirs.append(name) if not log_dirs: print("错误: 当前目录下没有找到日志目录(格式如20251110)") return None if len(log_dirs) == 1: print(f"找到日志目录: {log_dirs[0]}") return log_dirs[0] print("请选择要分析的日志目录:") for i, dir_name in enumerate(log_dirs, 1): print(f" {i}. {dir_name}") while True: try: choice = input(f"请输入序号 (1-{len(log_dirs)}): ").strip() idx = int(choice) - 1 if 0 <= idx < len(log_dirs): return log_dirs[idx] print(f"请输入 1-{len(log_dirs)} 之间的数字") except ValueError: print("请输入有效的数字") except KeyboardInterrupt: print("\n已取消") return None def main(): print("CDN日志流量分析工具") print("=" * 50) # 支持命令行参数指定日志目录 if len(sys.argv) > 1: log_dir = sys.argv[1] if not os.path.isdir(log_dir): print(f"错误: 目录 {log_dir} 不存在") return print(f"使用指定目录: {log_dir}") else: log_dir = select_log_directory() if not log_dir: return start_time = time.time() domain_path_traffic, client_ip_traffic, province_traffic, region_traffic, total_bytes = analyze_all_logs(log_dir) if total_bytes == 0: print("警告: 没有解析到任何有效的日志记录") return write_top10_results(domain_path_traffic, client_ip_traffic, province_traffic, region_traffic, total_bytes, log_dir) print_preview(domain_path_traffic, client_ip_traffic, province_traffic, region_traffic, total_bytes) print(f"\n总流量: {format_bytes(total_bytes)}") print(f"分析完成,耗时: {time.time() - start_time:.2f} 秒") if __name__ == '__main__': main() ```
执行脚本: ```bash python3 analyze.py ``` ### 3. 查看分析结果 脚本执行完成后,会在当前目录下生成 `output_YYYYMMDD` 目录,包含以下文件: ``` output_20251110/ ├── top10_domain_path.txt # 域名+路径流量 TOP10 ├── top10_client_ip.txt # 客户端 IP 流量 TOP10 ├── top10_province.txt # 省份流量 TOP10 └── top10_region.txt # 地区流量 TOP10 ``` ### 4. 结果文件示例 **top10_domain_path.txt** - 域名+请求路径流量统计: ``` 域名+请求路径 CDN流量 占比 www.example.com/index.html 123.45MB 35.20% www.example.com/assets/main.js 89.23MB 25.45% ``` **top10_client_ip.txt** - 客户端 IP 流量统计: ``` 客户端IP CDN流量 占比 192.168.1.100 234.56MB 15.30% 10.0.0.50 198.72MB 12.95% ``` **top10_province.txt** - 省份流量统计: ``` 省份 编号 CDN流量 占比 广东 4 456.78MB 28.50% 北京 22 389.45MB 24.30% ``` **top10_region.txt** - 地区流量统计: ``` 地区 编号 CDN流量 占比 亚太一区 2000000001 567.89MB 32.10% 北美 2000000005 423.56MB 23.95% ``` --- # 云存储/数据万象/能力概览 > 当前文档链接: https://docs.cloudbase.net/storage/ci-cos CloudBase 云存储基于腾讯云数据万象(Cloud Infinite,CI)服务,为您提供丰富的数据处理和内容安全能力,包括图片处理、文档转换、内容审核、智能检索等功能,帮助您构建安全、高效、智能的应用。 ## 如何使用 在 [云开发平台-云存储](https://tcb.cloud.tencent.com/dev#/storage) 页面,点击「**高级能力**」,即可查看数据万象的各项能力介绍,并根据指引快速开始使用。 ## 计费说明 内容审核、数据处理和智能检索由 **COS 数据万象(CI)服务**提供,按实际使用量计费。 :::info 💰 计费方式说明 **当前计费方式:** 由数据万象服务独立计费,费用单独结算,不包含在 CloudBase 云存储套餐中。 **未来计划:** 后续将支持 CloudBase 统一计费,届时可在 CloudBase 控制台统一管理和查看所有费用。 ::: **主要计费项:** - **内容审核**:按审核次数计费,不同类型价格不同 - **图片处理**:基础处理免费,高级处理按调用次数计费 - **文档处理**:按转换页数和预览次数计费 详细价格请参考:[数据万象计费说明](https://cloud.tencent.com/document/product/460/6970) --- ## 核心能力 ### 📋 内容审核 基于 AI 识别技术,自动检测文件中的违规内容,保障业务安全。 | 审核类型 | 说明 | | --- | --- | | **图片审核** | 检测图片中的涉黄、涉政、涉暴、违法、广告等违规内容 | | **视频审核** | 检测视频画面和音频中的违规内容 | | **音频审核** | 检测音频中的涉黄、涉政、辱骂、违法、广告等内容 | | **文本审核** | 检测文本中的敏感词汇和违规内容 | | **文档审核** | 检测 PDF、Word、Excel、PPT 等文档中的违规内容 | **应用场景:** UGC 内容审核、社交平台、电商平台、直播平台 👉 **[查看内容审核详细配置 →](./ci-cos-moderation)** --- ### 🖼️ 数据处理 提供丰富的文件处理能力,实时处理无需下载,优化用户体验和节省带宽成本。 #### 图片处理 | 功能类型 | 主要能力 | | --- | --- | | **基础处理** | 缩放、裁剪、旋转、格式转换、质量调整 | | **极智压缩** | TPG、HEIF、AVIF 等高压缩率格式 | | **高级压缩** | 智能压缩、有损/无损压缩 | | **图片水印** | 文字水印、图片水印、盲水印 | | **AI 处理** | 智能裁剪、去除背景、人像美颜 | #### 文档处理 | 功能 | 说明 | 支持格式 | | --- | --- | --- | | **文档转 HTML** | 将文档转为 HTML 在线预览 | Word、Excel、PPT、PDF、TXT 等 | | **文档转图片** | 将文档每页转为图片格式 | Word、Excel、PPT、PDF 等 | **应用场景:** 缩略图生成、自适应终端、图片优化、在线文档查看、知识库系统 👉 **[查看数据处理详细操作 →](./ci-cos-processing)** --- ### 🔍 智能检索 智能检索(MetaInsight)是腾讯云数据万象(CI)提供的 AI 能力,为云存储中的图片、视频、文档等多媒体文件提供智能内容分析和检索功能。整合**标量检索**(元数据精确匹配)和**向量检索**(AI 语义匹配),帮助您快速定位和管理海量多媒体资源。 | 检索类型 | 说明 | 应用场景 | | --- | --- | --- | | **基础信息检索** | 基于文件元数据检索,如文件名、大小、类型、创建时间等 | 文件管理、数据统计 | | **图片检索** | 支持以图搜图、以文搜图,基于图片内容的语义检索 | 网盘相册、商品检索推荐 | | **文档检索** | 文档内容的语义检索,支持关键词搜索文档内容 | 知识库、文档管理系统 | | **人脸检索** | 识别图片中的人脸特征,按人脸搜索包含同一人物的照片 | 安防监控、考勤系统、智能相册 | | **多模态混合检索** | 结合文字、图片、元数据等多种方式进行综合检索 | 复杂业务场景 | **应用场景:** 图片资源管理、智能相册、电商平台商品搜索、媒体资产管理、内容创作素材库 👉 **[查看智能检索使用教程 →](./ci-cos-search)** ## 相关资源 - [数据万象产品文档](https://cloud.tencent.com/document/product/460) - [数据万象计费说明](https://cloud.tencent.com/document/product/460/6970) - [COS 控制台](https://console.cloud.tencent.com/cos) --- # 云存储/数据万象/内容审核 > 当前文档链接: https://docs.cloudbase.net/storage/ci-cos-moderation 内容审核基于 AI 识别技术,帮助您自动检测文件中的违规内容,保障业务安全。 ## 支持的审核类型 | 审核类型 | 说明 | 识别内容 | | --- | --- | --- | | **图片审核** | 检测图片中的违规内容 | 涉黄、涉政、涉暴、违法、广告等 | | **视频审核** | 检测视频中的违规内容 | 涉黄、涉政、涉暴、违法、广告等 | | **音频审核** | 检测音频中的违规内容 | 涉黄、涉政、辱骂、违法、广告等 | | **文本审核** | 检测文本中的敏感词汇 | 涉黄、涉政、辱骂、违法、广告等 | | **文档审核** | 检测文档中的违规内容 | 支持 PDF、Word、Excel、PPT 等格式 | ## 主要功能 - **自动识别**:基于深度学习技术,自动识别违规内容 - **自定义策略**:根据业务需求配置审核策略和敏感词库 - **实时审核**:支持上传时自动审核,第一时间拦截违规内容 - **回调通知**:审核结果实时回调,快速响应 - **自动处理**:支持对违规内容自动冻结或删除 ## 应用场景 - **UGC 内容审核**:用户上传的图片、视频、文本内容审核 - **社交平台**:评论、动态、聊天内容的安全审核 - **电商平台**:商品图片、描述信息的合规审核 - **直播平台**:直播画面、弹幕内容的实时审核 --- ## 配置步骤 ### 控制台访问路径 [**云开发平台 - 云存储**](https://tcb.cloud.tencent.com/dev#/storage/ci) → 点击「前往 COS 存储桶」 → 左侧菜单「内容审核」 → 「自动审核配置」 → 选择审核类型(图片/视频/音频/文本/文档) ### 图片审核配置 **1. 进入图片审核** - 在左侧菜单选择「**内容审核**」→「**自动审核配置**」→「**图片审核**」 **2. 创建审核任务** - 点击「**创建任务**」按钮 - 选择审核类型:增量审核(自动审核新上传图片)或存量审核(审核已有图片) - 配置审核策略:选择需要审核的场景(涉黄、涉政、涉暴、违法、广告等) **3. 设置审核结果处理** - 配置回调地址:接收审核结果通知 - 设置自动冻结规则:对违规内容自动冻结 - 选择审核结果存储位置 **4. 启用任务** - 确认配置信息无误后,点击「**确定**」 - 任务创建后自动生效 **API 调用方式:** - [图片审核 API 文档](https://cloud.tencent.com/document/product/436/45434) - [图片批量审核 API](https://cloud.tencent.com/document/product/436/63593) ### 视频审核配置 **1. 进入视频审核** - 在左侧菜单选择「**内容审核**」→「**自动审核配置**」→「**视频审核**」 **2. 创建审核任务** - 点击「**创建任务**」 - 设置审核范围:指定文件路径或选择整个存储桶 - 配置审核策略:涉黄、涉政、涉暴、违法、广告等 **3. 配置审核参数** - **截帧方式**:设置视频截帧间隔(如每 5 秒截取一帧) - **审核维度**:选择画面审核、音频审核或两者都审核 - **回调设置**:配置审核结果回调 URL **4. 启用并监控** - 保存配置后任务自动执行 - 在任务列表查看审核进度和结果 **API 调用方式:** - [视频审核 API 文档](https://cloud.tencent.com/document/product/436/47316) - [查询视频审核结果 API](https://cloud.tencent.com/document/product/436/47317) ### 音频审核配置 **1. 进入音频审核** - 在左侧菜单选择「**内容审核**」→「**自动审核配置**」→「**音频审核**」 **2. 创建审核任务** - 点击「**创建任务**」 - 选择待审核的音频文件或文件夹 **3. 配置审核策略** - 选择审核场景:涉黄、涉政、辱骂、违法、广告等 - 设置语音识别语言(中文、英文等) - 配置敏感词库 **4. 设置回调和处理规则** - 配置审核结果回调地址 - 设置违规内容自动处理规则 - 保存并启用任务 **API 调用方式:** - [音频审核 API 文档](https://cloud.tencent.com/document/product/436/54063) - [查询音频审核结果 API](https://cloud.tencent.com/document/product/436/54064) ### 文本审核配置 **1. 进入文本审核** - 在左侧菜单选择「**内容审核**」→「**自动审核配置**」→「**文本审核**」 **2. 配置审核策略** - 点击「**审核配置**」 - 选择审核类型:涉黄、涉政、辱骂、违法、广告等 **3. 自定义敏感词库** - 添加业务相关的敏感词 - 设置词库优先级 - 配置命中规则 **4. API 调用方式** - 文本审核主要通过 API 调用 - 在应用中集成文本审核 API **API 调用方式:** - [文本审核 API 文档](https://cloud.tencent.com/document/product/436/56289) ### 文档审核配置 **1. 进入文档审核** - 在左侧菜单选择「**内容审核**」→「**自动审核配置**」→「**文档审核**」 **2. 创建审核任务** - 点击「**创建任务**」 - 选择待审核的文档文件 **3. 配置审核参数** - 支持格式:PDF、Word、Excel、PPT 等 - 选择审核场景和策略 - 设置审核结果通知方式 **4. 查看审核结果** - 任务完成后查看详细审核报告 - 支持按页查看违规内容 - 导出审核结果报告 **API 调用方式:** - [文档审核 API 文档](https://cloud.tencent.com/document/product/436/59381) - [查询文档审核结果 API](https://cloud.tencent.com/document/product/436/59382) --- ## 最佳实践 ### 1. 增量审核 + 存量审核结合 - **增量审核**:配置自动审核,新上传文件实时检测 - **存量审核**:定期对已有文件进行全量扫描 ### 2. 自定义敏感词库 根据业务特点添加行业相关敏感词: - 电商平台:假货、仿品等相关词汇 - 社交平台:人身攻击、歧视性词汇 - 金融平台:非法集资、诈骗相关词汇 ### 3. 人工复审机制 对于「疑似违规」的内容: 1. 通过回调接收审核结果 2. 将疑似违规内容放入人工复审队列 3. 人工审核后做最终决策 ### 4. 分级处理策略 根据违规程度采取不同措施: - **轻度违规**:警告用户,要求修改 - **中度违规**:直接删除内容,记录违规次数 - **重度违规**:冻结账号,上报相关部门 --- ## 常见问题 ### Q: 审核需要多长时间? A: - **图片审核**:通常在 1-3 秒内完成 - **视频审核**:根据视频时长,通常为视频时长的 10%-20% - **音频审核**:根据音频时长,通常为音频时长的 10%-20% - **文档审核**:根据文档页数,通常 1 页 1-2 秒 ### Q: 审核准确率如何? A: 数据万象基于腾讯多年积累的海量数据和深度学习技术: - **图片审核准确率**:≥ 99% - **视频审核准确率**:≥ 98% - **文本审核准确率**:≥ 99% ### Q: 如何降低审核成本? A: 建议采取以下策略: 1. 只对用户上传内容进行审核,系统内容无需审核 2. 根据用户信用等级调整审核策略(高信用用户可降低审核频率) 3. 使用自定义策略,只审核必要的场景 ### Q: 是否支持私有化部署? A: 数据万象内容审核是云服务,暂不支持私有化部署。 --- ## 相关资源 - [内容审核 API 文档](https://cloud.tencent.com/document/product/460/37318) - [图片审核详细说明](https://cloud.tencent.com/document/product/460/37317) - [视频审核详细说明](https://cloud.tencent.com/document/product/460/46427) - [音频审核详细说明](https://cloud.tencent.com/document/product/460/53395) - [文本审核详细说明](https://cloud.tencent.com/document/product/460/56285) - [返回总览页](./ci-cos.md) --- # 云存储/数据万象/数据处理 > 当前文档链接: https://docs.cloudbase.net/storage/ci-cos-processing 数据处理 提供丰富的文件处理能力,无需下载文件即可在云端完成处理。 ## 图片处理 对图片进行裁剪、缩放、水印、格式转换等操作,优化用户体验和节省带宽成本。 开通后: - ✅ **图片基础处理**:默认开启,可直接使用 - ⚙️ **图片极智压缩**:需要手动开启 - ⚙️ **图片高级压缩**:需要手动开启 | 功能 | 说明 | | --- | --- | | **基础图片处理** | 支持裁剪、缩放、旋转、格式转换、质量变换、高斯模糊、锐化等操作 | | **图片极智压缩** | 支持图片访问时无需参数自动压缩或通过处理参数 imageSlim 主动压缩,支持 JPG、静态 PNG、GIF 三种格式,压缩后不会改变图片格式 | | **图片高级压缩** | 通过处理参数 imageMogr2/format 将图片转换为 AVIF、HEIF、TPG、ASTC 压缩格式,也支持对 SVG 图片进行压缩 | ## 文档处理 将文档转换为 HTML 或图片,实现在线预览功能。 :::info 📌 功能支持说明 目前 CloudBase 云存储支持以下文档处理功能: - ✅ **文档转 HTML**:支持 Word、Excel、PPT、PDF 等格式转换为 HTML 进行在线预览 - ✅ **文档转图片**:支持将文档转换为图片格式(PNG、JPG) 暂不支持:视频和音频等多媒体处理功能。 ::: | 功能 | 说明 | 支持格式 | | --- | --- | --- | | **文档转 HTML** | 将文档转为 HTML 在线预览 | Word、Excel、PPT、PDF 等 | | **文档转图片** | 将文档转为图片格式 | Word、Excel、PPT、PDF 等 | **应用场景:** - **在线文档查看**:无需下载即可在浏览器中预览文档 - **文档内容展示**:将文档内容转为图片或 HTML 展示在应用中 - **知识库系统**:文档内容搜索和展示 --- ## 操作步骤 ### 图片基础处理 :::info 📌 功能说明 图片基础处理支持通过 URL 参数实时处理图片,无需预先配置。默认开启。 ::: 可在 [**云开发平台 - 云存储**](https://tcb.cloud.tencent.com/dev#/storage/ci) 页面点击「**前往 COS 存储桶**」,在左侧菜单「**数据处理**」→「**图片处理**」中查看「**图片基础处理**」模块。 **1. 使用 URL 参数处理** 在图片访问 URL 后添加处理参数即可: ``` https://your-bucket.cos.ap-region.myqcloud.com/image.jpg?imageMogr2/thumbnail/!50p ``` :::tip 💡 获取图片 URL 如需获取云存储中图片的临时访问链接,请参考 [获取临时链接](./sdk#获取临时链接)。 ::: **2. 常用处理参数** | 操作 | 参数 | 取值 | 示例 | | --- | --- | --- | --- | | **缩放**·按百分比等比缩放 | `imageMogr2/thumbnail/!p` | 1-1000,如 50p | `imageMogr2/thumbnail/!50p` | | **缩放**·指定宽度等比缩放 | `imageMogr2/thumbnail/x` | 1-10000,如 200x | `imageMogr2/thumbnail/200x` | | **缩放**·指定高度等比缩放 | `imageMogr2/thumbnail/x` | 1-10000,如 200 | `imageMogr2/thumbnail/x200` | | **缩放**·限定宽高最大值等比缩放 | `imageMogr2/thumbnail/x` | 1-10000 | `imageMogr2/thumbnail/200x200` | | **缩放**·强制缩放(可能变形) | `imageMogr2/thumbnail/x!` | 1-10000 | `imageMogr2/thumbnail/200x200!` | | **裁剪**·自定义裁剪 | `imageMogr2/cut/xxx` | 宽 × 高 × 水平偏移 × 垂直偏移 | `imageMogr2/cut/600x600x100x10` | | **裁剪**·缩放裁剪(指定锚点) | `imageMogr2/crop/x/gravity/` | pos:center / north / south 等九宫格 | `imageMogr2/crop/300x400/gravity/center` | | **裁剪**·圆角裁剪 | `imageMogr2/rradius/` | 1 ~ 原图最小边的一半 | `imageMogr2/rradius/100` | | **旋转**·顺时针旋转 | `imageMogr2/rotate/` | 0-360,如 90 | `imageMogr2/rotate/90` | | **格式**·格式转换 | `imageMogr2/format/` | jpg / png / webp / gif / heif / avif / tpg / astc / src | `imageMogr2/format/webp` | | **格式**·静图与动图分别转码 | `imageMogr2/format//anima-format/` | 动图支持 gif / png / webp / avif | `imageMogr2/format/jpg/anima-format/gif` | | **质量**·相对质量(按原图百分比) | `imageMogr2/rquality/` | 0-100 | `imageMogr2/rquality/60` | | **质量**·最低质量(兜底提升) | `imageMogr2/lquality/` | 0-100 | `imageMogr2/lquality/80` | | **效果**·高斯模糊 | `imageMogr2/blur/x` | 下载时 1-500,上传时 1-50 | `imageMogr2/blur/8x5` | | **效果**·锐化 | `imageMogr2/sharpen/` | 10-300,推荐 70 | `imageMogr2/sharpen/70` | | **效果**·亮度调节 | `imageMogr2/bright/` | -100 ~ 100 | `imageMogr2/bright/70` | | **效果**·对比度调节 | `imageMogr2/contrast/` | -100 ~ 100 | `imageMogr2/contrast/-50` | **3. 创建图片样式(可选)** 如果需要复用相同的处理规则,可以创建图片样式: 1. 在 [**云开发平台 - 云存储**](https://tcb.cloud.tencent.com/dev#/storage/ci) 页面点击「**前往 COS 存储桶**」 2. 在左侧菜单「**数据处理**」→「**图片处理**」中点击「**样式管理**」→「**添加样式**」 3. 配置处理参数并命名样式(如 `thumbnail`) 4. 使用样式:`image.jpg?imageView2/style/thumbnail` **4. 高级处理示例** ```bash # 缩放 + 格式转换 + 质量调整(组合使用) https://example.com/image.jpg?imageMogr2/thumbnail/!50p/format/webp/rquality/80 # 裁剪 + 旋转 https://example.com/image.jpg?imageMogr2/crop/300x300/rotate/90 # 添加文字水印 https://example.com/image.jpg?watermark/2/text/Q2xvdWRCYXNl/font/SGVsdmV0aWNh/fontsize/30/fill/IzAwMDAwMA==/dissolve/80/gravity/southeast/dx/10/dy/10 ``` ### 图片极智压缩 图片极智压缩能够在保证视觉效果的前提下,大幅降低图片体积。支持 JPG、静态 PNG、GIF 三种格式,压缩后不会改变图片格式。 **1. 开通极智压缩** 1. 在 [**云开发平台 - 云存储**](https://tcb.cloud.tencent.com/dev#/storage/ci) 页面点击「**前往 COS 存储桶**」 2. 在左侧菜单「**数据处理**」→「**图片处理**」中找到「**图片极智压缩**」模块 3. 点击「**开通服务**」并确认开通(按调用次数计费) **2. 使用极智压缩** 有两种方式可以按需求开启: **方式一:自动压缩(推荐)** 无需添加任何额外的参数,在正常访问 JPG、PNG、GIF 格式图片时将自动进行压缩: ``` # 直接访问图片,自动压缩 https://your-bucket.cos.ap-region.myqcloud.com/image.jpg ``` **方式二:API 压缩** 访问图片时,在图片链接后添加极智压缩参数 `imageSlim`,即可访问到压缩后的图片: ``` # 使用 imageSlim 参数主动压缩 https://your-bucket.cos.ap-region.myqcloud.com/image.jpg?imageSlim ``` :::info 📖 参考文档 更多详细参数说明,请参考 [图片极智压缩接口说明](https://cloud.tencent.com/document/product/460/94856)。 ::: **3. 压缩效果对比** 极智压缩可以在不改变图片格式的前提下,显著减小文件体积: | 格式 | 压缩前 | 压缩后 | 压缩率 | 视觉质量 | | --- | --- | --- | --- | --- | | JPG | 500 KB | 250 KB | 50% | 无明显差异 | | PNG | 800 KB | 400 KB | 50% | 无明显差异 | | GIF | 1 MB | 500 KB | 50% | 无明显差异 | ### 图片高级压缩 图片高级压缩通过 `imageMogr2/format` 参数将图片转换为 AVIF、HEIF、TPG、ASTC 等高压缩率格式,也支持对 SVG 图片进行压缩。 **1. 启用高级压缩功能** 1. 在 [**云开发平台 - 云存储**](https://tcb.cloud.tencent.com/dev#/storage/ci) 页面点击「**前往 COS 存储桶**」 2. 在左侧菜单「**数据处理**」→「**图片处理**」中找到「**图片高级压缩**」选项 3. 点击「**开通服务**」并确认开通(按调用次数计费) **2. 支持的压缩格式** | 格式 | 参数 | 压缩率 | 兼容性 | | --- | --- | --- | --- | | **HEIF** | `imageMogr2/format/heif` | 比 JPG 减小 80% 以上 | iOS 11+、Android P+ 原生支持 | | **AVIF** | `imageMogr2/format/avif` | 新一代格式,压缩率最高 | Chrome 85+、Firefox 93+ | | **TPG** | `imageMogr2/format/tpg` | 腾讯自研格式,压缩率极高 | 需客户端 SDK 支持 | | **ASTC** | `imageMogr2/format/astc` | 适用于纹理压缩 | OpenGL ES 3.0+ | **3. 使用方式** **方式一:下载时处理(不保存)** 在图片访问 URL 后添加处理参数: ```bash # 转换为 HEIF 格式 https://your-bucket.cos.ap-region.myqcloud.com/image.jpg?imageMogr2/format/heif # 转换为 AVIF 格式 https://your-bucket.cos.ap-region.myqcloud.com/image.jpg?imageMogr2/format/avif # 转换为 TPG 格式 https://your-bucket.cos.ap-region.myqcloud.com/image.jpg?imageMogr2/format/tpg ``` **方式二:上传时处理(保存结果)** 在图片上传时同时进行压缩处理并保存: ```javascript // 请求头中添加 Pic-Operations { "is_pic_info": 1, "rules": [{ "fileid": "compressed-image.heif", "rule": "imageMogr2/format/heif" }] } ``` **方式三:云上数据处理(处理已有图片)** 对已存储的图片进行处理并保存: ```bash POST /?image_process HTTP/1.1 # 请求体与上传时处理相同 ``` :::info 📖 参考文档 - [HEIF 压缩说明](https://cloud.tencent.com/document/product/436/113315) - [TPG 压缩说明](https://cloud.tencent.com/document/product/436/113316) - [AVIF 压缩说明](https://cloud.tencent.com/document/product/436/113317) ::: **4. 压缩效果对比** | 格式 | 文件大小 | 压缩率 | 适用场景 | | --- | --- | --- | --- | | 原图(PNG) | 1335.2 KB | - | 原始图片 | | HEIF | 52.87 KB | 96.0% | iOS/Android 移动端 | | AVIF | 约 50 KB | 96.3% | 现代浏览器 | | TPG | 约 55 KB | 95.9% | 腾讯生态应用 | ### 文档转 HTML 将文档转换为 HTML 格式,实现在线预览功能。 **1. 开通文档预览服务** 1. 在 [**云开发平台 - 云存储**](https://tcb.cloud.tencent.com/dev#/storage/ci) 页面点击「**前往 COS 存储桶**」 2. 在左侧菜单「**数据处理**」→「**文档处理**」中点击「**开通服务**」 3. 确认开通文档预览功能 **2. 使用文档转 HTML** 通过 URL 参数直接预览文档: ``` https://your-bucket.cos.ap-region.myqcloud.com/document.pdf?ci-process=doc-preview&dstType=html ``` **核心参数:** | 参数 | 必选 | 说明 | 取值 | | --- | --- | --- | --- | | `ci-process` | 是 | 指定数据万象处理能力 | 固定为 `doc-preview` | | `dstType` | 是 | 输出目标文件类型 | 固定为 `html`(小写) | | `srcType` | 否 | 输入源文件类型 | 不填则根据文件后缀自动识别(如 `pptx`、`pdf`、`docx`) | | `sign` | 否 | 对象下载签名 | 私有读文件必须传入,需 URLencode | | `copyable` | 否 | 是否允许复制内容 | `1`=可复制(默认),`0`=不可复制 | | `htmlParams` | 否 | 自定义配置参数(JSON) | 需进行 URL 安全的 Base64 编码 | | `htmlwaterword` | 否 | 水印文字内容 | 需进行 URL 安全的 Base64 编码 | | `htmlfillstyle` | 否 | 水印颜色和透明度 | RGBA 格式(如 `rgba(192,192,192,0.6)`),需 Base64 编码 | | `htmlfront` | 否 | 水印文字样式 | 如 `bold 20px Serif`,需 Base64 编码 | | `htmlrotate` | 否 | 水印旋转角度 | 0-360,默认 315 | | `htmlhorizontal` | 否 | 水印水平间距 | 单位 px,默认 50 | | `htmlvertical` | 否 | 水印垂直间距 | 单位 px,默认 100 | **使用示例:** ```bash # 基础预览 https://your-bucket.cos.ap-region.myqcloud.com/test.pptx?ci-process=doc-preview&dstType=html # 禁止复制 + 添加水印("test" 的 Base64 编码为 dGVzdAog) https://your-bucket.cos.ap-region.myqcloud.com/test.pptx?ci-process=doc-preview&dstType=html©able=0&htmlwaterword=dGVzdAog # 切换为英文界面(htmlParams 需 Base64 编码) https://your-bucket.cos.ap-region.myqcloud.com/test.pptx?ci-process=doc-preview&dstType=html&htmlParams=eyJjb21tb25PcHRpb25zIjp7Imxhbmd1YWdlIjoiZW4ifX0 ``` **htmlParams 配置示例:** ```json // 隐藏头部、切换语言为英文 { "mode": "normal", "commonOptions": { "isShowHeader": false, "language": "en" } } // 编码后作为 htmlParams 参数值 ``` :::info 📖 参考文档 详细使用说明请参考 [文档转 HTML 使用指引](https://cloud.tencent.com/document/product/460/52518)。 ::: **3. 支持的文档格式** - **演示文件**:pptx, ppt, pot, potx, pps, ppsx, dps, dpt, pptm, potm, ppsm - **文字文件**:doc, dot, wps, wpt, docx, dotx, docm, dotm - **表格文件**:xls, xlt, et, ett, xlsx, xltx, xlsb, xlsm, xltm, ets - **其他格式**:pdf, txt, log, xml, htm, html, 代码文件等 **4. 注意事项** - **文件大小限制**:输入文件大小需在 200MB 以内 - **页数限制**:输入文件页数限制在 5000 页以内 - **编码要求**:`htmlParams`、`htmlwaterword` 等字符串参数必须进行 URL 安全的 Base64 编码 - **权限要求**:私有读文件必须传入 `sign` 参数进行鉴权 ### 文档转图片 将文档每一页转换为图片格式。 **1. 使用 URL 参数转换** 通过 URL 参数实时转换文档为图片: ``` https://your-bucket.cos.ap-region.myqcloud.com/document.pdf?ci-process=doc-preview&page=1&dstType=png ``` **核心参数:** | 参数 | 说明 | 取值 | | --- | --- | --- | | `ci-process` | 必选,指定数据万象处理能力 | 固定为 `doc-preview` | | `page` | 可选,转换的文档页码 | 整数,从 1 开始,默认转换第 1 页 | | `dstType` | 可选,输出图片格式 | `png` 或 `jpg`,默认为 `jpg` | | `srcType` | 可选,源文件后缀类型 | 当文件无后缀时需指定(如 `pptx`、`pdf`、`docx`) | | `scale` | 可选,预览图片缩放比例 | 10-200,默认 100(即 100%) | | `quality` | 可选,生成图片的质量 | 1-100,默认 100 | | `imageDpi` | 可选,按指定 DPI 渲染图片 | 96-600,默认 96(注意:高 DPI 会增加处理时间) | **使用示例:** ```bash # 基础转换:转换第 1 页为 PNG https://your-bucket.cos.ap-region.myqcloud.com/document.pdf?ci-process=doc-preview&page=1&dstType=png # 指定质量和缩放:转换为 JPG,质量 85,缩放 50% https://your-bucket.cos.ap-region.myqcloud.com/document.pdf?ci-process=doc-preview&page=1&dstType=jpg&quality=85&scale=50 # 结合图片处理:转换后添加水印(需 URL 编码) https://your-bucket.cos.ap-region.myqcloud.com/test.pptx?ci-process=doc-preview&page=1&ImageParams=imageMogr2/thumbnail/!50p|watermark/2/text/dGVzdA/fontsize/30 ``` :::info 📖 参考文档 - [文档转图片使用指引](https://cloud.tencent.com/document/product/460/47074) - 支持通过 `ImageParams` 参数结合 [图片处理](https://cloud.tencent.com/document/product/460/18147) 功能(如缩放、水印、裁剪等) ::: **2. 支持的文档格式** - Microsoft Office:Word(.doc/.docx)、Excel(.xls/.xlsx)、PowerPoint(.ppt/.pptx) - PDF 文档(.pdf) **3. 注意事项** - **超时限制**:同步接口默认 10 秒转换超时,适用于百页以内的文档或预览少数页面 - **文件限制**:输入文件大小限制在 200MB 以内,页数限制在 5000 页以内 - **私有读存储桶**:URL 需要携带 COS 授权签名 --- ## 常见问题 ### Q: 图片处理是否会改变原图? A: 不会。图片处理是实时处理,不会修改原始文件。处理后的图片在访问时动态生成。 ### Q: 如何保存处理后的图片? A: 有两种方式: 1. **持久化处理**:在处理参数中添加 `|saveas` 参数,将处理结果保存为新文件 2. **下载后上传**:将处理后的图片下载到本地,再上传到存储桶 ### Q: 处理后的图片会缓存吗? A: 会。处理后的图片会在 CDN 节点缓存,后续访问相同 URL 时直接返回缓存结果,无需重复处理。 ### Q: 文档预览是否支持加密文档? A: 目前不支持加密文档的预览。需要先解密后再上传到存储桶进行预览。 ### Q: 如何批量处理图片? A: 可以使用以下方式: 1. **工作流**:在控制台创建工作流,自动处理新上传的文件 2. **批量处理任务**:在控制台创建批量处理任务,处理已有文件 3. **SDK**:通过代码遍历文件列表,调用处理 API --- ## 相关资源 - [图片处理 API 文档](https://cloud.tencent.com/document/product/460/36540) - [文档预览 API 文档](https://cloud.tencent.com/document/product/460/47074) - [文档转 HTML 详细说明](https://cloud.tencent.com/document/product/460/52518) - [文档转图片详细说明](https://cloud.tencent.com/document/product/460/47074) - [图片样式配置指南](https://cloud.tencent.com/document/product/460/46823) - [返回总览页](./ci-cos.md) --- # 云存储/数据万象/智能检索 > 当前文档链接: https://docs.cloudbase.net/storage/ci-cos-search 智能检索(MetaInsight)是基于腾讯云数据万象的 AI 能力,为云存储中的图片、视频、文档等多媒体文件提供智能内容分析和检索功能,帮助您快速定位和管理海量文件资源。 ## 功能介绍 智能检索整合了多种检索能力,支持**标量检索**(基于元数据的传统检索)和**向量检索**(基于语义的 AI 检索),提供多模态混合检索能力。 ### 核心能力 | 检索类型 | 说明 | 应用场景 | | --- | --- | --- | | **基础信息检索** | 基于文件元数据检索,如文件名、大小、类型、创建时间等 | 文件管理、数据统计 | | **图片检索** | 支持以图搜图、以文搜图,基于图片内容的语义检索 | 网盘相册、商品检索推荐 | | **人脸检索** | 识别图片中的人脸特征,按人脸搜索包含同一人物的照片 | 安防监控、考勤系统、智能相册 | ### 检索方式对比 | 特性 | 标量检索 | 向量检索 | | --- | --- | --- | | **原理** | 基于文件属性(元数据)精确匹配 | 基于 AI 语义理解相似度匹配 | | **典型用途** | 按文件名、类型、时间查询 | 图片内容搜索、文档语义检索 | | **优势** | 查询速度快、结果精确 | 智能理解、支持模糊匹配 | | **适用场景** | 已知文件属性的查询 | 内容相关性查询 | ### 应用场景 - **图片资源管理**:快速定位需要的图片素材 - **相册应用**:按人物、地点、事物分类照片 - **电商平台**:商品图片智能分类和搜索 - **媒体资产管理**:视频素材的智能标签和检索 - **内容创作**:快速找到符合需求的素材 --- ## 使用前准备 使用智能检索功能前,需要先开通数据万象能力并创建**数据集**。 ### 第 1 步:开通智能检索 1. 在 [云开发平台-云存储](https://tcb.cloud.tencent.com/dev#/storage) 页面,点击「**高级能力**」→「**智能检索**」,前往COS控制台进行配置 2. 按照页面提示开通数据万象智能检索能力,开通后默认**会创建基础信息检索模版** 智能检索 ### 第 2 步:管理数据集 ::::caution ⚠️ 注意事项 目前仅支持使用开通数据万象时创建的基础信息模板,云存储桶暂不支持单独创建新的数据集。 :::: 1. 在 [COS 控制台](https://console.cloud.tencent.com/cos/metaInsight/retrievalTask)左侧导航栏选择「**智能检索**」→「**数据集管理**」 2. 点击「**创建数据集**」按钮 3. 配置数据集信息: - **数据集名称**:自定义数据集名称 - **数据集描述**:选填,描述数据集用途 - **关联存储桶**:选择您的 CloudBase 云存储对应的 COS 存储桶 - **文件路径**:指定需要检索的文件路径(可选全部或指定前缀) 4. 点击「**确定**」完成创建 ### 第 3 步:等待元数据索引建立完成 创建数据集后,需要为文件建立索引: 1. 在数据集列表中,点击数据集名称进入详情页 2. 系统将自动分析文件并建立索引 智能检索 **索引时间**:通常在任务提交后 1-10 分钟内完成,具体时间取决于文件数量和大小。 --- ## 使用智能检索 智能检索提供两种使用方式:控制台操作和 API 调用。 ### 控制台检索 通过 COS 控制台可视化界面进行检索,支持基础信息、多模态混合检索两种检索类型。 #### 基础信息检索 适用于基于文件属性的查询,如文件名、contentType、文件大小等。 **操作步骤:** 1. 登录 [COS 控制台](https://console.cloud.tencent.com/cos/metaInsight/retrievalTask) 2. 在左侧导航栏选择「**智能检索**」→「**数据检索**」 3. 选择检索场景为「**基础信息检索**」 4. 配置查询条件: - 选择数据集 - 设置查询条件(支持多个条件组合): - **文件名称**:支持精确匹配或模糊匹配 - **内容类型**(contentType):如 image/jpeg、application/pdf - **文件大小**:支持范围查询 - **创建时间**:支持时间范围查询 - **自定义标签**:基于文件的自定义元数据 5. **高级配置**(可选): - 设置排序方式(按时间、大小等) - 配置数据聚合规则 - 设置返回结果数量 6. 点击「**查询**」获取结果 **查询条件示例:** | 查询需求 | 查询条件设置 | | --- | --- | | 查找所有 JPG 图片 | contentType = "image/jpeg" | | 查找大于 1MB 的文件 | 文件大小 > 1048576 | | 查找最近 7 天的文件 | 创建时间 >= 当前时间-7天 | | 查找文件名包含"产品"的图片 | 文件名称 contains "产品" AND contentType starts with "image/" | **支持的操作符**:`=`、`!=`、`>`、`>=`、`<`、`<=`、`contains`、`starts with`、`ends with` 等。 详细的字段和操作符支持列表,请参考:[COS 字段和操作符文档](https://cloud.tencent.com/document/product/460/106154) --- #### 多模态检索 ::::caution ⚠️ 功能状态 多模态检索功能暂未开放。 :::: 多模态检索支持基于图像内容和人脸识别的 AI 检索场景,包括以图搜图、以文搜图、人脸检索等。 **操作步骤:** 1. 登录 [COS 控制台](https://console.cloud.tencent.com/cos/metaInsight/retrievalTask) 2. 在左侧导航栏选择「**智能检索**」→「**数据检索**」 3. 选择检索场景为「**多模态混合检索**」 4. 输入检索内容: - **文字检索**:输入描述性文字,如"蓝天白云的风景照"、"红色的连衣裙" - **图片检索**:上传参考图片或选择存储桶中的图片(支持普通图像检索和人脸检索) 5. **高级设置**: - **返回数量**:设置返回结果的最大数量(默认 10,最大 100) - **相似度阈值**:设置最低相似度分数(0-100,图像检索推荐 60-80,人脸检索推荐 70-85) - **检索类型**:选择检索图片或全部 6. **元数据过滤**(可选): - 在检索结果基础上,进一步通过元数据条件过滤 - 例如:只返回 2024 年创建的图片 7. 点击「**查询**」获取结果 **多模态检索示例:** **以图搜图:** ``` 1. 上传一张"海滩风景"的图片 2. 设置相似度阈值为 70 3. 系统返回所有与该图片内容相似的照片 ``` **以文搜图:** ``` 1. 输入文字描述:"穿着白色婚纱的新娘" 2. 设置返回数量为 20 3. 系统返回语义匹配的图片 ``` **人脸检索:** ``` 1. 上传一张清晰的人脸照片 2. 设置相似度阈值为 75 3. 系统返回包含该人物的所有照片 4. 可按时间、相似度排序 ``` ::::tip 💡 检索优化建议 - **图像检索**:使用清晰、主体明确的图片,相似度阈值推荐 60-80 - **人脸检索**:使用清晰、正面的人脸照片作为参考图,相似度阈值推荐 70-85 - **索引配置**:人脸检索需要提前在数据集中配置人脸索引 :::: --- ### API 调用 智能检索支持通过 API 调用,可集成到您的应用中。 ::::tip 📚 使用方式参考 完整的 API 调用方式和参数说明,请参考:[数据检索 API 文档](https://cloud.tencent.com/document/product/436/108968) :::: --- ## 常见问题 ### 索引多久能完成? 通常在文件上传后 1-5 分钟内完成,具体时间取决于文件大小和数量。 ### 支持哪些文件格式? - **图片**:JPG、PNG、BMP、GIF、WebP、HEIF - **视频**:MP4、AVI、MOV、FLV 等主流格式 **检索文件:** ::::tip 📚 更多 API 示例 完整的 API 调用方式和更多语言示例,请参考: - [创建数据集 API 文档](https://cloud.tencent.com/document/product/436/108953) - [数据检索 API 文档](https://cloud.tencent.com/document/product/436/108954) - [COS SDK 文档](https://cloud.tencent.com/document/product/436/8629) :::: ### 如何提高检索准确度? 1. **高质量图片**:使用清晰、主体明确的图片 2. **多标签组合**:使用多个标签组合检索 3. **合理阈值**:以图搜图时适当调整相似度阈值 4. **人工审核**:对重要检索结果进行人工二次确认 ### 索引数据如何存储? 智能检索的索引数据由数据万象服务管理,不占用您的云存储空间。建议将标签信息同步存储到 CloudBase 数据库,便于复杂查询。 --- ## 相关资源 - [智能检索(数据检索)官方文档](https://cloud.tencent.com/document/product/436/107199) - [数据集管理文档](https://cloud.tencent.com/document/product/436/107200) - [字段和操作符支持列表](https://cloud.tencent.com/document/product/460/106154) - [数据万象计费说明](https://cloud.tencent.com/document/product/460/6970) - [提交工单](https://console.cloud.tencent.com/workorder/category?source=14&level1_id=83&level2_id=84&data_title=%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8) --- # 云存储/其他参考/文件存储迁移到云开发 > 当前文档链接: https://docs.cloudbase.net/storage/migrate-cos 本文档将指导您如何将数据从本地存储或其他云平台迁移到 CloudBase 云存储。 ## 迁移方案概览 根据您的数据来源和迁移需求,选择合适的迁移方案: | 数据来源 | 推荐工具 | 适用场景 | 数据量 | |---------|---------|---------|--------| | [本地数据](#方案-1cosbrowser推荐) | COSBrowser | 可视化操作、中小数据量 | < 10GB | | [本地数据 / 腾讯云COS](#方案-2coscli) | COSCLI | 命令行操作、批量处理 | 任意 | | [第三方云存储](#二第三方云存储迁移到云存储) | 云迁移 CMG、脚本 | 跨云平台迁移 | 任意 | ## 一、本地数据迁移到云存储 ### 方案 1:COSBrowser(推荐) #### 工具介绍 COSBrowser 是一款可视化界面工具,支持用户通过简单的界面操作进行数据上传、下载、生成访问链接等功能。 #### 适用场景 - ✅ 中小数据量迁移(< 10GB) - ✅ 需要可视化操作界面 - ✅ 批量上传下载文件 - ✅ 管理云存储文件 #### 操作步骤 **1. 下载并安装 COSBrowser** 访问 [COSBrowser 下载页面](https://cloud.tencent.com/document/product/436/11366) 下载并安装客户端。 **2. 使用密钥登录** :::warning 重要说明 必须使用 **密钥登录** 方式,并指定存储桶名和地域。使用腾讯云账号直接登录无法管理 CloudBase 云存储桶。 ::: 登录步骤: 1. 打开 COSBrowser 工具 2. 选择「密钥登录」 3. 填入以下信息: - **SecretId / SecretKey**:在 [访问密钥管理](https://console.cloud.tencent.com/cam/capi) 中获取 - **存储桶名称**:[云开发/云存储](https://tcb.cloud.tencent.com/dev?#/storage) 获取存储桶名称 ![](https://qcloudimg.tencent-cloud.cn/raw/713d70fa79f35c0e6ac8ad8dbeb89d14.png) - **所属地域**:选择您的环境所在地域(云存储这里固定为: `ap-shanghai`) **3. 上传本地文件** 登录成功后: 1. 在左侧本地文件系统中选择要上传的文件或文件夹 2. 拖拽到右侧云存储区域,或右键点击选择「上传」 3. 等待上传完成 详细使用方法请参考:[COSBrowser 使用文档](/storage/cosbrowser) --- ### 方案 2:COSCLI #### 工具介绍 COSCLI 是客户端命令行工具,使用 Golang 构建,无需安装依赖环境,开箱即用。通过简单的命令行指令即可对云存储中的对象实现批量上传、下载、删除等操作。 :::info 技术支持说明 COSCLI 工具由 **腾讯云 COS 团队** 维护,CloudBase 云开发团队不提供该工具的技术支持。使用该工具需要具备一定的命令行操作和技术基础。如遇到工具相关问题,请参考 [COSCLI 官方文档](https://cloud.tencent.com/document/product/436/63143) 或联系 COS 团队。 ::: #### 适用场景 - ✅ 大量文件批量迁移 - ✅ 自动化脚本部署 - ✅ 熟悉命令行操作 - ✅ 支持跨桶操作 详细使用方法请参考:[COSCLI 工具文档](https://cloud.tencent.com/document/product/436/63143) ## 二、第三方云存储迁移到云开发 ### 方案 : 云迁移 CMG [云迁移(Cloud Migration,CMG)](https://cloud.tencent.com/document/product/659/13908)是腾讯云提供的迁移工具整合以及监控平台。如果您的数据存储在阿里云 OSS、AWS S3 等第三方云平台,可以使用云迁移 CMG服务快速迁移到 CloudBase 云存储。 #### 支持的云平台 云迁移 CMG 支持从以下云平台迁移数据: | 云平台 | 迁移教程 | |--------|---------| | **阿里云 OSS** | [阿里云 OSS 迁移教程](https://cloud.tencent.com/document/product/659/81171) | | **AWS S3** | [AWS S3 迁移教程](https://cloud.tencent.com/document/product/659/81159) | | **七牛云** | [七牛云迁移教程](https://cloud.tencent.com/document/product/659/81165) | | **又拍云** | [又拍云迁移教程](https://cloud.tencent.com/document/product/659/81167) | | **华为云 OBS** | [华为云 OBS 迁移教程](https://cloud.tencent.com/document/product/659/81170) | | **金山云 KS3** | [金山云 KS3 迁移教程](https://cloud.tencent.com/document/product/659/81170) | | **百度云 BOS** | [百度云 BOS 迁移教程](https://cloud.tencent.com/document/product/659/81168) | ## 三、LeanCloud 存储迁移到云开发 ### 方案:使用迁移脚本工具 :::tip 下载迁移脚本 本迁移方案需要使用官方提供的迁移脚本工具。请先下载脚本包: ➡️ [下载 LeanCloud 迁移工具](https://6c6f-lowcode-0gwpl9v4125156ef-1258057692.tcb.qcloud.la/leancloud_storage_migrate.zip?sign=7c0169f55e50bcd59813747619e04a05&t=1768989575) 🚀 下载后解压到本地,请仔细阅读 `README.md` 文档,按照以下步骤进行配置和运行。 ::: 使用迁移脚本工具可以帮助你将 LeanCloud 存储的文件批量迁移到腾讯云开发云存储。 #### 功能特性 - ✅ 从 LeanCloud `_File` 表读取所有文件元数据 - ✅ 自动拼接 URL 并批量下载文件 - ✅ 批量上传到腾讯云开发云存储 - ✅ 支持并发控制,提高迁移效率 - ✅ 自动重试失败的下载 - ✅ 生成详细的迁移报告 #### 项目结构 ##### 原始结构(解压后) 这是你解压 zip 包后看到的文件结构: ``` leancloud_storage_migrate/ ├── migrate.js # 主迁移脚本(核心文件) ├── package.json # 项目依赖配置文件 └── README.md # 项目说明文档 ``` :::info说明 这 3 个文件是项目的原始文件,需要手动配置后才能使用。 ::: ##### 安装依赖、脚本运行后的结构 ``` leancloud_storage_migrate/ ├── migrate.js # 主迁移脚本(核心文件) ├── package.json # 项目依赖配置文件 ├── package-lock.json # 依赖版本锁定文件(自动生成) ├── README.md # 项目说明文档 ├── .gitignore # Git 忽略文件配置 ├── node_modules/ # 依赖包目录(npm install 后生成) ├── temp_files/ # 临时文件存储目录(运行时自动创建,完成后自动删除) └── migration_report.json # 迁移报告文件(运行完成后生成) ``` #### 快速开始 **第一步:安装 Node.js 依赖** 在项目目录下运行以下命令安装所需依赖: ```bash cd /path/to/leancloud_storage_migrate npm install ``` 这将自动安装以下依赖包: - `leancloud-storage` - LeanCloud SDK - `@cloudbase/node-sdk` - 腾讯云开发 Node.js SDK **第二步:配置参数** 打开 `migrate.js` 文件,找到配置区域(约在第 17-32 行),填入你的配置信息: ```javascript // LeanCloud 配置 const LEANCLOUD_CONFIG = { appId: 'YOUR_LEANCLOUD_APP_ID', // 👈 替换为你的 LeanCloud App ID appKey: 'YOUR_LEANCLOUD_APP_KEY', // 👈 替换为你的 LeanCloud App Key serverURL: 'https://YOUR_LEANCLOUD_SERVER_URL', // 👈 替换为你的 API 域名 fileDomain: 'https://YOUR_FILE_DOMAIN' // 👈 替换为你的文件域名 }; // 腾讯云开发配置 const CLOUDBASE_CONFIG = { env: 'YOUR_ENV_ID', // 腾讯云开发环境 ID secretId: 'YOUR_SECRET_ID', // 腾讯云 API 密钥 SecretId secretKey: 'YOUR_SECRET_KEY' // 腾讯云 API 密钥 SecretKey }; ``` :::info获取配置信息说明 **LeanCloud 配置:** 1. 登录 [LeanCloud 控制台](https://console.leancloud.cn/) 2. 选择你的应用 3. 进入「设置」→「应用凭证」,获取 `App ID` 和 `App Key` 4. 进入「设置」→「域名绑定」,获取 API 域名(`serverURL`)和文件域名(`fileDomain`) **腾讯云开发配置:** 1. 登录 [腾讯云开发平台](https://tcb.cloud.tencent.com/dev) 2. 选择你的环境,获取环境 ID(`env`) 3. 登录 [腾讯云 API 密钥管理](https://console.cloud.tencent.com/cam/capi) 4. 创建或查看密钥,获取 `SecretId` 和 `SecretKey` **⚠️ 重要:配置安全域名** - 登录 [腾讯云开发平台](https://tcb.cloud.tencent.com/dev) - 进入「环境管理」→「安全来源」 - 添加你的域名到安全域名列表(避免 CORS 错误) ::: **第三步:运行迁移脚本** 配置完成后,执行以下命令启动迁移: ```bash npm start ``` 或者直接使用 Node.js 运行: ```bash node migrate.js ``` **第四步:查看运行结果** 脚本运行时会实时显示进度信息: ``` ======================================== LeanCloud → 腾讯云开发 文件迁移工具 ======================================== 开始从 LeanCloud 查询 _File 表... 已查询 50 个文件... ✓ 共查询到 50 个文件 开始迁移文件,并发数: 5 ---------------------------------------- [1] 处理文件: example.jpg 下载 URL: https://xxx.com/xxx.jpg 正在下载... ✓ 下载完成 正在上传到云开发: migrated/example.jpg ✓ 上传完成,fileID: cloud://xxx... [2] 处理文件: photo.png 下载 URL: https://xxx.com/yyy.png 正在下载... ✓ 下载完成 正在上传到云开发: migrated/photo.png ✓ 上传完成,fileID: cloud://yyy... ... ======================================== 迁移完成! ---------------------------------------- 总计: 50 个文件 成功: 48 个 失败: 2 个 迁移报告已保存到: ./migration_report.json ``` 完成后会在当前目录生成 `migration_report.json` 报告文件。 #### 可选配置 你可以调整以下配置来优化迁移性能: ```javascript const DOWNLOAD_CONFIG = { tempDir: './temp_files', // 临时文件存储目录 concurrency: 5, // 并发下载数量(建议 3-10) retryTimes: 3, // 下载失败重试次数 timeout: 30000 // 请求超时时间(毫秒) }; ``` #### 迁移流程 1. **查询文件列表**:从 LeanCloud `_File` 表查询所有文件 2. **下载文件**:根据 `key` 拼接 URL 并下载到本地临时目录 3. **上传文件**:将文件上传到腾讯云开发云存储 4. **生成报告**:生成 `migration_report.json` 报告文件 5. **清理临时文件**:删除本地临时文件 #### 输出结果 迁移完成后,会生成 `migration_report.json` 报告文件,包含: ```json { "timestamp": "2026-01-21T10:30:00.000Z", "summary": { "total": 100, "success": 98, "failed": 2 }, "successList": [ { "objectId": "xxx", "name": "example.jpg", "fileID": "cloud://xxx.cloudbase.net/xxx", "cloudPath": "migrated/example.jpg" } ], "errorList": [ { "objectId": "yyy", "name": "failed.png", "error": "下载超时" } ] } ``` #### 注意事项 1. **网络稳定性**:确保网络连接稳定,大文件迁移可能需要较长时间 2. **磁盘空间**:确保有足够的磁盘空间存储临时文件 3. **并发数量**:根据网络带宽调整并发数,避免过高导致请求失败 4. **重试机制**:失败的文件会自动重试,最终失败的会记录在报告中 5. **云端路径**:所有文件会上传到 `migrated/` 目录下,可根据需要修改 6. **fileID 保存**:上传后的 `fileID` 会记录在报告中,建议保存以便后续使用 #### 故障排查 **下载失败** - 检查 `fileDomain` 配置是否正确 - 检查网络连接是否正常 - 尝试增加 `timeout` 超时时间 **上传失败** - 确认腾讯云开发环境 ID 正确 - 检查是否配置了安全域名 - 确认云存储配额是否充足 **CORS 错误** - 登录腾讯云开发控制台 - 在「安全配置」→「安全来源」中添加你的域名 --- ## 四、常见问题 ### 1. 如何提高迁移速度? - 使用 **COSCLI** 工具的并发上传功能 - 选择与源数据 **相同地域** 减少网络延迟 - 使用 **高带宽网络** 进行传输 - 对于小文件,可以 **打包后上传** 再解压 ### 2. 迁移会产生哪些费用? - **存储费用**:按实际存储量计费 - **流量费用**:CDN 流量费用 详见:[CloudBase 计费说明](https://cloud.tencent.com/document/product/876/39095) --- # 云存储/其他参考/常见问题 > 当前文档链接: https://docs.cloudbase.net/storage/faq ## 云存储文件突然不能访问了 当遇到云存储文件无法访问的情况时,可以按照以下步骤进行问题定位和解决: 1. **检查云存储权限配置**: 确保云存储的权限配置正确,特别是是否设置了公有读权限。错误的权限设置可能导致文件无法被正确访问。 2. **检查云开发套餐的 CDN 流量**: 前往云开发控制台,查看当前云开发套餐的 CDN 流量使用情况。如果 CDN 流量已用完,可能会导致文件访问受限。 3. **开启超限按量计费**: 如果 CDN 流量确实已用完,可以通过以下路径开启超限按量计费,以继续使用云存储服务: - 微信开发者工具 -> 云开发控制台 -> 设置 -> 环境设置 -> 按量计费 按照以上步骤操作后,云存储文件访问问题通常可以得到解决。如果问题依旧存在,建议查阅微信云开发官方文档或联系技术支持获取进一步帮助。 ## 云存储文件误删如何恢复 如果不小心删除了云存储中的文件,可以通过腾讯云 COS(对象存储)的回收站功能尝试恢复。 ### 前提条件 云存储的回收站功能需要**提前开启**才能使用。如果删除文件前未开启回收站功能,则无法恢复已删除的文件。 ### 恢复步骤 #### 1. 进入 COS 存储桶 1. 登录 [云开发控制台](https://tcb.cloud.tencent.com/dev) 2. 进入对应的云开发环境 3. 点击「云存储」模块 4. 点击「前往 COS 存储桶」按钮 #### 2. 查看回收站 1. 在 COS 控制台中,找到对应的存储桶 2. 点击「文件列表」 3. 查看是否有「回收站」选项 4. 进入回收站查找被删除的文件 #### 3. 恢复文件 1. 在回收站中找到需要恢复的文件 2. 选中文件后点击「恢复」 3. 文件将恢复到原来的路径 ### 常见问题 #### 为什么回收站是空的? 可能的原因: - 删除文件时未开启回收站功能 - 回收站中的文件已超过保留期限被自动清理 - 文件被永久删除(跳过回收站) #### 提示没有权限怎么办? 如果访问 COS 控制台时提示没有权限,需要: 1. 确认当前登录账号是云开发环境的主账号或有权限的子账号 2. 如果是子账号,需要主账号授予 COS 相关权限 #### 如何开启回收站功能? 建议提前在 COS 控制台开启回收站功能,防止误删文件无法恢复: 1. 进入 COS 控制台 2. 选择对应的存储桶 3. 进入「基础配置」 4. 找到「回收站」选项并开启 ### 相关文档 - [COS 回收站功能说明](https://cloud.tencent.com/document/product/436/106495) - [云存储管理指南](https://docs.cloudbase.net/storage/introduce) ## 资源包存储超限说明 - 当资源包的文件量超过套餐额度会导致文件存储服务被暂停,文件可能无法上传,您将无法上传、下载或访问任何存储在云端的文件 - 若需要更大的文件量存储,建议先升级您的套餐。 --- # cloud_function Documentation > 云开发函数服务,基于Serverless架构的函数计算平台,支持多种编程语言和触发器 # 云函数/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cloud-function/introduce --- title: 云函数 sidebar_label: 概述 description: CloudBase 云函数是腾讯云开发提供的无服务器计算服务,让您专注于业务逻辑开发,而无需管理服务器 keywords: [云函数, 无服务器, Serverless, CloudBase, 腾讯云开发] --- import DocCardList from '@theme/DocCardList'; # 云函数 **CloudBase 云函数**是腾讯云开发提供的**无服务器计算服务**,让您专注于业务逻辑开发,而无需管理服务器。写代码,上传,运行 —— 就这么简单。 ## 基本功能 CloudBase 云函数是运行在云端的代码片段,当事件发生时自动执行。您只需编写业务逻辑,CloudBase 负责服务器管理、扩缩容和运维。 **核心能力:** - 🚀 **事件驱动执行** - HTTP 请求、数据变化、定时任务自动触发 - ⚡ **自动扩缩容** - 根据请求量自动调整,最大并发 1000 - 💰 **按需付费** - 用多少付多少,空闲时零费用 - 🔗 **服务集成** - 与数据库、存储、认证无缝集成 **支持语言:** Node.js、Python、Java、Go、PHP **函数类型:** - **普通云函数** - 处理结构化业务逻辑,适合 API 接口、数据处理 - **HTTP 云函数** - 完整的 Web 服务能力,适合 Web 应用、文件上传 ## 工作原理 ### 执行流程 用户请求 → 事件触发 → 函数实例启动 → 执行代码 → 返回结果 → 实例回收 1. **事件触发** - HTTP 请求、数据库变更、定时任务等触发函数 2. **实例管理** - CloudBase 自动创建函数实例,加载代码 3. **代码执行** - 在隔离环境中执行业务逻辑 4. **服务调用** - 通过 SDK 访问数据库、存储等云服务 5. **结果返回** - 处理完成后返回结果,实例自动回收 ### 触发方式 | 触发方式 | 说明 | 使用场景 | |---------|------|----------| | **SDK 调用** | 客户端 SDK 直接调用 | 小程序、Web 应用 | | **HTTP 请求** | 通过 HTTP API 调用 | 第三方系统集成 | | **定时触发** | 按时间规则自动执行 | 数据统计、系统维护 | ### 运行环境特性 - **隔离性** - 每个函数运行在独立的容器环境 - **无状态** - 函数实例之间不共享状态 - **临时性** - 实例在执行完成后自动回收 - **弹性** - 根据负载自动创建和销毁实例 ## 快速开始 1. **[快速开始](/cloud-function/quick-start)** - 5 分钟创建第一个函数 2. **[开发指南](/cloud-function/develop/introduce)** - 详细开发文档 3. **[示例代码](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions)** - 丰富的示例库 4. **[Web 控制台](https://tcb.cloud.tencent.com/dev)** - 可视化管理界面 --- ## 示例 --- # 云函数/函数类型 > 当前文档链接: https://docs.cloudbase.net/cloud-function/quickstart/select-types CloudBase 云函数提供两种类型,分别适用于不同的业务场景。**选择合适的函数类型对性能、开发效率和成本都有重要影响。** ## 本质区别 | 对比维度 | 普通云函数 | HTTP 云函数 | |---------|---------|-----------| | **设计理念** | 事件驱动架构 (EDA) | Web 服务模式 (Serverless Web) | | **触发方式** | 事件触发(SDK、定时器、云产品事件) | HTTP 请求直接触发 | | **入参格式** | 固定的 `event` 和 `context` 对象 | 原生 HTTP Request/Response | | **并发模式** | 单实例单请求 | **单实例多并发** | | **端口监听** | 不需要 | **必须监听 9000 端口** | | **执行模式** | 支持同步/异步调用 | 仅同步调用 | | **框架支持** | 普通函数代码 | Express、Koa、Next.js 等 Web 框架 | | **适用场景** | 后台任务、数据处理、云产品集成 | Web 网站、API 服务、传统框架迁移 | ## 如何选择? ### 选择「普通云函数」,如果你的场景是: #### ✅ 后台数据处理 - 图片/视频处理、格式转换 - 日志分析、数据清洗 - 批量数据处理 **示例场景**:用户上传图片到云存储,自动触发云函数进行压缩和水印处理。 ```javascript // 普通云函数 - 响应 COS 上传事件 exports.main = async (event, context) => { // event 包含触发事件的数据 const { fileID } = event; // 处理业务逻辑 const result = await processImage(fileID); // 返回 JSON 格式结果 return { code: 0, data: result }; }; ``` #### ✅ 定时任务 - 定期数据备份 - 定时报表生成 - 定时数据同步 **示例场景**:每天凌晨 2 点自动生成前一天的业务报表并发送邮件。 #### ✅ 云产品集成 - 响应云存储(COS)文件上传事件 - 消费消息队列(CKafka)消息 - 数据库触发器 **示例场景**:监听数据库变更,当用户注册时自动发送欢迎邮件。 #### ✅ 异步任务处理 - 不需要立即返回结果的后台操作 - 长时间运行的计算任务 - 批量处理任务 ### 选择「HTTP 云函数」,如果你的场景是: #### ✅ Web 应用开发 - 企业官网、落地页 - H5 页面、小程序后端 - 前后端分离的 API 服务 **示例场景**:开发一个前后端分离的待办事项应用,前端调用 HTTP 云函数的 RESTful API。 ```javascript // HTTP 云函数 - 使用 Express 框架 const express = require('express'); const app = express(); app.use(express.json()); // RESTful API 接口 app.get('/api/todos', async (req, res) => { const todos = await db.collection('todos').get(); res.json({ code: 0, data: todos }); }); app.post('/api/todos', async (req, res) => { const result = await db.collection('todos').add(req.body); res.json({ code: 0, data: result }); }); // 必须监听 9000 端口 app.listen(9000, () => { console.log('HTTP Function started on port 9000'); }); ``` #### ✅ 传统框架迁移 - 已有 Express/Koa/Next.js 等项目想快速上云 - **零改造或低改造迁移**(只需修改监听端口为 9000) - 利用现有的 npm 生态和中间件 **示例场景**:将现有的 Express 应用迁移到 Serverless,只需修改监听端口即可。 #### ✅ 高并发 Web 服务 - 需要单实例处理多个并发请求 - 降低冷启动影响 - 优化成本和性能 **对比说明**:HTTP 云函数支持单实例多并发,相同流量下可以用更少的实例数,显著降低冷启动次数和成本。 #### ✅ 实时通信场景 - WebSocket 长连接 - SSE(Server-Sent Events)服务端推送 - 文件流式上传下载 **示例场景**:构建实时聊天应用,使用 WebSocket 实现消息推送。 ## 详细说明 ### 普通云函数(Custom Function) 「普通云函数」基于**事件驱动架构**设计,函数被动响应各种事件源(定时器、云产品事件、SDK 调用等)。 **核心特点:** - **简单易用**:只需实现业务逻辑函数,无需关心 HTTP 协议细节 - **标准化入参**:`event` 包含事件数据,`context` 包含运行环境信息 - **多种触发方式**:支持 SDK、HTTP API、定时器、云产品事件触发 - **灵活的调用模式**: - **同步调用**:等待函数执行完成并返回结果 - **异步调用**:立即返回请求 ID,函数在后台执行 **典型使用场景:** | 场景类型 | 具体示例 | |---------|---------| | 数据处理 | 图片压缩、视频转码、文档格式转换 | | 定时任务 | 每日报表生成、定期数据备份、定时清理 | | 事件响应 | 文件上传后处理、消息队列消费、数据库变更触发 | | 微服务组件 | 独立的业务逻辑单元、后台计算服务 | ### HTTP 云函数(HTTP Function) 「HTTP 云函数」专为 **Web 服务场景**设计,让传统 Web 应用能够无缝迁移到 Serverless。 **核心特点:** - **原生 HTTP 支持**:直接接收和处理完整的 HTTP 请求,无需协议转换 - **零改造迁移**:现有 Web 框架代码只需修改监听端口为 9000 即可部署 - **高并发性能**:单实例可同时处理多个请求,大幅降低冷启动影响 - **完整生态支持**:兼容 Express、Koa、Next.js、Nuxt.js 等主流框架 - **丰富的 HTTP 特性**: - 完整的 HTTP Header、Cookie 操作 - 自定义 HTTP 状态码 - WebSocket、SSE 实时通信 - 文件流式处理 **典型使用场景:** | 场景类型 | 具体示例 | |---------|---------| | Web 应用 | 企业官网、产品落地页、管理后台 | | API 服务 | RESTful API、GraphQL 接口 | | 框架迁移 | Express/Koa/Next.js 项目快速上云 | | 实时通信 | WebSocket 聊天、SSE 消息推送、实时数据流 | ## 快速决策指南 不确定选择哪种类型?回答以下问题: 1. **你的服务需要直接响应 HTTP 请求吗?** - 是 → 考虑 HTTP 云函数 - 否 → 考虑普通云函数 2. **你的项目已经使用了 Express/Koa 等 Web 框架吗?** - 是 → 强烈推荐 HTTP 云函数(低改造成本) - 否 → 根据业务场景选择 3. **你的业务是否需要处理高并发的 Web 请求?** - 是 → HTTP 云函数(单实例多并发,性能更好) - 否 → 两者皆可 4. **你需要定时任务或响应云产品事件吗?** - 是 → 普通云函数(更适合事件驱动场景) - 否 → 根据业务场景选择 5. **你需要 WebSocket 或 SSE 实时通信吗?** - 是 → 必须使用 HTTP 云函数 - 否 → 两者皆可 :::tip 混合使用 在实际项目中,可以同时使用两种类型的云函数: - 用 HTTP 云函数处理 Web 请求和 API 接口 - 用普通云函数处理后台任务和定时任务 ::: ## 了解更多 - [云函数类型选型指南](https://cloud.tencent.com/document/product/583/73483) - [普通云函数开发指南](/cloud-function/how-coding) - [HTTP 云函数开发指南](/cloud-function/develop/how-to-writing-functions-code) --- # 云函数/开发说明 > 当前文档链接: https://docs.cloudbase.net/cloud-function/develop/introduce ## 编程语言支持 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; ### 普通云函数 | 语言 | 版本 | 推荐场景 | 特点 | |------|------|----------|------| | **Node.js** | 20.19, 18.15, 16.13 | Web API、数据处理 | 生态丰富、开发效率高 | | **Python** | 3.10, 3.9, 3.7 | 数据分析、AI/ML | 科学计算库丰富 | | **PHP** | 8.0, 7.4 | Web 开发、CMS | Web 开发传统优势 | | **Go** | 最新版本 | 高性能服务、系统工具 | 性能优秀、并发能力强 | | **Java** | 11 (Kona JDK) | 企业级应用、大数据 | 生态成熟、性能稳定 | ### HTTP 云函数 | 语言 | 版本 | 推荐框架 | 适用场景 | |------|------|----------|----------| | **Node.js** | 20.19, 18.15, 16.13 | Express, NestJS, Next.js | 全栈应用、API 服务 | | **Python** | 3.10, 3.9, 3.7 | Django, Flask, FastAPI | Web 应用、数据 API | | **PHP** | 8.0, 7.4 | Laravel, Symfony | 传统 Web 应用 | | **Go** | 最新版本 | Gin, Echo, Fiber | 高性能 API 服务 | | **Java** | 11 (Kona JDK) | Spring Boot, Quarkus | 企业级 Web 服务 | ### Node.js 版本特性 - **Node.js 20.19**(推荐) - 最新 LTS 版本,性能和安全性最佳 - 支持最新的 JavaScript 特性 - 更好的内存管理和启动性能 - **Node.js 18.15** - 稳定的 LTS 版本 - 广泛的生态系统支持 - 适合生产环境使用 - **Node.js 16.13** - 较老的 LTS 版本 - 兼容性考虑时使用 ### Python 版本特性 - **Python 3.10**(推荐) - 支持结构化模式匹配 - 更好的错误消息 - 性能优化 - **Python 3.9** - 字典合并操作符 - 类型提示改进 - 稳定可靠 - **Python 3.7** - 基础功能完整 - 兼容性考虑时使用 ## 开发环境准备 ### 本地开发环境 #### Node.js 开发环境 ```bash # 安装 Node.js(推荐使用 nvm 管理版本) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash nvm install 20.19.0 nvm use 20.19.0 # 验证安装 node --version npm --version ``` #### Python 开发环境 ```bash # 安装 Python(推荐使用 pyenv 管理版本) curl https://pyenv.run | bash pyenv install 3.10.0 pyenv global 3.10.0 # 创建虚拟环境 python -m venv cloudbase-env source cloudbase-env/bin/activate # Linux/macOS # cloudbase-env\Scripts\activate # Windows # 验证安装 python --version pip --version ``` ## 相关资源 ### 开发工具 - [CloudBase CLI](/cli-v1/functions/deploy) - 命令行工具 - [CloudBase SDK](/api-reference/server/node-sdk/functions) - 服务端 SDK 通过本指南,您应该已经了解了 CloudBase 云函数开发的核心概念和最佳实践。建议从简单的示例开始,逐步掌握云函数的开发和部署流程。 --- # 云函数/快速开始/普通云函数/快速开始 > 当前文档链接: https://docs.cloudbase.net/cloud-function/quick-start 云函数本质也是一份代码,这里以 **Node.js** 作为示例,介绍如何创建云函数。 **Node.js** 服务一般都需要一个**入口文件 index.js**,如果您用到了 **npm** 包,还需要一个 **package.json** 文件,用于描述依赖关系。 因此最基本的 Node.js 服务目录结构如下: ```bash └── helloWorld ├── index.js └── package.json ``` ## 创建云函数 您可以通过以下三种方式创建云函数,请根据您的开发环境选择合适的方式: 进入 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf),点击「**新建云函数**」按钮。 1. 选择合适的模版,没有合适的可以选择从空白创建 2. 选择运行环境为 **Nodejs 18.15**(也可以选择其他版本) 3. 填入函数名称 4. 点击「**开始创建**」按钮 创建完成后,进入到云函数中,可以看到默认有个 **index.js** 文件。 5. 点击下方「**保存并安装依赖**」,即可完成云函数的**部署** 如果您是基于「**微信开发者工具**」进行开发云函数,那么可以直接在微信开发者工具中创建云函数,步骤参考 [云函数快速开始](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/guide/functions/getting-started.html)。 1. 右键云函数目录,选择「**新建 Node.js 云函数**」,即可创建云函数 2. 编写 `index.js` 文件内容如下: ```js exports.main = async function () { return 'Hello World!'; }; ``` 3. 选择云函数目录,右键点击,选择「**创建并部署:云端安装依赖**」,即可完成云函数的**部署** 1. 在本地创建一个空的文件夹,作为项目的根目录,这里命名为 `cloud-demo`(项目根目录) 2. 进入根目录,创建 **functions** 文件夹(存放云函数的目录) 3. 在 **functions** 下创建 **hello_world** 文件夹(具体云函数目录) 4. 在云函数 **hello_world** 中创建 **index.js**(云函数入口文件) 此时目录结构如下: ```bash └── cloud-demo # 项目根目录 └── functions # 云函数目录 └── hello_world # 云函数实例文件夹 └── index.js # 云函数入口文件 ``` 5. 编写 `index.js` 文件内容如下: ```js exports.main = async function () { return 'Hello World!'; }; ``` 6. 然后打开终端到当前 `hello_world` 文件夹下,执行以下命令初始化 **package.json**: ```bash npm init -y ``` 7. 通过 [CloudBase CLI](/cli-v1/quick-start) 工具进行部署云函数 在终端执行如下命令全局安装 CLI: ```bash npm i -g @cloudbase/cli ``` 安装成功后输入如下命令检查是否安装成功: ```bash tcb -v ``` 如果看到输出版本号,说明已经安装成功。 8. CLI 登录 需要让 CLI 登录到云函数需要发布的环境中,执行如下命令: ```bash tcb login ``` CloudBase CLI 会自动打开云开发控制台获取授权,您需要点击「**同意授权**」按钮允许 CloudBase CLI 获取授权。如您没有登录,您需要登录后才能进行此操作。 9. 在**项目根目录**运行以下命令,并且使用**默认配置**: > ⚠️ 注意:需要获取云开发环境的**环境 ID** ```bash tcb fn deploy hello_world -e ``` ![](https://qcloudimg.tencent-cloud.cn/raw/5022534c1b74d6f2d11c838b680dcdb9.png) ## 调用云函数 调用云函数主要有以下几种方法: - 使用云开发 **SDK** - 使用 **HTTP 访问服务** - 使用 **HTTP API** - 使用[定时触发器](/cloud-function/timer-trigger) ### 使用 SDK 调用云函数 ```js wx.cloud .callFunction({ // 云函数名称 name: 'hello_world', // 传给云函数的参数 data: { a: 1, }, }) .then((res) => { console.log(res); // Hello World! }) .catch(console.error); ``` ```js import cloudbase from '@cloudbase/js-sdk'; const app = cloudbase.init({ env: 'xxxx-yyy', }); app.callFunction({ // 云函数名称 name: 'hello_world', // 传给云函数的参数 data: { a: 1, }, }) .then((res) => { console.log(res); // Hello World! }) .catch(console.error); ``` ```js const cloudbase = require('@cloudbase/node-sdk'); const app = cloudbase.init({ env: 'xxxx-yyy', }); app.callFunction({ // 云函数名称 name: 'hello_world', // 传给云函数的参数 data: { a: 1, }, }) .then((res) => { console.log(res); // Hello World! }) .catch(console.error); ``` ### 使用 HTTP 访问服务调用云函数 您可以选择创建 **HTTP 服务** 用来访问云函数,然后通过 HTTP 调用云函数。 有如下两种操作方式: #### 云开发控制台 1. 进入 [云开发平台/HTTP 访问服务](https://tcb.cloud.tencent.com/dev?#/env/http-access) 2. 新建一个「域名关联资源」: - 关联资源选择云函数,hello_world - 域名选择默认域名,也可以选择您自定义域名 - 填写触发路径,这里填写 `/hello` ![](https://qcloudimg.tencent-cloud.cn/raw/342a027d8d10805110608dce3f914b04.png) 3. 点击确定,等待 3-5 分钟后,即可完成 **HTTP 服务** 创建 ![](https://qcloudimg.tencent-cloud.cn/raw/9144a724e4fa6a106ed3449c0124f5ce.png) 4. HTTP 服务地址为 `默认域名+触发路径`,您也可以选择用自定义域名进行绑定资源 #### CLI 工具 具体请参考 [HTTP 访问服务](/cli-v1/service) 执行以下命令创建一条 HTTP 服务路由,路径为 `/hello`,指向的云函数为 `hello_world`: ```bash tcb service create -p hello -f hello_world -e ``` 随后便可以通过进入 [云开发平台/HTTP 访问服务](https://tcb.cloud.tencent.com/dev?#/env/http-access) 查看创建的 HTTP 服务。 ### 使用 HTTP API 调用云函数 创建好云函数后,可以通过 HTTP API 在任意客户端调用云函数,无需 SDK。 具体请参考 [HTTP API 调用云函数](/http-api/functions/functions-post),访问 HTTP API 需要一个 [ACCESS_TOKEN](/http-api/basic/access-token)。 ```bash curl -L 'https://your-envId.api.tcloudbasegateway.com/v1/functions/:name' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ -d '{}' ``` --- # 云函数/快速开始/HTTP 云函数/Node.js 快速开始 > 当前文档链接: https://docs.cloudbase.net/cloud-function/quickstart/httpfunc/nodejs 本文档介绍如何从零开始创建一个 Node.js 应用,并将其部署到 CloudBase HTTP 云函数中。 ## 前置条件 在开始之前,请确保您已经: - 安装了 [Node.js](https://nodejs.org/) (推荐 v18 或更高版本) - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Node.js 开发知识 ## 第一步:创建项目目录 创建名为 `helloworld` 的新目录,并进入该目录: ```bash mkdir helloworld cd helloworld ``` ## 第二步:初始化项目配置 创建 `package.json` 文件,用于描述项目信息和依赖关系,你可以通过 `npm init -y` 快速初始化项目配置 ```json { "name": "helloworld", "description": "Simple hello world sample in Node", "version": "1.0.0", "type": "module", "main": "index.js", "scripts": { "start": "node index.js" }, "author": "Tencent CloudBase", "license": "Apache-2.0" } ``` ## 第三步:编写应用代码 在同一目录中创建 `index.js` 文件,这是应用的入口文件: > ⚠️ **重要提示**:CloudBase HTTP 云函数的默认端口必须是 `9000`。 ```javascript import { createServer } from "node:http"; import { Readable } from "node:stream"; const server = createServer(async (req, res) => { if (req.url.split('?')[0] === "/") { res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" }); res.end("Hello World!"); } else if (req.url.split('?')[0] === "/myip") { // 设置 CORS 头,允许跨域请求 res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Content-Type", "application/json"); try { // 使用 fetch 获取远程数据(这里使用 ipinfo.io 作为示例) const response = await fetch("https://ipinfo.io", { headers: { Accept: "application/json", }, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } Readable.fromWeb(response.body).pipe(res); } catch (error) { console.error("获取 IP 信息失败:", error); res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Failed to fetch remote data", message: error.message })); } } else if (req.url.split('?')[0] === "/health") { // 健康检查接口 res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "healthy", timestamp: new Date().toISOString(), version: "1.0.0" })); } else { res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not Found" })); } }); const port = 9000; const host = "0.0.0.0"; server.listen(port, host, () => { console.log(`Server running at http://localhost:${port}/`); console.log(`Try accessing http://localhost:${port}/myip to see your IP info`); console.log(`Health check available at http://localhost:${port}/health`); }); ``` 此代码创建了一个基本的 Web 服务器,提供以下功能: - **根路径** (`/`):返回 "Hello World!" 消息 - **IP 查询** (`/myip`):获取并返回客户端 IP 信息 - **健康检查** (`/health`):返回服务状态信息 - **错误处理**:对于未知路径返回 404 错误 :::tip 提示 除了使用 Node.js 原生 `http` 模块,您还可以使用 `@cloudbase/functions-framework` 框架获得更简洁的开发体验。更多详细信息请参考 [Functions Framework 快速开始](/cloud-function/quickstart/httpfunc/func-framework)。 ::: ## 第四步: 创建启动脚本 > 💡 **注意**: > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 ```nano scf_bootstrap``` 或者 ```vim scf_bootstrap``` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 创建 `scf_bootstrap` 文件(无扩展名),这是 CloudBase 云函数的启动脚本: ```bash #!/bin/bash node index.js ``` > ⚠️ **注意**: > - 文件名必须是 `scf_bootstrap`,没有扩展名 > - 确保文件具有执行权限 为启动脚本添加执行权限: ```bash chmod +x scf_bootstrap ``` ## 第五步:本地测试(可选) 在部署之前,您可以在本地测试应用: ```bash node index.js ``` 测试成功后,您可以通过以下方式验证: - 访问 `http://localhost:9000/` 查看 Hello World 消息 - 访问 `http://localhost:9000/myip` 查看 IP 信息 - 访问 `http://localhost:9000/health` 查看健康状态 按 `Ctrl + C` 停止本地服务器。 ## 第六步:部署到 CloudBase HTTP 云函数 ### 准备部署文件 确保您的项目目录包含以下文件: ``` helloworld/ |-- scf_bootstrap ├── package.json └── index.js ``` ### 通过控制台部署 1. 登录 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf?tab=function) 2. 点击「新建云函数」 3. 选择「HTTP 云函数」 4. 填写函数名称(如:`helloworld`) 5. 选择运行时:**Node.js 18.x** 6. 选择「本地上传」方式 7. 将项目文件打包为 zip 文件并上传(请选择文件进行打包,不要打包根目录文件夹) 8. 点击「确定」完成部署 ### 通过 CLI 部署 详情请参考 [CLI 部署 HTTP 函数](/cli-v1/functions/deploy#http-函数) ### 打包项目 如果需要手动打包,可以使用以下命令: ```bash # 创建 zip 包 zip -r helloworld.zip package.json index.js scf_bootstrap ``` ## 第七步:访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问```HTTP 云函数```。 您可以通过以下方式测试: - 访问根路径查看 Hello World 消息 - 访问 `/myip` 路径查看 IP 信息 - 访问 `/health` 路径查看服务状态 ## 常见问题 ### Q: 为什么必须使用 9000 端口? A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。 ### Q: 如何查看函数日志? A: 在 CloudBase 控制台的云函数页面,点击函数名称进入详情页,可以查看运行日志。 ### Q: 支持哪些 Node.js 版本? A: CloudBase 支持 Node.js 12.x、14.x、16.x、18.x 等版本,建议使用最新的 LTS 版本。 ### Q: 如何处理 CORS 跨域问题? A: 在响应头中设置 `Access-Control-Allow-Origin` 等 CORS 相关头部,如示例代码所示。 ### Q: 函数冷启动时间较长怎么办? A: 可以通过预热机制、减少依赖包大小、优化代码逻辑等方式来减少冷启动时间。 ## 最佳实践 1. **错误处理**:始终为异步操作添加适当的错误处理 2. **响应头设置**:正确设置 Content-Type 和其他必要的响应头 3. **日志记录**:使用 `console.log` 记录关键信息,便于调试 4. **环境变量**:使用 `process.env` 管理配置信息 5. **代码结构**:对于复杂应用,建议使用 Express.js 等框架 ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/function-configuration/config) - 学习如何 [添加数据库操作](/api-reference/server/node-sdk/mysql/fetch) - 查看 [Express.js 框架集成](/cloud-function/frameworks-examples/express) --- # 云函数/快速开始/HTTP 云函数/Python 快速开始 > 当前文档链接: https://docs.cloudbase.net/cloud-function/quickstart/httpfunc/python 本文档介绍如何从零开始创建一个 Python 应用,并将其部署到 CloudBase HTTP 云函数中。该项目使用 Flask 作为 Web 应用框架。 ## 前置条件 在开始之前,请确保您已经: - 安装了 [Python 3.10](https://www.python.org/downloads/) 或更高版本 - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Python 和 Flask 开发知识 ## 第一步:创建项目目录 创建名为 `helloworld-python` 的新项目目录,并进入该目录: ```bash mkdir helloworld-python cd helloworld-python ``` ## 第二步:设置 Python 虚拟环境 创建并激活虚拟环境,以便管理项目依赖: ```bash # 创建虚拟环境 python -m venv env # 激活虚拟环境 # Linux/macOS source env/bin/activate # Windows # env\Scripts\activate ``` > 💡 **提示**:使用虚拟环境可以避免依赖冲突,确保项目的独立性。 ## 第三步:安装依赖 安装 Flask 框架: ```bash pip install flask ``` ## 第四步:编写应用代码 创建 `app.py` 文件作为应用的入口文件: > ⚠️ **重要提示**:CloudBase HTTP 云函数的默认端口必须是 `9000`。 ```python import os from flask import Flask, jsonify, request app = Flask(__name__) @app.route('/') def hello(): """根路径处理函数""" return 'Hello World from Python Flask!' @app.route('/health') def health_check(): """健康检查接口""" return jsonify({ 'status': 'healthy', 'message': 'Python Flask app is running', 'python_version': os.sys.version }) @app.route('/api/info') def get_info(): """获取请求信息""" return jsonify({ 'method': request.method, 'path': request.path, 'headers': dict(request.headers), 'args': dict(request.args) }) @app.errorhandler(404) def not_found(error): """404 错误处理""" return jsonify({'error': 'Not Found', 'message': 'The requested resource was not found'}), 404 @app.errorhandler(500) def internal_error(error): """500 错误处理""" return jsonify({'error': 'Internal Server Error', 'message': 'Something went wrong'}), 500 if __name__ == '__main__': # 监听所有网络接口的 9000 端口 app.run(host='0.0.0.0', port=9000, debug=False) ``` 此代码创建了一个基本的 Flask Web 应用,提供以下功能: - **根路径** (`/`):返回欢迎消息 - **健康检查** (`/health`):返回应用状态信息 - **信息接口** (`/api/info`):返回请求详细信息 - **错误处理**:统一的 404 和 500 错误处理 ## 第五步:生成依赖文件 生成 `requirements.txt` 文件,记录项目依赖: ```bash pip freeze > requirements.txt ``` 查看生成的依赖文件: ```bash cat requirements.txt ``` 典型的 `requirements.txt` 内容如下: ```txt blinker==1.7.0 click==8.1.7 Flask==3.0.0 itsdangerous==2.1.2 Jinja2==3.1.2 MarkupSafe==2.1.3 Werkzeug==3.0.1 ``` ## 第六步:创建启动脚本 > 💡 **注意**: > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 ```nano scf_bootstrap``` 或者 ```vim scf_bootstrap``` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 创建 `scf_bootstrap` 文件(无扩展名),这是 CloudBase 云函数的启动脚本: ```bash #!/bin/bash # 设置 Python 依赖加载路径,我们默认设置在 third_party 目录 export PYTHONPATH="./third_party:$PYTHONPATH" /var/lang/python310/bin/python3.10 app.py ``` > ⚠️ **注意**: > - 文件名必须是 `scf_bootstrap`,没有扩展名 > - Python 路径需要与运行时版本匹配(这里使用 Python 3.10) > - 确保文件具有执行权限 为启动脚本添加执行权限: ```bash chmod +x scf_bootstrap ``` ## 第七步:本地测试(可选) 在部署之前,您可以在本地测试应用: ```bash python app.py ``` 测试成功后,您可以通过以下方式验证: - 访问 `http://localhost:9000/` 查看欢迎消息 - 访问 `http://localhost:9000/health` 查看健康状态 - 访问 `http://localhost:9000/api/info` 查看请求信息 按 `Ctrl + C` 停止本地服务器。 ## 第八步:准备部署文件 部署前将依赖安装到 `third_party` 目录下: > ⚠️ **注意**: > - HTTP 云函数并不会自动安装 Python 依赖,所以我们需要自己将依赖下载到代码包中 ```bash pip install -r requirements.txt -t third_party ``` 确保您的项目目录包含以下文件: ``` helloworld-python/ ├── third_party/ # Python 依赖 ├── app.py # 应用主文件 ├── requirements.txt # 依赖列表 └── scf_bootstrap # 启动脚本 ``` ## 第九步:打包项目 将项目文件打包为 ZIP 文件: ```bash # 确保在项目根目录 zip -r helloworld-python.zip third_party app.py scf_bootstrap ``` > 💡 **打包提示**: > - 包含 `third_party` 目录以确保依赖完整 > - 确保 `scf_bootstrap` 文件包含在内 > - 避免包含不必要的文件(如 `.git`、`__pycache__` 等) ## 第十步:部署到 CloudBase HTTP 云函数 ### 通过控制台部署 1. 登录 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf?tab=function) 2. 点击「新建云函数」 3. 选择「HTTP 云函数」 4. 填写函数名称(如:`helloworld-python`) 5. 选择运行时:**Python 3.10** 6. 选择「本地上传」方式 7. 上传刚才创建的 `helloworld-python.zip` 文件 8. 点击「确定」完成部署 ### 通过 CLI 部署 详情请参考 [CLI 部署 HTTP 函数](/cli-v1/functions/deploy#http-函数) ## 第十一步:访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问```HTTP 云函数```。 您可以通过以下方式测试: - 访问根路径查看欢迎消息 - 访问 `/health` 路径查看应用状态 - 访问 `/api/info` 路径查看请求信息 ## 常见问题 ### Q: 为什么必须使用 9000 端口? A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。 ### Q: scf_bootstrap 文件的作用是什么? A: `scf_bootstrap` 是云函数的启动脚本,用于设置环境变量和启动应用程序。 ### Q: 如何查看函数日志? A: 在 CloudBase 控制台的云函数页面,点击函数名称进入详情页,可以查看运行日志。 ### Q: 支持哪些 Python 版本? A: CloudBase 支持 Python 3.6、3.7、3.9、3.10 等版本,建议使用 Python 3.10。 ### Q: 如何处理依赖包过大的问题? A: 可以使用层(Layer)功能来管理大型依赖包,或者使用 `pip install --no-deps` 只安装必要的包。 ## 最佳实践 1. **依赖管理**:使用 `requirements.txt` 精确指定依赖版本 2. **错误处理**:实现统一的错误处理机制 3. **日志记录**:使用 Python 的 `logging` 模块记录关键信息 4. **环境变量**:使用环境变量管理配置信息 5. **代码结构**:对于复杂应用,建议使用蓝图(Blueprint)组织代码 ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/function-configuration/config) - 学习如何 [连接数据库](/api-reference/server/node-sdk/mysql/fetch) - 查看 [Flask 框架进阶用法](/cloud-function/frameworks-examples/flask) --- # 云函数/快速开始/HTTP 云函数/Go 快速开始 > 当前文档链接: https://docs.cloudbase.net/cloud-function/quickstart/httpfunc/golang 本文档介绍如何从零开始创建一个 Go 应用,并将其部署到 CloudBase HTTP 云函数中。 ## 前置条件 在开始之前,请确保您已经: - 安装了 [Go](https://golang.org/dl/) (推荐 Go 1.19 或更高版本) - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Go 开发知识 ## 第一步:创建项目目录 创建名为 `helloworld-golang` 的新目录,并进入该目录: ```bash mkdir helloworld-golang cd helloworld-golang ``` ## 第二步:初始化项目配置 初始化 Go 模块: ```bash go mod init helloworld-golang ``` 这将在当前目录创建 `go.mod` 文件。 ## 第三步:安装依赖 安装必要的依赖包: ```bash go get github.com/gin-gonic/gin go get github.com/gin-contrib/cors ``` 这将安装 Gin Web 框架和 CORS 中间件。 ## 第四步:编写应用代码 在同一目录中创建 `main.go` 文件,这是应用的入口文件: > ⚠️ **重要提示**:CloudBase HTTP 云函数的默认端口必须是 `9000`。 ```go package main import ( "encoding/json" "io" "net/http" "os" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) // IPInfo 结构体用于存储 IP 信息 type IPInfo struct { IP string `json:"ip"` City string `json:"city"` Region string `json:"region"` Country string `json:"country"` Loc string `json:"loc"` Org string `json:"org"` } func main() { r := gin.Default() // 配置 CORS r.Use(cors.New(cors.Config{ AllowOrigins: []string{"*"}, AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, })) // 根路径 r.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Hello World!") }) // 获取 IP 信息 r.GET("/myip", func(c *gin.Context) { resp, err := http.Get("https://ipinfo.io/json") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to fetch remote data", "message": err.Error(), }) return } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to read response", "message": err.Error(), }) return } c.Data(http.StatusOK, "application/json", body) }) // 健康检查 r.GET("/health", func(c *gin.Context) { health := map[string]interface{}{ "status": "healthy", "timestamp": "2024-01-01T00:00:00Z", // 实际应用中应使用 time.Now().Format(time.RFC3339) "version": "1.0.0", } c.JSON(http.StatusOK, health) }) // 404 处理 r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{"error": "Not Found"}) }) // 启动服务器 port := os.Getenv("PORT") if port == "" { port = "9000" } r.Run(":" + port) } ``` 此代码创建了一个基本的 Web 服务器,提供以下功能: - **根路径** (`/`):返回 "Hello World!" 消息 - **IP 查询** (`/myip`):获取并返回客户端 IP 信息 - **健康检查** (`/health`):返回服务状态信息 - **错误处理**:对于未知路径返回 404 错误 ## 第五步:创建启动脚本 > 💡 **注意**: > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 ```nano scf_bootstrap``` 或者 ```vim scf_bootstrap``` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 创建 `scf_bootstrap` 文件(无扩展名),这是 CloudBase 云函数的启动脚本: ```bash #!/bin/bash ./main ``` > ⚠️ **注意**: > - 文件名必须是 `scf_bootstrap`,没有扩展名 > - 确保文件具有执行权限 为启动脚本添加执行权限: ```bash chmod +x scf_bootstrap ``` ## 第六步:编译应用 编译 Go 应用: ```bash GOOS=linux GOARCH=amd64 go build -o main main.go ``` 这将在 Linux 环境下编译可执行文件,确保在 CloudBase 环境中正常运行。 ## 第七步:本地测试(可选) 在部署之前,您可以在本地测试应用: ```bash go run main.go ``` 或者运行编译后的二进制文件: ```bash ./main ``` 测试成功后,您可以通过以下方式验证: - 访问 `http://localhost:9000/` 查看 Hello World 消息 - 访问 `http://localhost:9000/myip` 查看 IP 信息 - 访问 `http://localhost:9000/health` 查看健康状态 按 `Ctrl + C` 停止本地服务器。 ## 第八步:部署到 CloudBase HTTP 云函数 ### 准备部署文件 确保您的项目目录包含以下文件: ``` helloworld/ ├── scf_bootstrap ├── go.mod ├── go.sum ├── main.go └── main ``` ### 通过控制台部署 1. 登录 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf?tab=function) 2. 点击「新建云函数」 3. 选择「HTTP 云函数」 4. 填写函数名称(如:`helloworld-go`) 5. 选择运行时:**Go 1.x** 6. 选择「本地上传」方式 7. 将项目文件打包为 zip 文件并上传(请选择文件进行打包,不要打包根目录文件夹) 8. 点击「确定」完成部署 ### 通过 CLI 部署 详情请参考 [CLI 部署 HTTP 函数](/cli-v1/functions/deploy#http-函数) ### 打包项目 如果需要手动打包,可以使用以下命令: ```bash # 创建 zip 包 zip -r helloworld-golang.zip main scf_bootstrap ``` ## 第九步:访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问```HTTP 云函数```。 您可以通过以下方式测试: - 访问根路径查看 Hello World 消息 - 访问 `/myip` 路径查看 IP 信息 - 访问 `/health` 路径查看服务状态 ## 常见问题 ### Q: 为什么必须使用 9000 端口? A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。 ### Q: 如何查看函数日志? A: 在 CloudBase 控制台的云函数页面,点击函数名称进入详情页,可以查看运行日志。 ### Q: 支持哪些 Go 版本? A: CloudBase 支持 Go 1.16、1.17、1.18、1.19 等版本,建议使用较新的稳定版本。 ### Q: 如何处理 CORS 跨域问题? A: 使用 Gin 的 CORS 中间件配置跨域,如示例代码所示。 ### Q: 函数冷启动时间较长怎么办? A: 可以通过减小二进制文件大小、优化代码逻辑、使用静态编译等方式来减少冷启动时间。 ### Q: 如何进行交叉编译? A: 使用 `GOOS=linux GOARCH=amd64` 环境变量进行 Linux 交叉编译,确保在 CloudBase 环境中正常运行。 ### Q: 为什么需要编译成二进制文件? A: CloudBase 云函数环境是 Linux 系统,需要编译成对应平台的二进制文件才能正常运行。 ## 最佳实践 1. **错误处理**:始终为 HTTP 请求添加适当的错误处理 2. **响应头设置**:正确设置 Content-Type 和其他必要的响应头 3. **日志记录**:使用 `log` 包记录关键信息,便于调试 4. **环境变量**:使用 `os.Getenv()` 管理配置信息 5. **代码结构**:对于复杂应用,建议按功能模块拆分文件 6. **性能优化**:使用连接池、缓存等技术提升性能 7. **安全考虑**:验证输入参数,防止注入攻击 ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/function-configuration/config) - 查看 [Gin 框架集成](/cloud-function/frameworks-examples/gin) --- # 云函数/快速开始/HTTP 云函数/Java 快速开始 > 当前文档链接: https://docs.cloudbase.net/cloud-function/quickstart/httpfunc/java 本文档介绍如何从零开始创建一个 Java Spring Boot 应用,并将其部署到 CloudBase HTTP 云函数中。 ## 前置条件 在开始之前,请确保您已经: - 安装了 [Java JDK](https://www.oracle.com/java/technologies/downloads/) (推荐 JDK 8 或更高版本) - 安装了 [Maven](https://maven.apache.org/) - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Spring Boot 开发知识 ## 第一步:创建项目目录 创建名为 `helloworld-java` 的新目录,并进入该目录: ```bash mkdir helloworld-java cd helloworld-java ``` ## 第二步:初始化项目配置 创建 `pom.xml` 文件,用于描述项目信息和依赖关系: ```xml 4.0.0 com.example helloworld-java 1.0.0 jar helloworld-java Simple hello world sample in Spring Boot org.springframework.boot spring-boot-starter-parent 2.7.18 8 8 8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-maven-plugin ``` ## 第三步:创建标准 Spring Boot 目录结构 ```bash mkdir -p src/main/java/com/example/demo mkdir -p src/main/resources ``` ## 第四步:编写应用代码 创建 `src/main/java/com/example/demo/DemoApplication.java` 文件,这是应用的入口文件: > ⚠️ **重要提示**:CloudBase HTTP 云函数的默认端口必须是 `9000`。 ```java package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.*; import org.springframework.http.*; import org.springframework.web.client.RestTemplate; import java.util.*; @SpringBootApplication @RestController public class DemoApplication { private final RestTemplate restTemplate = new RestTemplate(); public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @GetMapping("/") public String hello() { return "Hello World!"; } @GetMapping("/myip") public ResponseEntity getIpInfo() { try { // 设置 CORS 头,允许跨域请求 HttpHeaders headers = new HttpHeaders(); headers.set("Access-Control-Allow-Origin", "*"); headers.setContentType(MediaType.APPLICATION_JSON); // 使用 RestTemplate 获取远程数据(这里使用 ipinfo.io 作为示例) String response = restTemplate.getForObject("https://ipinfo.io", String.class); return new ResponseEntity<>(response, headers, HttpStatus.OK); } catch (Exception error) { System.err.println("获取 IP 信息失败: " + error.getMessage()); Map errorResponse = new HashMap<>(); errorResponse.put("error", "Failed to fetch remote data"); errorResponse.put("message", error.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); } } @GetMapping("/health") public ResponseEntity> health() { // 健康检查接口 Map health = new HashMap<>(); health.put("status", "healthy"); health.put("timestamp", new Date().toInstant().toString()); health.put("version", "1.0.0"); return ResponseEntity.ok() .header("Content-Type", "application/json") .body(health); } @RequestMapping(value = "/{path:[^\\.]*}") public ResponseEntity> notFound() { // 错误处理:对于未知路径返回 404 错误 Map error = new HashMap<>(); error.put("error", "Not Found"); return ResponseEntity.status(HttpStatus.NOT_FOUND) .header("Content-Type", "application/json") .body(error); } } ``` 创建配置文件 `src/main/resources/application.yml`: ```yaml # 服务器配置 # CloudBase HTTP 云函数的默认端口必须是 9000 server: port: 9000 address: 0.0.0.0 # 应用配置 spring: application: name: helloworld-java ``` 此代码创建了一个基本的 Web 服务器,提供以下功能: - **根路径** (`/`):返回 "Hello World!" 消息 - **IP 查询** (`/myip`):获取并返回客户端 IP 信息 - **健康检查** (`/health`):返回服务状态信息 - **错误处理**:对于未知路径返回 404 错误 ## 第五步:构建应用 使用 Maven 构建应用: ```bash mvn clean package ``` 构建成功后,会在 `target/` 目录下生成 `helloworld-java-1.0.0.jar` 文件。 ## 第六步: 创建启动脚本 > 💡 **注意**: > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 ```nano scf_bootstrap``` 或者 ```vim scf_bootstrap``` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 创建 `scf_bootstrap` 文件(无扩展名),这是 CloudBase 云函数的启动脚本: ```bash #!/bin/bash java -jar target/helloworld-java-1.0.0.jar ``` > ⚠️ **注意**: > - 文件名必须是 `scf_bootstrap`,没有扩展名 > - 确保文件具有执行权限 为启动脚本添加执行权限: ```bash chmod +x scf_bootstrap ``` ## 第七步:本地测试(可选) 在部署之前,您可以在本地测试应用: ```bash java -jar target/helloworld-java-1.0.0.jar ``` 测试成功后,您可以通过以下方式验证: - 访问 `http://localhost:9000/` 查看 Hello World 消息 - 访问 `http://localhost:9000/myip` 查看 IP 信息 - 访问 `http://localhost:9000/health` 查看健康状态 按 `Ctrl + C` 停止本地服务器。 ## 第八步:部署到 CloudBase HTTP 云函数 ### 准备部署文件 确保您的项目目录包含以下标准的 Spring Boot 项目结构: ``` helloworld-java/ ├── scf_bootstrap ├── pom.xml ├── src/ │ └── main/ │ ├── java/ │ │ └── com/example/demo/DemoApplication.java │ └── resources/ │ └── application.yml └── target/ └── helloworld-java-1.0.0.jar ``` ### 通过控制台部署 1. 登录 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf?tab=function) 2. 点击「新建云函数」 3. 选择「HTTP 云函数」 4. 填写函数名称(如:`helloworld-java`) 5. 选择运行时:**Java 8** 6. 选择「本地上传」方式 7. 将项目文件打包为 zip 文件并上传(请选择文件进行打包,不要打包根目录文件夹) 8. 点击「确定」完成部署 ### 通过 CLI 部署 详情请参考 [CLI 部署 HTTP 函数](/cli-v1/functions/deploy#http-函数) ### 打包项目 如果需要手动打包,可以使用以下命令: ```bash # 创建 zip 包(包含 Maven 项目结构和构建产物) zip -r helloworld-java.zip pom.xml target/helloworld-java-1.0.0.jar scf_bootstrap ``` ## 第九步:访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问```HTTP 云函数```。 您可以通过以下方式测试: - 访问根路径查看 Hello World 消息 - 访问 `/myip` 路径查看 IP 信息 - 访问 `/health` 路径查看服务状态 ## 常见问题 ### Q: 为什么必须使用 9000 端口? A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。在 Spring Boot 中可通过 `application.yml` 的 `server.port=9000` 配置。 ### Q: 如何查看函数日志? A: 在 CloudBase 控制台的云函数页面,点击函数名称进入详情页,可以查看运行日志。 ### Q: 支持哪些 Java 版本? A: CloudBase 支持 Java 8、Java 11 等版本,建议使用 Java 8 以获得最佳兼容性。 ### Q: 如何处理 CORS 跨域问题? A: 在响应头中设置 `Access-Control-Allow-Origin` 等 CORS 相关头部,如示例代码所示。 ### Q: 函数冷启动时间较长怎么办? A: 可以通过减少 JAR 包大小、优化 Spring Boot 配置等方式来减少冷启动时间。 ### Q: 如何修改应用程序端口? A: 在 `application.yml` 文件中修改 `server.port` 配置项,但 CloudBase HTTP 云函数必须使用 9000 端口。 ### Q: 为什么使用 Spring Boot 而不是原生 Java HTTP 服务器? A: Spring Boot 提供了更丰富的功能、更好的开发体验和更强的生态系统支持,如依赖注入、自动配置、监控等,适合构建生产级应用。 ## 最佳实践 1. **错误处理**:使用 Spring Boot 的全局异常处理机制,通过 `@ControllerAdvice` 注解统一处理异常 2. **响应头设置**:正确设置 Content-Type 和其他必要的响应头,可利用 Spring 的 ResponseEntity 灵活控制 3. **日志记录**:使用 Spring Boot 内置的日志框架(如 SLF4J + Logback)记录关键信息,便于调试和监控 4. **代码结构**:利用 Spring Boot 的注解简化开发,如 `@RestController`、`@GetMapping`、`@PostMapping` 等 5. **依赖管理**:使用 Maven 管理项目依赖,保持依赖版本的一致性 6. **配置管理**:将配置信息放在 `application.yml` 或 `application.properties` 中,支持不同环境的配置 7. **性能优化**:使用 Spring Boot 的懒加载、排除自动配置等特性优化启动时间 ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/function-configuration/config) - 查看 [Spring Boot 框架集成](/cloud-function/frameworks-examples/springboot) --- # 云函数/快速开始/HTTP 云函数/Functions Framework 快速开始 > 当前文档链接: https://docs.cloudbase.net/cloud-function/quickstart/httpfunc/func-framework 本文介绍使用 `@cloudbase/functions-framework` 框架进行开发,它提供了更简洁的函数式编程体验。 ## 前置条件 在开始之前,请确保您已经: - 安装了 [Node.js](https://nodejs.org/) (推荐 v18 或更高版本) - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Node.js 开发知识 ## 第一步:创建项目目录 创建名为 `my-web-function` 的新目录,并进入该目录: ```bash mkdir my-web-function cd my-web-function ``` ## 第二步:初始化项目配置 `package.json` 文件: ```json { "name": "my-web-function", "version": "1.0.0", "main": "index.js", "dependencies": { "@cloudbase/functions-framework": "^1.14.0" } } ``` ## 第三步:编写示例代码 `index.js` 文件: ```javascript exports.main = async function (event, context) { const { httpContext } = context; const { url, httpMethod } = httpContext; const path = new URL(url).pathname; // 处理不同的 HTTP 方法 switch (httpMethod) { case "GET": return { statusCode: 200, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: "Hello from Web Function", path, timestamp: new Date().toISOString(), }), }; case "POST": return { statusCode: 201, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: "Data received", data: JSON.parse(body || "{}"), }), }; default: return { statusCode: 405, body: JSON.stringify({ error: "Method not allowed" }), }; } }; ``` ## 第四步: 编写启动脚本 创建 `scf_bootstrap` 文件: ```bash #!/bin/bash node node_modules/@cloudbase/functions-framework/bin/tcb-ff.js --port=9000 --logDirname=/tmp/logs --interceptOutput=false --extendedContextKey=X-Cloudbase-Context ``` :::tip 注意 `scf_bootstrap` 文件需要具有可执行权限,在 Linux/macOS 系统中使用 `chmod +x scf_bootstrap` 命令设置权限。 ::: ## 第五步:部署到 CloudBase HTTP 云函数 ### 准备部署文件 确保您的项目目录包含以下文件: ``` my-web-function/ |-- scf_bootstrap ├── package.json └── index.js ``` ### 通过控制台部署 1. 登录 [CloudBase 控制台](https://tcb.cloud.tencent.com/dev) 2. 选择您的环境,进入「云函数」页面 3. 点击「新建云函数」 4. 选择「HTTP 云函数」 5. 填写函数名称(如:`helloworld`) 6. 选择运行时:**Node.js 18.x** 7. 选择「本地上传」方式 8. 将项目文件打包为 zip 文件并上传 9. 点击「确定」完成部署 ### 通过 CLI 部署 详情请参考 [CLI 部署 HTTP 函数](/cli-v1/functions/deploy#http-函数) ### 打包项目 如果需要手动打包,可以使用以下命令: ```bash # 创建 zip 包 zip -r helloworld.zip package.json index.js ``` ## 第七步:访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问`HTTP 云函数`。 ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/develop/introduce) - 学习如何 [添加数据库操作](/api-reference/server/node-sdk/mysql/fetch) --- # 云函数/基础配置/基础配置 > 当前文档链接: https://docs.cloudbase.net/cloud-function/function-configuration/config 前往 [云函数](https://tcb.cloud.tencent.com/dev?#/scf?tab=function) 详情页,可对云函数进行基础配置,包括内存配置、超时时间、网络配置等。 ## 内存配置 根据指定的内存分配函数运行时可用的计算资源,CPU按跟随内存的大小,按比例自动分配。 * 说明: 云函数运行时的最大内存限制 * 默认值: 256MB * 可选范围: 64MB - 3072MB ## 超时时间 限制调用时函数代码的最大运行时间。超过该时间仍未运行结束时,执行代码将被强制中断。可设置范围为 1~900 秒。 * 说明: 函数最大运行时间,超时将被强制中断 * 默认值: 3秒 * 可选范围: 1秒 - 900秒 ## 网络配置 ### 公网访问 云函数默认开启公网访问能力,您可以根据安全需求选择关闭。关闭后,函数将无法访问公网资源。 ### 固定出口 IP 开启此功能后,云函数将获得一个固定的公网出口 IP,该 IP 会与同一命名空间下其他开启此功能的函数共享。 固定出口 IP :::tip 提示 只有云函数开启公网访问时,固定出口 IP 功能才能生效。 ::: ### 内网访问(VPC 配置) 默认情况下,云函数服务与您的腾讯云账号下的其它资源(如: cvm, mysql, redis 等)内网网络无法互通。如果需要访问其它资源,需要为服务开启内网互联功能。 **开通前提:** * 您需要[腾讯云私有网络](https://console.cloud.tencent.com/vpc/vpc?rid=4)创建 VPC 网段。 * 区域选择`上海区域`。 * VPC 网段`子网掩码`尽量使用 >= 22。 * 子网`子网掩码`尽量 `<=28`, 该子网将被云函数服务占用,请尽量不要在该子网中创建其它资源,以免云函数服务被影响。 :::tip 提示 * 配置 VPC 后如需外网访问能力,请确认 VPC 网络中配置了 [公网网关](https://cloud.tencent.com/document/product/215/20078) 或 [NAT 网关](https://cloud.tencent.com/document/product/215/4975) * 有关 VPC 网络配置的更多信息,请参考 [VPC 私有网络说明](https://cloud.tencent.com/document/product/215) * 关于云函数网络配置的详细说明,请参考 [网络配置详情](https://cloud.tencent.com/document/product/583/14571) ::: ## 请求多并发 :::info 适用范围 此配置仅适用于 HTTP 云函数类型。 ::: HTTP 云函数支持「请求多并发」功能,允许单个实例同时处理多个请求,可以在以下场景中带来显著收益: ### 适用场景 * **IO 密集型场景**:如 WebSocket 长连接业务,可减少计费执行时长,节省费用 * **连接池复用**:多个请求并发在同一实例中可复用数据库连接池,减缓下游服务压力 * **降低冷启动**:请求并发密集时,多个请求只需要一个实例进行处理,无需拉起多个实例,从而降低实例冷启动几率,降低响应延迟 ### 配置说明 * **默认状态**:关闭 * **并发范围**:2 - 100 并发 * **配置方式**:前往 [云开发平台/云函数/函数配置](https://tcb.cloud.tencent.com/dev#/scf) 页面开启并设置最大并发数 :::tip 提示 合理配置并发数可以有效提升函数性能和资源利用率。对于 IO 密集型业务,建议启用此功能以优化成本。 ::: ## WebSocket 协议 :::info 适用范围 此配置仅适用于 HTTP 云函数类型。 ::: HTTP 云函数支持「WebSocket 协议」,实现服务端与客户端之间的双向实时通信。开启后可配置 WebSocket 连接的超时时间。 ### 配置说明 * **默认状态**:关闭 * **超时时间范围**:10 - 7200 秒 * **配置方式**:前往 [云开发平台/云函数/函数配置](https://tcb.cloud.tencent.com/dev#/scf) 页面开启并设置超时时间 ### 代码实现 开启 WebSocket 协议后,需要在云函数代码中实现 WebSocket 处理逻辑: ```javascript // 主函数:处理 WebSocket 消息 exports.main = async function (event, context) { const { ws } = context; if (ws) { ws.on('message', (msg) => { console.log('收到消息:', msg); ws.send(`服务端回复: ${msg}`); }); ws.on('close', () => { console.log('连接已关闭'); }); } }; // 处理 WebSocket 协议升级 exports.main.handleUpgrade = async function (context) { // 可在此进行身份验证、权限检查 return { allowWebSocket: true, }; }; ``` :::tip 提示 * WebSocket 连接会长期占用资源,请根据实际业务场景合理配置超时时间 * 详细的 WebSocket 开发指南请参考 [使用 WebSocket](/cloud-function/function-calls/websocket) * 了解 HTTP 云函数的更多特性请参考 [编写 HTTP 云函数](/cloud-function/develop/how-to-writing-functions-code#websocket) ::: ## 限频配置 云函数支持配置访问限频,帮助您控制函数的调用频率,防止恶意调用和资源滥用: * **限频规则**:可以设置每秒、每分钟或每小时的最大调用次数 * **限频策略**:支持基于 IP、用户 ID 等维度进行限频控制 * **超限处理**:当调用频率超过限制时,系统会返回相应的错误信息 :::tip 提示 合理配置限频规则可以有效保护您的云函数免受恶意攻击,同时控制成本。详细配置方法请参考 [限频功能配置指南](/envconfig/ratelimit/intro)。 ::: --- # 云函数/基础配置/环境变量 > 当前文档链接: https://docs.cloudbase.net/cloud-function/function-configuration/env 在创建或编辑云函数时,您可以通过修改配置中的环境变量,为云函数的运行环境增加、删除或修改环境变量。 在配置环境变量后,环境变量将在函数运行时配置到所在的操作系统环境中,并在函数执行期间全局生效。函数代码可以使用读取系统环境变量的方式来获取到设置的具体值并在代码中使用。 ## 查看环境变量 在配置好云函数的环境变量后,可通过查看云函数的函数配置,查询到具体已配置的环境变量,环境变量以 key=value 的形式显示。 ## 使用环境变量 已配置的环境变量,会在函数运行时配置到函数所在的运行环境中,可通过代码读取系统环境变量的方式来获取到具体值并在代码中使用。需要注意的是,环境变量无法在本地进行读取。 假设针对云函数,配置的环境变量的 key 为 key,以下为各运行环境读取并打印此环境变量值的示例代码。 - 在 Node.js 运行环境中,读取环境变量的方法为: ```js var value = process.env.key; console.log(value); ``` - 在 Python 运行环境中,读取环境变量的方法为: ```py import os value = os.environ.get('key') print(value) ``` ## 使用场景 - 可变值提取:针对业务中有可能会变动的值,提取至环境变量中,可避免需要根据业务变更而修改代码。 - 加密信息外置:认证、加密相关的 key,从代码中提取至环境变量,可避免相关 key 硬编码在代码中而引起的安全风险。 - 环境区分:针对不同开发阶段所要进行的配置和数据库信息,可提取到环境变量中。针对开发和发布的不同阶段,仅需要修改环境变量的值,分别执行开发环境数据库和发布环境数据库即可。 ## 使用限制 针对云函数的环境变量,有如下使用限制: - key 必须以字母 [a-zA-Z] 开头,只能包含字母数字字符和下划线( [a-zA-Z0-9_])。 - 预留的环境变量 key 无法配置。预留的 key 包括: - SCF\_ 开头的 key,例如 SCF_RUNTIME。 - QCLOUD\_ 开头的 key,例如 QCLOUD_APPID。 - TENCENTCLOUD\_ 开头的 key,例如 TENCENTCLOUD_SECRETID。 ## 已内置环境变量 | 环境变量 Key | 具体值或值来源 | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | TENCENTCLOUD_SESSIONTOKEN | \{临时 SESSION TOKEN\} | | TENCENTCLOUD_SECRETID | \{临时 SECRET ID\} | | TENCENTCLOUD_SECRETKEY | \{临时 SECRET KEY\} | | \_SCF_SERVER_PORT | 28902 | | TENCENTCLOUD_RUNENV | SCF | | USER_CODE_ROOT | /var/user/ | | TRIGGER_SRC | timer(使用定时触发器时) | | PYTHONDONTWRITEBYTECODE | x | | PYTHONPATH | /var/user:/opt | | CLASSPATH | /var/runtime/java x:/var/runtime/java x/lib/\*:/opt(x 为 8 或 11) | | NODE_PATH | /var/user:/var/user/node_modules:/var/lang/node x/lib/node_modules:/opt:/opt/node_modules(x 为 16、14、12、10、8 或 6) | | PHP_INI_SCAN_DIR | /var/user/php_extension:/opt/php_extension | | \_ | /var/lang/python3/bin/python x(x 为 37、3 或 2) | | PWD | /var/user | | LOGNAME | qcloud | | LANG | en_US.UTF8 | | LC_ALL | en_US.UTF8 | | USER | qcloud(事件函数在 Node.js 16.13环境下有该内置变量,`HTTP 云函数`则无该变量) | | HOME | /home/qcloud | | PATH | /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin | | SHELL | /bin/bash | | SHLVL | 3 | | LD_LIBRARY_PATH | /var/runtime/java x:/var/user:/opt(x 为 8 或 11) | | HOSTNAME | \{hostid\} | | SCF_RUNTIME | 函数运行时 | | SCF_FUNCTIONNAME | 函数名 | | SCF_FUNCTIONVERSION | 函数版本 | | TENCENTCLOUD_REGION | 区域 | | TENCENTCLOUD_APPID | 账号 APPID | | TENCENTCLOUD_UIN | 账号 UIN | | TENCENTCLOUD_TZ | 时区,当前为 UTC | :::tip 提示 获取临时密钥信息的代码,需放在 main 函数中触发。详情请参见: [获取运行角色临时密钥信息](https://cloud.tencent.com/document/product/583/47933#.E8.8E.B7.E5.8F.96.E8.BF.90.E8.A1.8C.E8.A7.92.E8.89.B2.E4.B8.B4.E6.97.B6.E5.AF.86.E9.92.A5.E4.BF.A1.E6.81.AF) ::: --- # 云函数/云函数管理/编写普通云函数 > 当前文档链接: https://docs.cloudbase.net/cloud-function/how-coding ## 基础代码示例 以下是一个简单的 Node.js 云函数示例,展示如何处理入参并返回结果: ```js // index.js - 云函数入口文件 exports.main = async (event, context) => { // 1. 解析云函数入参 const { a, b } = event; // 2. 执行业务逻辑 const sum = a + b; // 3. 返回结果 return { sum, timestamp: Date.now(), requestId: context.requestId, }; }; ``` ### 异步处理实践 由于实例的管理由平台自动处理,推荐云函数采用 async/await 模式,避免使用 Promise 链式调用: ```js exports.main = async (event, context) => { // ❌ 不推荐:Promise 链式调用 getList().then((res) => { // do something... }); // ✅ 推荐:使用 async/await const res = await getList(); // do something... }; ``` ## 函数入参详解 每个云函数调用都会收到两个重要对象:`event` 和 `context`。 ### event 对象 `event` 对象包含**触发云函数的事件数据**,其内容根据触发方式不同而变化: - **小程序调用**:包含小程序端传入的参数 - **HTTP 请求调用**:包含 [HTTP 请求信息](/service/access-cloud-function)(如请求头、请求体等) - **定时触发**:包含定时触发的相关信息 ### context 对象 `context` 对象提供**调用上下文信息**,帮助您了解函数的运行环境和调用方式: - **请求 ID**:当前调用的唯一标识符 - **调用来源**:触发函数的服务或客户端信息 - **执行环境**:函数的运行时信息 - **用户身份**:调用方的身份信息(如有) ## 函数返回值 云函数支持两种响应方式:**简单响应**和**集成响应**。系统会根据返回值的格式自动识别响应类型。 ### 简单响应 直接返回数据,系统自动生成标准的 HTTP 响应。 ```javascript exports.main = async () => { return "Hello CloudBase"; }; ``` **HTTP 响应**: ```http HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Hello CloudBase ``` ```javascript exports.main = async () => { return { success: true, data: { id: 123, name: "CloudBase" }, timestamp: Date.now() }; }; ``` **HTTP 响应**: ```http HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 {"success":true,"data":{"id":123,"name":"CloudBase"},"timestamp":1699999999999} ``` ### 集成响应(高级) 当需要精确控制 HTTP 状态码、响应头等信息时,使用集成响应格式: ```javascript { statusCode: number, // HTTP 状态码(必填) headers: { // HTTP 响应头(可选) "headerName": "headerValue" }, body: string, // 响应体内容(可选) isBase64Encoded: boolean // body 是否为 Base64 编码(可选) } ``` > 💡 **识别规则**:返回值包含 `statusCode` 字段时,系统会识别为集成响应。 ### 集成响应示例 ```javascript exports.main = async () => { return { statusCode: 200, headers: { "Content-Type": "text/html; charset=utf-8" }, body: ` CloudBase

欢迎使用云开发

这是通过云函数返回的 HTML 页面

` }; }; ``` 浏览器访问时会直接渲染该 HTML 页面。
```javascript exports.main = async (event) => { const { path } = event; return { statusCode: 302, headers: { "Location": `https://docs.cloudbase.net${path}` } }; }; ``` 访问云函数时会自动跳转到指定 URL。 ```javascript const fs = require('fs'); const path = require('path'); exports.main = async () => { // 读取图片文件并转换为 Base64 const imagePath = path.join(__dirname, 'image.png'); const imageBuffer = fs.readFileSync(imagePath); const base64Image = imageBuffer.toString('base64'); return { statusCode: 200, headers: { "Content-Type": "image/png" }, body: base64Image, isBase64Encoded: true }; }; ``` > ⚠️ **注意**:二进制文件(图片、音视频等)必须设置 `isBase64Encoded: true`。 ```javascript exports.main = async (event) => { const { queryStringParameters } = event; // 参数验证 if (!queryStringParameters?.userId) { return { statusCode: 400, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: "Bad Request", message: "userId 参数不能为空" }) }; } // 业务逻辑 const userId = queryStringParameters.userId; try { // ... 处理业务逻辑 return { statusCode: 200, body: JSON.stringify({ success: true }) }; } catch (error) { return { statusCode: 500, body: JSON.stringify({ error: "Internal Server Error", message: error.message }) }; } }; ``` ```javascript exports.main = async () => { return { statusCode: 200, headers: { "Content-Type": "application/javascript" }, body: ` console.log("Hello from CloudBase!"); window.cloudbaseConfig = { envId: "your-env-id", region: "ap-shanghai" }; ` }; }; ``` 可用于动态生成 JavaScript 配置文件。
## 安装第三方依赖 当云函数需要使用第三方 npm 包时,需要先安装相应的依赖包。CloudBase 在线编辑器提供了便捷的依赖管理功能。 ### 打开终端 在 CloudBase 在线编辑器中,您可以通过以下方式打开终端: - **快捷键**:使用 `Ctrl + J(Windows/Linux)` 或 `command + J(macOS)` - **菜单操作**:点击编辑器上方的「终端」按钮,选择「新建终端」 ### 安装依赖 在终端中使用 `npm add` 命令安装所需的依赖包。 以安装 CloudBase Node.js SDK 为例: ```bash npm add @cloudbase/node-sdk ``` 安装其他常用依赖包: ```bash # 时区处理库 npm add moment-timezone # HTTP 请求库 npm add axios # 工具库 npm add lodash ``` ### 使用依赖 安装完成后,您可以在代码中引用这些依赖: 在云函数 Node.js 环境中无法直接采用 ES Module 规范编写代码,主要原因在于,云函数默认支持的入口文件(index.js)必须遵循 CommonJS 规范,若需要使用 ES Module 规范请参考 [使用-es-module-规范](#使用-es-module-规范) ```js const cloudbase = require('@cloudbase/node-sdk'); exports.main = async (event, context) => { // 初始化 CloudBase const app = cloudbase.init({ env: 'your-envid', // 替换为您的环境 ID }); const db = app.database(); const collection = db.collection('users'); // 查询数据 const { data } = await collection.get(); return { success: true, count: data.length, }; }; ``` ### 上传和部署 完成代码编写和依赖安装后,根据项目情况选择相应的上传方式: - **有第三方依赖**:点击「**保存并安装依赖**」按钮 - 系统会自动上传代码文件和 `package.json` - 在云端环境中自动执行 `npm install` 安装依赖 - 确保云函数运行时能够正确加载所有依赖包 - **无第三方依赖**:点击「**保存**」按钮 - 仅上传代码文件 - 适用于未使用第三方依赖的云函数 :::tip 依赖管理提示 - 避免安装过多不必要的依赖,以减少函数包的大小和启动时间 ::: ## 环境变量使用 云函数可以通过 `process.env` 获取环境变量,这是管理配置信息的实践: ### 获取环境变量 ```js exports.main = async (event, context) => { // 获取环境变量 const dbUrl = process.env.DATABASE_URL; const apiKey = process.env.API_KEY; const nodeEnv = process.env.NODE_ENV || 'development'; // 使用环境变量进行配置 const config = { database: dbUrl, apiKey: apiKey, debug: nodeEnv === 'development', }; return { message: '环境变量获取成功', environment: nodeEnv, }; }; ``` ### 环境变量实践 ```js exports.main = async (event, context) => { // 检查必需的环境变量 const requiredEnvVars = ['DATABASE_URL', 'API_KEY']; const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]); if (missingVars.length > 0) { throw new Error(`缺少必需的环境变量: ${missingVars.join(', ')}`); } // 安全地使用环境变量 const config = { dbUrl: process.env.DATABASE_URL, apiKey: process.env.API_KEY, timeout: parseInt(process.env.TIMEOUT) || 5000, }; return { success: true, config }; }; ``` :::tip 注意 - 敏感信息(如 API 密钥、数据库连接字符串)应通过环境变量传递,不要硬编码在代码中 - 环境变量值始终是字符串类型,需要时请进行类型转换 - 建议为环境变量设置默认值,提高代码的健壮性 ::: ## 时区设置 云函数的运行环境内保持的是 UTC 时间,即 0 时区时间,和北京时间有 8 小时的时间差。 可以通过语言的时间处理相关库或代码包(如 moment-timezone),识别 UTC 时间并转换为+8 区北京时间。 ### 时区处理示例 ```javascript const moment = require('moment-timezone'); // 需在 package.json 中指定并安装依赖 exports.main = async (event, context) => { // javascript date console.log(new Date()); // 2021-03-16T08:04:07.441Z (UTC+0) console.log(moment().tz('Asia/Shanghai').format()); // 2021-03-16T16:04:07+08:00 (UTC+8) // 获取当前北京时间 const beijingTime = moment().tz('Asia/Shanghai'); return { utcTime: new Date().toISOString(), beijingTime: beijingTime.format(), timestamp: beijingTime.valueOf(), }; }; ``` ### 时区处理实践 ```javascript const moment = require('moment-timezone'); exports.main = async (event, context) => { // 统一时区处理函数 const getBeijingTime = (date = new Date()) => { return moment(date).tz('Asia/Shanghai'); }; // 格式化时间输出 const formatTime = (date, format = 'YYYY-MM-DD HH:mm:ss') => { return getBeijingTime(date).format(format); }; // 业务逻辑中使用 const currentTime = getBeijingTime(); const formattedTime = formatTime(); console.log('当前北京时间:', formattedTime); return { success: true, currentTime: formattedTime, timestamp: currentTime.valueOf(), }; }; ``` ## 使用 ES Module 规范 在云函数 Node.js 环境中无法直接采用 ES Module 规范编写代码,主要原因在于,云函数默认支持的入口文件(`index.js`)必须遵循 CommonJS 规范,并且文件名必须为 **「index.js」**。然而,Node.js 对于符合 ES Module 规范的模块文件要求其扩展名为 `.mjs`。 在云函数中使用 ES Module 需要创建三个核心文件,形成完整的调用链路:`index.js` → `entry.mjs` → `util.mjs` ### 项目结构 ``` cloud-function/ ├── index.js # 云函数入口文件(CommonJS) ├── entry.mjs # ES Module 入口文件 └── src └── util.mjs # 业务逻辑模块,命名可自定义 ``` ### 1. 创建云函数入口文件 index.js ```js // index.js - 云函数入口文件 exports.main = async (event, context) => { try { // 动态导入 ES Module 入口文件 const { entry } = await import('./entry.mjs'); return await entry(event, context); } catch (error) { console.error('云函数执行失败:', error); return { success: false, error: error.message, requestId: context.request_id }; } }; ``` ### 2. 创建 ES Module 入口文件 entry.mjs ```js // entry.mjs - ES Module 入口文件 import { getUserList } from './src/util.mjs'; /** * ES Module 入口函数 * @param {Object} event - 事件对象 * @param {Object} context - 上下文对象 * @returns {Promise} 处理结果 */ export const entry = async (event, context) => { return getUserList(event, context); }; ``` ### 3. 创建业务逻辑模块 util.mjs ```js // src/util.mjs - 业务逻辑模块 import cloudbase from '@cloudbase/node-sdk'; const app = cloudbase.init({ env: 'your-envid', // 替换为您的环境 ID }); const models = app.models; export const getUserList = async (event) => { const res = await models.user.list({}); return { success: true, data: res, }; }; ``` > 💡 注意: ES Module 文件必须使用 `.mjs` 扩展名,这样 Node.js 才能正确识别并处理 ES Module 语法。 ## 错误处理与日志记录 ### 错误处理实践 ```js exports.main = async (event, context) => { try { // 参数验证 if (!event.userId) { throw new Error('缺少必需参数: userId'); } // 业务逻辑处理 const result = await processUserData(event.userId); return { success: true, data: result, }; } catch (error) { // 记录错误日志 console.error('函数执行失败:', { error: error.message, stack: error.stack, event, requestId: context.requestId, }); // 返回友好的错误信息 return { success: false, error: error.message, requestId: context.requestId, }; } }; ``` ## 性能优化建议 ### 执行时间优化 ```js exports.main = async (event, context) => { const startTime = Date.now(); try { // 使用并行处理提升性能 const promises = event.items.map((item) => processItem(item)); const results = await Promise.all(promises); const duration = Date.now() - startTime; console.log(`函数执行耗时: ${duration}ms`); return { success: true, data: results, duration, }; } catch (error) { console.error('执行错误:', error); throw error; } }; ``` ### 内存使用优化 ```js exports.main = async (event, context) => { // 分批处理大数据,避免内存溢出 const batchSize = parseInt(process.env.BATCH_SIZE) || 100; const results = []; for (let i = 0; i < event.data.length; i += batchSize) { const batch = event.data.slice(i, i + batchSize); const batchResult = await processBatch(batch); results.push(...batchResult); // 及时清理不需要的变量 batch.length = 0; // 记录处理进度 console.log(`已处理 ${Math.min(i + batchSize, event.data.length)}/${event.data.length} 条数据`); } return results; }; ``` --- # 云函数/云函数管理/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cloud-function/how-use --- title: 调用普通云函数 description: 学习如何通过多种方式调用 CloudBase 云函数,包括小程序 SDK、Web SDK、Node.js SDK、HTTP API 和 HTTP 访问服务 keywords: [云函数调用, SDK, HTTP API, 小程序, Web, Node.js] --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # 调用普通云函数 云开发提供了多种 SDK、API 供开发者调用云函数,包括小程序 SDK、JS SDK、Node SDK、HTTP API 等,满足不同场景和平台的需求。 ## 调用方式概览 | 调用方式 | 适用场景 | 特点 | | ----------------- | -------------------------- | -------------------------------- | | **小程序 SDK** | 微信小程序 | 原生支持,自动携带用户身份 | | **Web SDK** | 浏览器环境、前端应用 | 简单易用,自动处理认证 | | **Node.js SDK** | 服务端、云函数互调 | 支持服务端调用,功能完整 | | **HTTP API** | 跨语言调用、第三方系统集成 | 标准 REST API,支持所有语言 | | **HTTP 访问服务** | 前端直接访问、自定义域名 | 支持自定义路径和域名,可跨域访问 | ## 调用示例 微信小程序可以直接使用 `wx.cloud.callFunction` API 调用云函数,无需额外配置。 ```javascript // 基础调用方式 wx.cloud.callFunction({ name: 'hello-world', data: { name: 'CloudBase', message: 'Hello from MiniProgram' }, success: res => { console.log('调用成功:', res.result); }, fail: err => { console.error('调用失败:', err); } }); ``` > 💡 **提示**:小程序调用云函数时会自动携带用户的 OPENID,可在云函数中通过 `event.userInfo.openId` 获取。 **相关文档** - [小程序云开发官方文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/guide/functions/getting-started.html) 通过 CloudBase Web SDK 调用云函数,适用于浏览器环境和前端应用。 - [Web SDK API 文档](/api-reference/webv2/functions) - [Web SDK 身份认证](/auth/web/getting-started) ```javascript import tcb from '@cloudbase/js-sdk'; // 初始化 const app = tcb.init({ env: 'your-env-id' }); // 匿名登录(如果需要) await app.auth().signInAnonymously(); async function callFunction() { try { const result = await app.callFunction({ name: 'hello-world', data: { name: 'CloudBase', message: 'Hello from Web' } }); console.log('调用成功:', result); return result.result; } catch (error) { console.error('调用失败:', error); throw error; } } // 使用示例 callFunction().then(result => { console.log('函数返回:', result); }); ``` 通过 CloudBase Node.js SDK 调用云函数,适用于服务端和云函数互调场景。 [Node.js SDK API 文档](/api-reference/server/node-sdk/functions) ```javascript const tcb = require('@cloudbase/node-sdk'); // 初始化 const app = tcb.init({ env: 'your-env-id', secretId: 'your-secret-id', // 可选,用于服务端调用 secretKey: 'your-secret-key' // 可选,用于服务端调用 }); async function callFunction() { try { const result = await app.callFunction({ name: 'hello-world', data: { name: 'CloudBase', message: 'Hello from Node.js' } }); console.log('调用成功:', result); return result.result; } catch (error) { console.error('调用失败:', error); throw error; } } ``` 通过 HTTP API 调用云函数,支持跨语言访问,适合第三方系统集成。 - [HTTP API 完整文档](/http-api/functions/functions-post) - [AccessToken 获取方法](/http-api/basic/access-token) ```bash # 基础调用 curl -L 'https://your-env-id.api.tcloudbasegateway.com/v1/functions/hello-world' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer your-access-token' \ -H 'Content-Type: application/json' \ -d '{ "message": "Hello CloudBase", "timestamp": 1640995200000 }' # HTTP 云函数调用 curl -L 'https://your-env-id.api.tcloudbasegateway.com/v1/functions/web-function?webfn=true' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer your-access-token' \ -H 'Content-Type: application/json' \ -d '{ "path": "/api/users", "method": "GET" }' ``` 通过 HTTP 访问服务调用云函数,支持自定义域名和路径,适合前端直接访问。 - [HTTP 访问服务配置文档](/service/introduce) - [HTTP 访问云函数](/service/access-cloud-function) ## 相关文档 - [HTTP API 认证](/http-api/basic/access-token) - 了解如何获取和使用访问令牌 - [HTTP 访问服务](/service/introduce) - 配置自定义域名和访问路径 - [云函数开发指南](/cloud-function/develop/introduce) - 学习如何编写云函数代码 - [云函数最佳实践](/cloud-function/best-practices) - 了解云函数开发的最佳实践 --- # 云函数/云函数管理/控制台部署云函数 > 当前文档链接: https://docs.cloudbase.net/cloud-function/manage ## 在线部署 您可以直接在云开发控制台编辑并部署云函数代码,如果没有创建过云函数,可以创建一个空白模版云函数 1. 在 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev#/scf?tab=function) 选择目标函数 2. 在代码编辑器中修改代码 3. 点击「保存并安装依赖」完成部署 ![在线编辑函数代码](https://qcloudimg.tencent-cloud.cn/raw/81a6898ac5cfc68c9e64a3f1973e8999.png) ### 安装依赖 :::tip 提示 1. 目前仅针对 Node.js 提供在线安装依赖功能。 2. 出于业务安全性考虑,已禁用安装依赖时的自动执行脚本功能。若安装的依赖包 package.json 文件中包含 preinstall、install 或 postinstall 等脚本命令,安装时将会报错。建议您通过层绑定或上传 zip 包的方式进行依赖管理。 ::: 点击「保存并安装依赖」,云函数后台会检查代码包根目录下的 `package.json` 文件,并根据其中的依赖项,尝试使用 `npm install` 安装依赖 ## 上传代码包部署 对于较复杂的项目,您可以将代码打包后上传: 1. 将函数代码及依赖打包成 ZIP 文件 2. 在云函数详情页面,选择「上传 ZIP 包」 3. 选择本地 ZIP 文件并上传 4. 点击「保存并安装依赖」完成部署 :::tip 提示 ZIP 包的根目录应该包含入口文件,例如 Node.js 环境下的 `index.js` 。 ::: --- # 云函数/云函数管理/部署云函数 > 当前文档链接: https://docs.cloudbase.net/cli-v1/functions/deploy 使用 `tcb fn deploy` 命令可以快速部署云函数到云开发环境。 ```bash tcb fn deploy [options] [name] ``` ## 命令参数 | 参数 | 说明 | 必填 | | ---------------------------- | ------------------------------------------------------------------------ | ---- | | `-e, --env-id ` | 环境 Id | 否 | | `--httpFn` | 部署为 HTTP 云函数 | 否 | | `--ws` | 部署 HTTP 云函数时启用 WebSocket 协议 | 否 | | `--code-secret ` | 传入此参数将保护代码,格式为 36 位大小写字母和数字 | 否 | | `--force` | 如果存在同名函数,上传后覆盖同名函数 | 否 | | `--path ` | 自动创建 HTTP 访问服务访问路径 | 否 | | `--dir ` | 指定云函数的文件夹路径 | 否 | | `--all` | 部署配置文件中包含的全部云函数 | 否 | | `--deployMode ` | 上传模式:`cos`(默认推荐)、`zip`(不推荐,限 1.5 MB)、`image`(镜像) | 否 | | `--yes` | 跳过交互确认,直接执行部署 | 否 | | `-h, --help` | 查看命令帮助信息 | 否 | ## 基本用法 ### 方式一:使用配置文件(推荐) 在包含 `cloudbaserc.json` 配置文件的项目目录下执行: ```bash # 部署指定函数 tcb fn deploy # 部署配置文件中的所有函数 tcb fn deploy --all ``` ### 方式二:从当前目录部署 在函数代码目录下(包含 `package.json`),无需配置文件即可直接部署: ```bash cd my-function tcb fn deploy ``` CLI 会自动从 `package.json` 读取函数名称,并使用默认配置部署。 ### 部署 HTTP 函数 添加 `--httpFn` 参数即可部署为 HTTP 函数: ```bash tcb fn deploy --httpFn ``` ## 使用示例 ### 标准工作空间部署 ``` project/ ├── cloudbaserc.json └── functions/ └── hello/ ├── index.js └── package.json ``` ```bash tcb fn deploy hello ``` ### 自定义函数目录 ```json // cloudbaserc.json { "envId": "your-env-id", "functionRoot": "./functions", "functions": [ { "name": "api", "dir": "./src/cloud-functions/api", "runtime": "Nodejs20.19" } ] } ``` ```bash tcb fn deploy api ``` ### 命令行指定目录 ```bash tcb fn deploy hello --dir ./my-custom-path/hello ``` ## 部署路径解析 部署时,CLI 按以下优先级解析函数代码路径: ``` 1. --dir 参数(命令行指定) → 最高优先级 2. functions[].dir(配置文件) → 次优先级 3. functionRoot + 函数名 → 默认路径(functionRoot 默认为 ./functions) 4. 当前工作目录 → 无配置文件时的兜底 ``` | 场景 | 配置文件 | --dir | functions.dir | 最终路径 | | ---------- | -------- | ------- | ------------- | --------------------- | | 标准部署 | ✅ 存在 | ❌ | ❌ | `functionRoot/函数名` | | 自定义目录 | ✅ 存在 | ❌ | ✅ 指定 | `functions.dir` 路径 | | 命令行覆盖 | ✅ 存在 | ✅ 指定 | - | `--dir` 路径 | | 无配置文件 | ❌ | ❌ | - | `当前工作目录` | :::caution 注意 批量部署时(使用 `--all` 或不指定函数名),`--dir` 参数无效,会被忽略。 ::: ## 高级选项 ### 代码加密 通过 `--code-secret` 参数对代码进行加密,密钥需要使用 36 位大小写字母和数字组成: ```bash tcb fn deploy app --code-secret 7sGLwMnhgEfKmkqg2dMjB6xWk2hCxsAgGR6w ``` :::warning 注意 启用代码加密后,将无法在小程序 IDE、腾讯云控制台中查看云函数的代码和信息。 ::: ### 覆盖同名函数 如果云端已存在同名函数,CLI 会提示是否覆盖。如需强制覆盖,可使用 `--force` 参数: ```bash tcb fn deploy app --force ``` :::caution 重要提示 使用 `--force` 参数覆盖函数时,会同时覆盖函数的配置和触发器。 ::: ### 跳过交互确认 在自动化脚本或 CI/CD 流程中,可以使用 `--yes` 参数跳过所有交互式确认: ```bash # 部署时跳过确认 tcb fn deploy app --yes # 批量部署跳过确认 tcb fn deploy --all --yes # 强制覆盖并跳过确认 tcb fn deploy app --force --yes ``` :::tip 自动化部署建议 在 CI/CD 流程中,建议始终添加 `--yes` 参数以避免脚本阻塞等待用户输入。 ::: ## HTTP 函数 HTTP 函数是专门针对 Web 服务场景优化的云函数类型,支持标准 HTTP 请求响应模式。 ### 部署方式 #### 方式一:使用配置文件 在 `cloudbaserc.json` 中将 `type` 设置为 `HTTP`,然后执行: ```bash tcb fn deploy webFunction ``` #### 方式二:使用命令行参数 ```bash tcb fn deploy --httpFn ``` #### 方式三:从当前目录部署 ```bash cd my-web-function tcb fn deploy --httpFn ``` ### 启动脚本 HTTP 函数需要 `scf_bootstrap` 启动脚本来启动 Web Server。如果未创建,CLI 会提示自动生成。 :::tip 详细说明 关于启动脚本的完整说明(基本要求、示例模板、运行时路径、常见错误排查),请参考 [启动脚本](/cloud-function/develop/scf-bootstrap)。 ::: ### 相关指南 - [HTTP 函数项目结构和编写指南](/cloud-function/develop/how-to-writing-functions-code) - [云函数调用方式](https://docs.cloudbase.net/cloud-function/function-calls/) ## 镜像部署 云函数支持通过容器镜像部署,适用于需要自定义运行环境、使用非标准语言或依赖复杂系统库的场景。 ### 适用场景 - 需要自定义运行时环境 - 使用 Node.js/Python 以外的语言(如 Go、Rust) - 依赖特定系统库或二进制文件 - 需要预装大量依赖包 - 已有容器化应用迁移 ### 前置条件 使用镜像部署云函数前,请确保已完成以下准备工作: 1. **授权镜像拉取**:为云函数角色授权策略 `QcloudAccessForSCFRoleInPullImage`,一次性操作:[点击授权](https://console.cloud.tencent.com/cam/role/grant?roleName=SCF_QcsRole&policyName=QcloudAccessForSCFRoleInPullImage&principal=eyJzZXJ2aWNlIjoic2NmLnFjbG91ZC5jb20ifQ%3D%3D) 2. **准备镜像仓库**:在 [腾讯云容器镜像服务 TCR](https://console.cloud.tencent.com/tcr/repository) 创建仓库;参考 [获取访问凭证](https://cloud.tencent.com/document/product/1141/41829) 和 [推送镜像](https://cloud.tencent.com/document/product/1141/41811) 3. **镜像要求**:基于 Linux 的 `amd64` 镜像,容器内监听 **9000 端口** 4. **创建日志角色**(用于日志投递): - 在 [CAM 角色控制台](https://console.cloud.tencent.com/cam/role) 创建角色 - 选择**腾讯云产品服务**,角色载体选择 `scf` 和 `cls` - 关联[自定义策略](/quick-start/integrate-cloudbase#日志角色权限与多租户隔离)(推荐采用共享角色 + 只写权限方案,防止跨环境日志越权) - 记录角色名称(如 `SCF_CLSWriteOnly`),后续在配置文件中使用 > **多租户安全提示**:单租户场景可使用预置策略 `QcloudCLSFullAccess`。如果在同一账号下管理多个租户环境,建议使用自定义策略以防止跨环境日志越权,详见[日志角色权限与多租户隔离](/quick-start/integrate-cloudbase#日志角色权限与多租户隔离)。 ### 部署流程 #### 步骤 1:构建镜像 在函数代码目录下(包含 `Dockerfile`),构建 Docker 镜像: ```bash cd /path/to/your-function # 构建镜像(必须指定 linux/amd64 平台) docker build --platform linux/amd64 -t your-function:v1 . ``` :::warning 重要 在 ARM 架构机器(如 M1/M2 Mac)上构建时,**必须**添加 `--platform linux/amd64` 参数,否则镜像无法在云函数环境运行。 ::: #### 步骤 2:推送镜像 将镜像推送到腾讯云容器镜像服务(TCR): ```bash # 标记镜像 docker tag [imageId] <访问域名>/your-namespace/your-function:[tag] # 推送镜像 docker push <访问域名>/your-namespace/your-function:[tag] ``` #### 步骤 3:配置并部署 在 `cloudbaserc.json` 中配置 `imageConfig`: ```json { "envId": "your-env-id", "functions": [ { "name": "your-function", "imageConfig": { "imageType": "personal", "imageUri": "<访问域名>/your-namespace/your-function:v1" }, "role": "SCF_CLSFullAccess", "clsLogsetId": "your-logset-id", "clsTopicId": "your-topic-id" } ] } ``` :::warning 日志配置 镜像部署的云函数需要手动配置日志投递: 1. 在 [日志服务控制台](https://console.cloud.tencent.com/cls/topic) 创建日志集和日志主题,获取 `clsLogsetId` 和 `clsTopicId` 2. 在前置条件中创建的日志角色名称填入 `role` 字段 ::: 执行部署: ```bash tcb fn deploy --deployMode image ``` :::tip 配置详情 完整的镜像配置项说明,请参考 [镜像配置](/cli-v1/functions/configs#镜像配置)。 ::: ## 配置文件 云函数的配置通过 `cloudbaserc.json` 文件管理,包括函数名称、运行时、超时时间、环境变量等。 :::tip 配置详情 完整的配置项说明和示例,请参考 [云函数配置文件](/cli-v1/functions/configs)。 ::: ## 注意事项 :::caution 函数类型不可变更 已部署的函数类型(普通函数/HTTP 函数)不支持变更。如需变更类型,请先删除原函数后重新部署。 ::: :::caution 环境变量更新规则 - **@cloudbase/cli 2.12.0 及以上版本**:部署时支持选择**增量更新**或**覆盖更新**环境变量 - **@cloudbase/cli 2.12.0 以下版本**:`cloudbaserc.json` 中的环境变量配置会**完全覆盖**线上已配置的环境变量 **重要**:如果您使用的是 2.12.0 以下版本,且在控制台中手动配置了环境变量,请确保在 `cloudbaserc.json` 中也包含这些配置,否则部署后原有环境变量将会丢失。 ::: ## 相关文档 - [HTTP 函数开发指南](/cloud-function/web-func) - [云函数配置](/cli-v1/functions/configs) - [Access Token 获取](/http-api/basic/access-token) --- # 云函数/云函数管理/测试云函数 > 当前文档链接: https://docs.cloudbase.net/cloud-function/develop/how-test 本文档详细介绍如何使用云开发提供的测试、日志查看和监控功能,帮助开发者高效调试和监控云函数运行状态。 ## 前置条件 在开始测试和监控云函数之前,请确保: - 已创建腾讯云开发环境 - 已部署至少一个云函数 - 具有相应的访问权限 ## 云函数测试 云开发提供了多种云函数测试方式,支持在线调试和本地测试,帮助开发者更加方便地调试代码。 ### 控制台测试 通过云开发控制台可以快速测试云函数的执行效果。 #### 测试步骤 1. 在控制台的对应云函数管理面板中,点击「测试」按钮,打开测试弹窗 2. 选择测试模板或自定义测试参数 3. 点击「执行」运行测试 4. 查看运行结果和日志输出 ![控制台测试界面](https://qcloudimg.tencent-cloud.cn/raw/d7a91502cc57ebf17c8323fbf24a57e0.png) #### 测试参数配置 **使用模板参数** - 点击「提交方法」下拉菜单,选择测试函数的模板方法 - 模板参数在测试时作为 `event` 参数传递给函数 **自定义参数** 在测试参数编辑器中输入 JSON 格式的测试数据: ```json { "name": "test", "data": { "key": "value", "number": 123 } } ``` #### 本地调试工具 除了可视化测试功能,还可以使用命令行工具 [scf-cli](https://github.com/TencentCloud/scf-node-debug) 进行本地调试: ```bash # 安装 scf-cli npm install -g scf-cli # 本地调试函数 scf local invoke --template template.yaml --event event.json ``` ### 微信开发者工具测试 在微信开发者工具中可以直接测试云函数。 #### 测试步骤 1. 在控制台的对应云函数管理面板中,点击「云端测试」,打开测试弹窗 2. 配置测试参数 3. 点击「执行」运行测试 4. 查看执行结果 ![微信工具测试界面](https://main.qcloudimg.com/raw/0b5740fd250161df3801d547aed42fbe.jpg) #### 参数配置说明 - **提交方法**:选择测试函数的模板方法,当前支持 `Hello World` 事件模板 - **测试参数**:在编辑器中输入 JSON 格式的测试参数 - **运行结果**:执行完毕后,结果将显示在"运行测试"栏中 #### 本地调试支持 微信开发者工具同样支持使用 [scf-cli](https://github.com/TencentCloud/scf-node-debug) 进行本地快速调试。 --- # 云函数/HTTP云函数管理/编写 HTTP 云函数 > 当前文档链接: https://docs.cloudbase.net/cloud-function/develop/how-to-writing-functions-code 「HTTP 云函数」是专为 Web 服务场景设计的云函数类型,提供了原生 HTTP 支持、实时通信能力和多函数路由等特性。 > 💡 **关于基础能力**:本文聚焦 HTTP 云函数的**特有能力**(HTTP 处理、SSE、WebSocket、函数路由等)。如需了解云函数的**通用能力**(依赖安装、环境变量、时区处理等),请参考 [编写普通云函数](/cloud-function/how-coding)。 ## 快速开始 ### 第一步:创建函数入口文件 创建 `index.js` 作为 HTTP 函数入口文件: ```js exports.main = function (event, context) { return `Hello world!`; }; ``` ### 第二步:创建启动脚本(必需) 在项目根目录创建 `scf_bootstrap` 文件(**无扩展名**),内容为启动项目的命令: ```bash #!/bin/bash node index.js ``` 启动脚本详情请参考:[启动文件说明](/cloud-function/develop/scf-bootstrap) ### 项目结构 完整的项目目录结构如下: ``` my-web-function/ ├── scf_bootstrap # 启动脚本(必需,无扩展名) ├── package.json # 项目配置 ├── index.js # 函数入口文件 └── node_modules/ # 依赖包(npm install 后生成) ``` ### 第一步:创建函数入口文件 创建 `main.py` 作为 HTTP 函数入口文件: ```python from http.server import HTTPServer, BaseHTTPRequestHandler class Handler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(b'Hello World!') if __name__ == '__main__': server = HTTPServer(('0.0.0.0', 9000), Handler) print('Server listening at http://localhost:9000') server.serve_forever() ``` ### 第二步:创建启动脚本(必需) 在项目根目录创建 `scf_bootstrap` 文件(**无扩展名**),内容为启动项目的命令: ```bash #!/bin/bash /var/lang/python3/bin/python3 main.py ``` 启动脚本详情请参考:[启动文件说明](/cloud-function/develop/scf-bootstrap) ### 项目结构 完整的项目目录结构如下: ``` python-http-function/ ├── scf_bootstrap # 启动脚本(必需,无扩展名) └── main.py # 函数入口文件 ``` **参考资源**: - 更多示例:[示例代码仓库](https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudrunfunctions) - 模板代码:[JavaScript 模板](https://github.com/TencentCloudBase/func-v2-template) | [TypeScript 模板](https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudrunfunctions/ts-multiple-functions) - 完整快速开始教程:[Functions Framework 快速开始](/cloud-function/quickstart/httpfunc/func-framework) > 💡 **提示**:HTTP 云函数支持「函数路由」功能,允许多个子函数运行在同一个实例上。详见 [HTTP 云函数路由](/cloud-function/develop/function-routing)。 ## 函数结构与参数 HTTP 云函数支持直接使用 web 相关框架来进行开发,例如:Nodejs 环境下的 Express、Koa、NestJS,或 Python 环境下的 Flask、Django 等,或其他各语言的web框架。 ## 函数路由 HTTP云函数支持web框架,因此可以通过对应的web框架进行实现多路由 也可以基于 `@cloudbase/functions-framework` 框架实现。它支持将一个大函数拆分成多个子函数,并通过请求路径将不同的请求路由到不同的处理函数。 请参考:[HTTP 云函数路由](/cloud-function/develop/function-routing) ## 实时通信能力 HTTP 云函数提供两种实时通信方式:「SSE(Server-Sent Events)」和「WebSocket」。 ### SSE(Server-Sent Events) 「SSE」是一种基于 HTTP 的服务端推送技术,支持**单向**实时数据流传输(服务端 → 客户端)。 **核心特点**: - 基于 HTTP 协议,兼容性好,**默认支持无需配置** - 客户端自动重连 - 实现简单,资源占用低 - 适合 AI 对话流式输出、实时日志、进度更新等场景 **详细文档**:完整的 SSE 使用指南、消息格式规范、常见问题解决,请参考 [SSE 协议支持](/cloud-function/develop/sse)。 ### WebSocket 「WebSocket」是一种全双工通信协议,支持**双向**实时通信(服务端 ↔ 客户端)。 **核心特点**: - 双向实时通信,持久连接,低延迟 - 需要在控制台**开启 WebSocket 协议支持** - 服务器必须监听 **9000 端口** - 适合实时聊天、协作编辑、游戏服务器等场景 **详细文档**:完整的 WebSocket 使用指南、控制台配置步骤、使用限制、常见问题解决,请参考 [WebSocket 协议支持](/cloud-function/develop/websocket)。 ### 技术选型对比 | 特性 | SSE | WebSocket | | ---------- | --------------------- | --------------------- | | 通信方式 | 单向(服务端→客户端) | 双向(服务端↔客户端) | | 协议 | HTTP | WebSocket 协议 | | 实现复杂度 | 简单 | 相对复杂 | | 配置要求 | 无需配置 | 需控制台开启 | | 自动重连 | 是 | 否(需手动实现) | | 适用场景 | 单向数据推送 | 实时双向通信 | **选择建议**: - 只需服务端推送数据(如 AI 对话、日志、进度)→ 选择 **SSE** - 需要双向实时通信(如聊天、协作、游戏)→ 选择 **WebSocket** ## 使用 TypeScript 编写函数代码 ### 类型定义安装 ```sh npm install @cloudbase/functions-typings@v-1 ``` ### 基本类型定义 ```ts import { TcbEventFunction } from '@cloudbase/functions-typings'; ``` `TcbEventFunction` 是一个泛型类型: ```ts TcbEventFunction ``` - `EventT`:定义 `event` 参数类型 - `ResultT`:定义函数返回值类型 ### 定义 event 参数类型 **JSON 请求体**: ```ts type JsonEvent = { a: number; b: string; c: boolean }; export const main: TcbEventFunction = function (event, context) { event.a; // number event.b; // string }; ``` **FormData 文件上传**: ```ts import { TcbEventFunction, File } from '@cloudbase/functions-typings'; type FormEvent = { str: string; file: File }; export const main: TcbEventFunction = function (event, context) { event.str; event.file.filepath; }; ``` ### 定义返回值类型 **普通响应**: ```ts export const main: TcbEventFunction = function (event, context) { return 'done.'; }; ``` **集成响应**: ```ts import { TcbEventFunction, IntegrationResponse } from '@cloudbase/functions-typings'; export const main: TcbEventFunction> = function (event, context) { return { statusCode: 200, headers: {}, body: 'Hello world', }; }; ``` **异步函数**: ```ts export const main: TcbEventFunction> = async function (event, context) { return new Promise((resolve) => { setImmediate(() => { resolve('done.'); }); }); }; ``` ### 完整示例 ```ts import { TcbEventFunction, File, IntegrationResponse } from '@cloudbase/functions-typings'; // GET 无请求体 export const main: TcbEventFunction = function (event, context) {}; // JSON 请求体 type JsonEvent = { a: number; b: string }; export const main: TcbEventFunction = function (event, context) { event.a; event.b; }; // FormData 上传 type FormEvent = { str: string; file: File }; export const main: TcbEventFunction = function (event, context) { event.str; event.file.filepath; }; // 二进制流 export const main: TcbEventFunction = function (event, context) { event.byteLength; }; // 访问 Context 信息 export const main: TcbEventFunction = function (event, context) { context.extendedContext?.envId; context.extendedContext?.userId; }; // 普通响应 export const main: TcbEventFunction = function (event, context) { return 'done.'; }; // 集成响应 export const main: TcbEventFunction> = function (event, context) { return { statusCode: 200, headers: {}, body: '', }; }; // 异步函数 export const main: TcbEventFunction> = async function (event, context) { return new Promise((resolve) => { setImmediate(() => { resolve('done.'); }); }); }; // SSE export const main: TcbEventFunction> = async function (event, context) { const sse = context.sse?.(); if (sse && !sse.closed) { sse.on('close', () => { console.log('sse closed'); }); sse.send({ data: 'hello from sse function' }); sse.send([{ data: 'This is the first message.' }, { data: 'This is the second message.' }]); } return ''; }; // WebSocket export const main: TcbEventFunction = async function (event, context) { if (context.ws) { context.ws.on('open', (msg) => { console.log('open: ', msg); }); context.ws.on('error', (msg) => { console.log('error: ', msg); }); context.ws.on('close', (msg) => { console.log('close: ', msg); }); context.ws.on('message', (msg) => { console.log('message: ', msg); }); context.ws.send('hello from websocket function'); } }; main.handleUpgrade = async function (context) { return { allowWebSocket: true, }; }; ``` ## 项目组织结构 ### 基于 TypeScript 的多函数项目 ```tree . ├── README.md ├── Dockerfile ├── src/ │ ├── func-a/ │ │ ├── built/ # 编译后代码 │ │ ├── src/ # TypeScript 源码 │ │ ├── package.json │ │ ├── cloudbase-functions.json │ │ └── tsconfig.json │ └── func-b/ │ ├── built/ │ ├── src/ │ ├── package.json │ └── tsconfig.json ├── package.json └── tsconfig.json ``` 项目结构参考:[TypeScript 模板项目](https://github.com/TencentCloudBase/func-v2-template) ### 单函数项目 ```txt . ├── README.md ├── built/ # 编译后代码 ├── src/ # TypeScript 源码 ├── Dockerfile ├── package.json ├── cloudbase-functions.json └── tsconfig.json ``` > 💡 **提示**:JavaScript 项目无需 `src`、`built`、`tsconfig.json` 等目录和文件。 ## 相关文档 - [云函数类型选择](/cloud-function/quickstart/select-types) - [编写普通云函数](/cloud-function/how-coding) - [HTTP 云函数路由](/cloud-function/develop/function-routing) - [SSE 协议支持](/cloud-function/develop/sse) - [WebSocket 协议支持](/cloud-function/develop/websocket) - [测试云函数](/cloud-function/develop/how-test) --- # 云函数/HTTP云函数管理/启动文件说明 > 当前文档链接: https://docs.cloudbase.net/cloud-function/develop/scf-bootstrap 在 HTTP 云函数中,`scf_bootstrap` 是一个**必需的启动文件**,作为 Web Server 的启动入口。它负责启动您的 Web 服务、配置运行环境、加载依赖并确保服务正常监听请求。 ## 启动文件的作用 `scf_bootstrap` 文件在云函数实例启动时自动执行,主要承担以下职责: | 职责 | 说明 | | ----------------- | -------------------------------------------------------------------------------------------------- | | **启动 Web 服务** | 确保您的 Web 服务正常启动并监听请求(默认端口为 `9000`) | | **环境配置** | 设定运行时依赖库的路径、环境变量等 | | **依赖加载** | 加载自定义语言、版本依赖的库文件及扩展程序
如需实时拉取依赖,建议下载至 `/tmp` 目录 | | **全局初始化** | 执行函数调用前所需的全局操作
(如 HTTP Client 初始化、数据库连接池创建等),便于在调用阶段复用 | | **插件加载** | 启动安全、监控等插件 | ## 使用前提 为了确保启动文件能被正确执行,必须满足以下条件: | 要求 | 说明 | | ---------------- | -------------------------------------------------------------- | | **文件名固定** | 必须命名为 `scf_bootstrap`,**不支持其他名称** | | **可执行权限** | 文件必须具备可执行权限(建议 `777` 或 `755`) | | **系统环境** | 需能在 SCF 系统环境(CentOS 7.6)中运行 | | **Shebang 声明** | 如果是 Shell 脚本,第一行必须包含 `#!/bin/bash` | | **绝对路径** | 启动命令必须使用绝对路径(见下文"标准语言环境绝对路径") | | **监听地址** | 建议使用 `0.0.0.0`,**不可使用内部回环地址 `127.0.0.1`** | | **文件格式** | 文件结尾必须以 LF 换行符结束(不能是 CRLF) | | **写权限限制** | 在腾讯云标准环境下,仅 `/tmp` 目录可读写,输出文件时请注意路径 | > ⚠️ **重要提示**:在 Linux/macOS 系统中,使用 `chmod +x scf_bootstrap` 或 `chmod 777 scf_bootstrap` 命令设置可执行权限。 ## 标准语言环境绝对路径 在编写启动命令时,必须使用语言对应的**绝对路径**。以下是常见版本路径参考: | 语言版本 | 绝对路径 | | ----------------- | ------------------------------------ | | **Node.js 18.15** | `/var/lang/node18/bin/node` | | **Node.js 16.13** | `/var/lang/node16/bin/node` | | **Node.js 14.18** | `/var/lang/node14/bin/node` | | **Node.js 12.16** | `/var/lang/node12/bin/node` | | **Python 3.10** | `/var/lang/python310/bin/python3.10` | | **Python 3.9** | `/var/lang/python39/bin/python3.9` | | **Python 3.7** | `/var/lang/python37/bin/python3.7` | | **Python 3.6** | `/var/lang/python3/bin/python3.6` | | **PHP 8.0** | `/var/lang/php80/bin/php` | | **PHP 7.4** | `/var/lang/php74/bin/php` | | **PHP 7.2** | `/var/lang/php7/bin/php` | | **Java 11** | `/var/lang/java11/bin/java` | | **Java 8** | `/var/lang/java8/bin/java` | ## 启动命令模板 ### Functions Framework(推荐) ```bash #!/bin/bash node node_modules/@cloudbase/functions-framework/bin/tcb-ff.js --port=9000 --logDirname=/tmp/logs --interceptOutput=false --extendedContextKey=X-Cloudbase-Context ``` **参数说明**: | 参数 | 说明 | 默认值 | | ---------------------- | -------------------- | ------------------- | | `--port` | 函数监听端口 | 9000 | | `--logDirname` | 日志存储目录 | /tmp/logs | | `--interceptOutput` | 是否拦截标准输出 | false | | `--extendedContextKey` | 扩展上下文请求头键名 | X-Cloudbase-Context | > 💡 **提示**:需要在 `package.json` 中安装 `@cloudbase/functions-framework` 依赖。 ### Express 框架 ```bash #!/bin/bash export PORT=9000 /var/lang/node18/bin/node app.js ``` > 📝 **说明**:请将 `app.js` 修改为您的实际启动文件名。 ### Next.js 框架 ```bash #!/bin/bash export PORT=9000 /var/lang/node18/bin/node node_modules/next/dist/bin/next start ``` ### Flask 框架 ```bash #!/bin/bash export PORT=9000 /var/lang/python3/bin/python3 app.py ``` > 📝 **说明**:请将 `app.py` 修改为您的实际启动文件名。 ### Django 框架 ```bash #!/bin/bash export PORT=9000 /var/lang/python3/bin/python3 manage.py runserver 0.0.0.0:9000 ``` ### FastAPI 框架 ```bash #!/bin/bash export PORT=9000 /var/lang/python3/bin/python3 -m uvicorn main:app --host 0.0.0.0 --port 9000 ``` > 📝 **说明**:请将 `main:app` 中的 `main` 修改为您的应用文件名,`app` 修改为应用实例名。 ### 内置 Web Server ```bash #!/bin/bash /var/lang/php7/bin/php -c /var/runtime/php7 -S 0.0.0.0:9000 hello.php ``` > 📝 **说明**:请将 `hello.php` 修改为您的入口文件名。 ### Laravel 框架 ```bash #!/bin/bash /var/lang/php7/bin/php artisan serve --host=0.0.0.0 --port=9000 ``` ### Spring Boot ```bash #!/bin/bash export PORT=9000 /var/lang/java8/bin/java -Dserver.port=${PORT} -jar target/my-app.jar ``` > 📝 **说明**:请将 `my-app.jar` 修改为您的实际 JAR 文件名。 ### 标准 HTTP Server ```bash #!/bin/bash export PORT=9000 ./main ``` > 📝 **说明**:请确保已编译好可执行文件 `main`。 ### Gin 框架 ```bash #!/bin/bash export PORT=9000 ./app ``` > 📝 **说明**:请将 `app` 修改为您的实际可执行文件名。 ## 相关文档 - [编写 HTTP 云函数](/cloud-function/develop/how-to-writing-functions-code) - [云函数类型选择](/cloud-function/quickstart/select-types) - [Node.js HTTP 云函数快速开始](/cloud-function/quickstart/httpfunc/nodejs) - [Python HTTP 云函数快速开始](/cloud-function/quickstart/httpfunc/python) - [Go HTTP 云函数快速开始](/cloud-function/quickstart/httpfunc/golang) - [Java HTTP 云函数快速开始](/cloud-function/quickstart/httpfunc/java) - [Functions Framework 快速开始](/cloud-function/quickstart/httpfunc/func-framework) --- # 云函数/HTTP云函数管理/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cloud-function/function-calls/index --- title: HTTP 云函数调用方式 description: 学习如何通过多种方式调用 CloudBase HTTP 云函数,包括小程序调用、HTTP API 调用、HTTP 访问服务调用等 keywords: [云函数调用, HTTP API, Web 客户端, 小程序调用, 流式传输] --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import DocCardList from '@theme/DocCardList'; # HTTP 云函数调用方式 CloudBase HTTP 云函数支持多种调用方式,满足不同场景和平台的需求。您可以根据实际情况选择最适合的调用方式。 ## 调用方式概览 | 调用方式 | 适用场景 | 特点 | | --- | --- | --- | | **小程序调用** | 微信小程序 | 原生支持,支持流式传输,无需额外配置 | | **HTTP API** | 跨语言调用、第三方系统集成 | 标准 REST API,支持所有编程语言 | | **HTTP 访问服务** | 浏览器环境、前端应用 | 通过自定义域名调用,支持 CORS | 通过小程序调用方法,无需配置或放通域名即可调用 HTTP 云函数,适合微信小程序开发。 ## 小程序调用 通过微信小程序原生 SDK 中的 `wx.cloud.callHTTPFunction` 方法,可以直接调用 HTTP 云函数,支持流式传输(SSE)。 :::warning 版本要求 调用 HTTP 云函数时,确保小程序基础库版本为 **3.15.1** 及以上。 ::: ### 调用方法 `wx.cloud.callHTTPFunction` 方法的调用方式与 `wx.request` 类似,支持多种请求方法和流式传输。 **基础调用示例:** ```js wx.cloud.callHTTPFunction({ name: 'my-http-function', // 云函数名称 config: { env: 'your-env-id', // 可选:指定云开发环境 ID }, data: { key: 'value' }, // 请求数据 path: '/api/hello', // 请求路径 method: 'post', // 请求方法 header: { 'X-Custom-Header': 'value', // 自定义请求头 }, timeout: 1500, // 超时时间(毫秒) enableChunked: true, // 启用流式传输 onHeadersReceived: (res) => { console.log("httpFunc headers received", res) }, onChunkedReceived: (res) => { console.log("chunked",res) const arrayBuffer = res.data; const uint8Array = new Uint8Array(arrayBuffer); let str = ''; for (let i = 0; i < uint8Array.length; i++) { str += String.fromCharCode(uint8Array[i]); } // 尝试解码 utf-8 try { const decodedStr = decodeURIComponent(escape(str)); console.log('SSE chunk received:', decodedStr); } catch (e) { console.log('SSE chunk received (raw):', str); } }, success: (res) => { console.log("httpFunc success", res) }, fail: (err) => { console.log("callHttpFunction err", err) }, }); ``` ### 请求参数说明 #### 调用参数 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `name` | string | 是 | 云函数名称 | | `config` | object | 否 | 配置对象 | | `config.env` | string | 否 | 云开发环境 ID | | `data` | object | 否 | 请求数据(body) | | `path` | string | 是 | 请求路径 | | `method` | string | 是 | 请求方法,可选值:`get` / `post` / `put` / `delete` / `head` / `options` | | `header` | object | 否 | 自定义请求头 | | `enableChunked` | boolean | 否 | 是否启用流式传输 | | `dataType` | string | 否 | 指定响应的数据类型,仅非流式情况下生效 | | `responseType` | string | 否 | 指定响应的数据类型,仅非流式情况下生效 | | `timeout` | number | 否 | 请求超时时间,单位为毫秒(ms),需设置小于 1500 ms | | `success` | function | 否 | 请求成功的回调 | | `fail` | function | 否 | 请求失败的回调 | | `complete` | function | 否 | 请求结束的回调(成功或失败都会执行) | | `onHeadersReceived` | function | 否 | 流式模式下,收到响应头时的回调 | | `onChunkedReceived` | function | 否 | 流式模式下,收到 chunk 数据时的回调 | #### 返回参数 | 参数 | 类型 | 说明 | | --- | --- | --- | | `data` | string / Object / ArrayBuffer | 返回的数据内容,流式返回时为 ArrayBuffer | | `statusCode` | number | HTTP 状态码 | | `header` | Object | 响应头 | ### 流式调用(SSE) `wx.cloud.callHTTPFunction` 支持流式调用,通过设置 `enableChunked: true` 开启流式传输,配合 `onChunkedReceived` 回调接收流式数据。适用于 AI 对话、实时数据推送等场景。 #### 云函数侧示例(Express SSE) ```js const express = require('express'); const app = express(); app.get('/sse', (req, res) => { // 1. 设置 SSE 必需的响应头 res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // 2. 发送一个初始注释(可选,用于避免某些代理超时) res.write(': keep-alive\n\n'); // 3. 定时发送事件 const intervalId = setInterval(() => { const data = { time: new Date().toISOString() }; // SSE 格式:data: <内容>\n\n res.write(`data: ${JSON.stringify(data)}\n\n`); }, 1000); // 4. 客户端关闭连接时清理定时器 req.on('close', () => { clearInterval(intervalId); res.end(); }); }); app.listen(9000); ``` :::tip 超时设置 函数配置时需注意超时时间设置。上述 demo 中的函数为持续发送消息,会在函数超时后自动中断。建议根据实际业务场景合理设置超时时间。 ::: #### 小程序侧示例 ```js wx.cloud.callHTTPFunction({ name: 'express-sse', // 根据需要修改为实际函数名 path: '/sse', method: 'GET', enableChunked: true, onHeadersReceived: (res) => { console.log("httpFn headers received", res) }, onChunkedReceived: (res) => { console.log("chunked",res) const arrayBuffer = res.data; const uint8Array = new Uint8Array(arrayBuffer); let str = ''; for (let i = 0; i < uint8Array.length; i++) { str += String.fromCharCode(uint8Array[i]); } // 尝试解码 utf-8 try { const decodedStr = decodeURIComponent(escape(str)); console.log('SSE chunk received:', decodedStr); } catch (e) { console.log('SSE chunk received (raw):', str); } }, }); ``` 通过 HTTP API 调用云函数支持跨语言访问,适合服务端应用和第三方系统集成。 ## HTTP API 调用 ### 获取访问令牌 调用前需要先获取访问令牌(AccessToken),获取方式请参考 [AccessToken 文档](/http-api/basic/access-token)。 ### API 调用格式 **请求 URL:** ```text POST https://{env-id}.api.tcloudbasegateway.com/v1/functions/{function-name}?webfn=true ``` :::warning 重要 调用 HTTP 云函数时,必须在请求 URL 中添加 `webfn=true` 参数,否则将按照普通云函数方式调用。 ::: **请求头:** ```http Authorization: Bearer {access_token} Content-Type: application/json ``` ### 调用示例 #### cURL ```bash # 调用普通云函数 curl -L 'https://your-env-id.api.tcloudbasegateway.com/v1/functions/your-function-name' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer your-access-token' \ -H 'Content-Type: application/json' \ -d '{ "message": "Hello CloudBase", "timestamp": 1640995200000 }' # 调用 HTTP 云函数 curl -L 'https://your-env-id.api.tcloudbasegateway.com/v1/functions/your-web-function?webfn=true' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer your-access-token' \ -H 'Content-Type: application/json' \ -d '{ "path": "/api/users", "method": "GET" }' ``` ### API 参数说明 #### 路径参数 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `env-id` | string | 是 | 云开发环境 ID | | `function-name` | string | 是 | 云函数名称 | #### 查询参数 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `webfn` | string | 否 | 调用 HTTP 云函数时设置为 `true` | #### 请求头参数 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `Authorization` | string | 是 | Bearer Token 认证,格式为 `Bearer {access_token}` | | `Content-Type` | string | 是 | 请求内容类型,通常为 `application/json` | | `X-Qualifier` | string | 否 | 指定调用函数的版本 | HTTP 云函数支持通过自定义域名进行标准的 HTTP 调用,适合浏览器环境和前端应用。 ## HTTP 访问服务调用 ### 前提条件 - 已创建 HTTP 云函数 - 已配置 HTTP 访问服务和自定义域名 - 已获取函数的访问 URL ### 配置 HTTP 访问服务 1. **创建 HTTP 云函数**:在云开发控制台创建一个新的 HTTP 云函数 2. **进入函数详情**:函数创建成功后,点击函数名称进入详情页面 3. **配置访问路径**:在函数配置页面找到「HTTP 访问服务路径」选项,点击「去设定」 4. **设置域名和路径**:在 HTTP 访问服务页面配置自定义域名和访问路径 :::tip 提示 详细的 HTTP 访问服务配置方法,请参考 [HTTP 访问服务文档](../../service/introduce)。 ::: ### 客户端调用示例 #### Fetch API ```javascript // GET 请求 async function callFunction() { try { const response = await fetch('https://your-domain.com/your-function-path', { method: 'GET', headers: { 'Content-Type': 'application/json', }, credentials: 'include', // 包含 cookies }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); console.log('函数返回结果:', result); return result; } catch (error) { console.error('请求失败:', error); throw error; } } // POST 请求 async function postToFunction(data) { try { const response = await fetch('https://your-domain.com/your-function-path', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), credentials: 'include', }); const result = await response.json(); return result; } catch (error) { console.error('POST 请求失败:', error); throw error; } } ``` #### Axios ```javascript import axios from 'axios'; // 配置 axios 实例 const api = axios.create({ baseURL: 'https://your-domain.com', timeout: 10000, headers: { 'Content-Type': 'application/json', }, withCredentials: true, }); // 请求拦截器 api.interceptors.request.use( (config) => { // 可以在这里添加认证 token // config.headers.Authorization = `Bearer ${getToken()}`; return config; }, (error) => { return Promise.reject(error); } ); // 响应拦截器 api.interceptors.response.use( (response) => { return response.data; }, (error) => { console.error('API 请求失败:', error.response?.data || error.message); return Promise.reject(error); } ); // GET 请求 async function getFunctionData() { try { const result = await api.get('/your-function-path'); console.log('获取数据成功:', result); return result; } catch (error) { console.error('获取数据失败:', error); throw error; } } // POST 请求 async function postFunctionData(data) { try { const result = await api.post('/your-function-path', data); console.log('提交数据成功:', result); return result; } catch (error) { console.error('提交数据失败:', error); throw error; } } ``` ### 错误处理 #### 常见 HTTP 状态码 | 状态码 | 说明 | 处理建议 | | ------ | -------------- | -------------------- | | `200` | 请求成功 | 正常处理响应数据 | | `400` | 请求参数错误 | 检查请求参数格式 | | `401` | 未授权 | 检查认证信息 | | `404` | 函数不存在 | 检查 URL 路径 | | `500` | 服务器内部错误 | 检查函数代码逻辑 | | `502` | 网关错误 | 检查函数是否正常运行 | | `504` | 请求超时 | 优化函数执行时间 | #### 错误处理最佳实践 ```javascript async function callFunctionSafely(url, options = {}) { try { const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', }, ...options, }); // 检查响应状态 if (!response.ok) { const errorData = await response.text(); throw new Error(`HTTP ${response.status}: ${errorData}`); } const result = await response.json(); return { success: true, data: result }; } catch (error) { console.error('函数调用失败:', error); // 根据错误类型进行不同处理 if (error.name === 'TypeError') { return { success: false, error: '网络连接失败' }; } else if (error.message.includes('404')) { return { success: false, error: '函数不存在' }; } else { return { success: false, error: error.message }; } } } ``` ## 相关文档 --- # 云函数/HTTP云函数管理/HTTP 云函数路由 > 当前文档链接: https://docs.cloudbase.net/cloud-function/develop/function-routing 基于 `@cloudbase/functions-framework` 框架可以将一个大函数拆分成多个子函数,并通过请求路径将不同的请求路由到不同的处理函数 ## 为什么需要函数路由 传统的云函数开发模式中,每个函数都需要独立部署和运行,这会带来以下问题: - **资源浪费**:每个函数独占一个实例,即使请求量很小也需要保持运行 - **冷启动频繁**:多个函数意味着更多的冷启动次数 - **部署复杂**:需要分别部署和管理多个函数 - **成本较高**:更多的实例数量带来更高的资源成本 使用函数路由可以解决这些问题: - **资源共享**:多个函数共享同一实例,充分利用计算资源 - **降低成本**:减少实例数量,降低冷启动次数 - **简化部署**:一次部署多个函数,统一管理 - **提升性能**:共享内存和连接池等资源,提高响应速度 ## 前置条件 使用函数路由功能需要安装 `@cloudbase/functions-framework` 依赖: ```bash npm install @cloudbase/functions-framework ``` 该框架提供了函数加载、路由分发、请求处理等核心能力。 ## 快速开始 ### 第一步:创建项目结构 创建一个支持多函数的项目目录: ``` my-functions/ ├── package.json # 项目配置 ├── scf_bootstrap # 启动脚本 ├── cloudbase-functions.json # 函数路由配置(关键) ├── index.js # 默认函数入口 ├── user/ # user 函数目录 │ └── index.js └── order/ # order 函数目录 └── index.js ``` ### 第二步:创建函数代码 **默认函数(index.js)**: ```javascript exports.main = function (event, context) { return { message: 'Hello from default function', path: context.httpContext.url, }; }; ``` **user 函数(user/index.js)**: ```javascript exports.main = function (event, context) { return { message: 'User API', path: context.httpContext.url, method: context.httpContext.httpMethod, }; }; ``` **order 函数(order/index.js)**: ```javascript exports.main = function (event, context) { return { message: 'Order API', path: context.httpContext.url, }; }; ``` ### 第三步:配置函数路由 创建 `cloudbase-functions.json` 配置文件: ```json { "functionsRoot": ".", "functions": [ { "name": "default", "directory": "." }, { "name": "user", "directory": "user", "triggerPath": "/api/user" // triggerPath 是 routes 的简化写法,可以直接在函数定义中指定触发路径 }, { "name": "order", "directory": "order", "triggerPath": "/api/order" } ], "routes": [ { "functionName": "echo", "path": "/echo" } ] } ``` ### 第四步:配置 package.json ```json { "name": "my-functions", "version": "1.0.0", "main": "index.js", "dependencies": { "@cloudbase/functions-framework": "latest" } } ``` ### 第五步:创建启动脚本 创建 `scf_bootstrap` 文件(无扩展名): ```bash #!/bin/bash node node_modules/@cloudbase/functions-framework/bin/tcb-ff.js \ --port=9000 \ --logDirname=/tmp/logs \ --interceptOutput=false \ --extendedContextKey=X-Cloudbase-Context \ --functionsConfigFile=cloudbase-functions.json ``` > ⚠️ **重要**:`scf_bootstrap` 文件需要具有可执行权限。在 Linux/macOS 系统中使用 `chmod +x scf_bootstrap` 命令设置权限。 **启动参数说明**: | 参数 | 说明 | 默认值 | | ----------------------- | -------------------- | ------------------------ | | `--port` | 函数监听端口 | 9000 | | `--logDirname` | 日志存储目录 | /tmp/logs | | `--interceptOutput` | 是否拦截标准输出 | false | | `--extendedContextKey` | 扩展上下文请求头键名 | X-Cloudbase-Context | | `--functionsConfigFile` | 函数路由配置文件路径 | cloudbase-functions.json | ### 第六步:安装依赖 ```bash npm install ``` ### 第七步:本地测试 启动函数服务: ```bash ./scf_bootstrap ``` 测试不同路由: ```bash # 测试默认函数 curl http://localhost:9000/ # 测试 user 函数 curl http://localhost:9000/api/user # 测试 order 函数 curl http://localhost:9000/api/order ``` ### 第八步:部署到云端 使用云开发 CLI 或控制台部署函数,部署后访问: ```bash # 默认函数 https://your-function.run.tcloudbase.com/ # user 函数 https://your-function.run.tcloudbase.com/api/user # order 函数 https://your-function.run.tcloudbase.com/api/order ``` ## 路由匹配规则 理解路由匹配规则对于正确配置函数路由至关重要: ### 1. 前缀匹配 路由匹配采用**前缀匹配**模式。当请求路径以某个路由规则的 `path` 开头时,即可匹配该路由。 **示例**: ```json { "routes": [ { "functionName": "user", "path": "/api/user" } ] } ``` 以下请求路径都会匹配到 `user` 函数: - `/api/user` ✅ - `/api/user/` ✅ - `/api/user/profile` ✅ - `/api/user/list` ✅ 以下请求路径不会匹配: - `/api/users` ❌(不是以 `/api/user` 开头) - `/api` ❌(路径不完整) ### 2. 最长匹配 当多个路由规则都能匹配同一请求路径时,框架会选择**最长**的那个路由规则。 **示例**: ```json { "routes": [ { "functionName": "user", "path": "/api/user" }, { "functionName": "userProfile", "path": "/api/user/profile" } ] } ``` 路由结果: - `/api/user` → `user` 函数 - `/api/user/list` → `user` 函数 - `/api/user/profile` → `userProfile` 函数(最长匹配) - `/api/user/profile/detail` → `userProfile` 函数(最长匹配) ### 3. 末尾斜杠处理 路径末尾的 `/` 不影响匹配结果,`/api/user` 和 `/api/user/` 被视为等价。 **示例**: ```json { "routes": [ { "functionName": "user", "path": "/api/user" } ] } ``` 以下路径都会匹配: - `/api/user` ✅ - `/api/user/` ✅ ### 4. 路径单元精确匹配 路由匹配以 `/` 分隔为路径单元,每个路径单元需要**精确匹配**。 **示例**: ```json { "routes": [ { "functionName": "user", "path": "/api/user" } ] } ``` 路由结果: - `/api/user` ✅ - `/api/users` ❌(`users` 与 `user` 不匹配) - `/api/user123` ❌(`user123` 与 `user` 不匹配) ### 5. 一函数多路由 一个函数可以配置多个触发路径,从不同的 URL 路径访问同一个函数。 **示例**: ```json { "routes": [ { "functionName": "user", "path": "/api/user" }, { "functionName": "user", "path": "/v1/user" }, { "functionName": "user", "path": "/v2/user" } ] } ``` 以上三个路径都会路由到 `user` 函数。 ### 6. 一路由一函数 同一个路径只能指向一个函数,不能同时指向多个函数。 **错误示例**: ```json { "routes": [ { "functionName": "userA", "path": "/api/user" }, { "functionName": "userB", "path": "/api/user" } ] } ``` > ⚠️ **注意**:上述配置会导致路由冲突,框架会报错。 ### 7. 默认路由 可以配置一个 `path` 为 `/` 的路由作为默认路由,处理所有未匹配的请求。 **示例**: ```json { "routes": [ { "functionName": "user", "path": "/api/user" }, { "functionName": "order", "path": "/api/order" }, { "functionName": "default", "path": "/" } ] } ``` 路由结果: - `/api/user` → `user` 函数 - `/api/order` → `order` 函数 - `/` → `default` 函数 - `/about` → `default` 函数 - `/health` → `default` 函数 ## 进阶用法 ### 使用 triggerPath 简化配置 `triggerPath` 是 `routes` 的简化写法,可以直接在函数定义中指定触发路径。 **对比**: 使用 `triggerPath`: ```json { "functions": [ { "name": "user", "directory": "user", "triggerPath": "/api/user" } ], "routes": [] } ``` 等价于: ```json { "functions": [ { "name": "user", "directory": "user" } ], "routes": [ { "functionName": "user", "path": "/api/user" } ] } ``` > 💡 **提示**:推荐使用 `triggerPath`,配置更简洁。如果需要一个函数对应多个路由,则需要使用 `routes` 配置。 ### 结合 Express/Koa 框架 函数路由可以与 Express、Koa 等 Web 框架结合使用,每个子函数可以是一个完整的 Web 应用。 **示例**: ```javascript // user/index.js const express = require('express'); const app = express(); app.get('/profile', (req, res) => { res.json({ user: 'profile' }); }); app.get('/list', (req, res) => { res.json({ users: [] }); }); exports.main = app; ``` 访问: - `https://your-function.api.tcloudbasegateway.com/v1/functions/{function-name}/api/user/profile?webfn=true` - `https://your-function.api.tcloudbasegateway.com/v1/functions/{function-name}/api/user/list?webfn=true` ### 混合使用 SSE 和 WebSocket 不同的子函数可以处理不同类型的请求,比如一个函数处理 HTTP 请求,另一个处理 WebSocket 连接。 **示例**: ``` . ├── cloudbase-functions.json ├── index.js # HTTP 函数 └── ws/ # WebSocket 函数 └── index.js ``` `cloudbase-functions.json`: ```json { "functionsRoot": ".", "functions": [ { "name": "default", "directory": "." }, { "name": "ws", "directory": "ws", "triggerPath": "/ws" } ] } ``` ## VSCode 编辑器支持 安装 `@cloudbase/functions-framework` 依赖后,可以在 VSCode 中获得 `cloudbase-functions.json` 的 schema 自动提示。 在项目根目录创建 `.vscode/settings.json` 文件: ```json { "json.schemas": [ { "fileMatch": ["cloudbase-functions.json"], "url": "./node_modules/@cloudbase/functions-framework/functions-schema.json" } ] } ``` 配置后,在编辑 `cloudbase-functions.json` 文件时会自动提示字段和类型。 ## 完整示例 ### 项目结构 ``` my-api-service/ ├── .vscode/ │ └── settings.json # VSCode 配置 ├── package.json # 项目配置 ├── scf_bootstrap # 启动脚本 ├── cloudbase-functions.json # 函数路由配置 ├── index.js # 默认函数 ├── user/ # 用户 API │ └── index.js ├── order/ # 订单 API │ └── index.js ├── product/ # 商品 API │ └── index.js └── sse/ # SSE 流式响应 └── index.js ``` ### cloudbase-functions.json ```json { "functionsRoot": ".", "functions": [ { "name": "default", "directory": ".", "triggerPath": "/" }, { "name": "user", "directory": "user", "triggerPath": "/api/user" }, { "name": "order", "directory": "order", "triggerPath": "/api/order" }, { "name": "product", "directory": "product", "triggerPath": "/api/product" }, { "name": "sse", "directory": "sse", "triggerPath": "/stream" } ] } ``` ## 常见问题 ### 1. 路由配置不生效 **问题**:配置了路由规则,但访问时仍然路由到错误的函数。 **解决方案**: - 检查 `cloudbase-functions.json` 文件是否在正确的位置 - 检查 `scf_bootstrap` 启动脚本中是否指定了 `--functionsConfigFile` 参数 - 检查路由规则的顺序,确保最长匹配规则在前 - 使用 `console.log` 输出 `context.httpContext.url` 查看实际请求路径 ### 2. 函数找不到 **问题**:启动时报错:`Function xxx not found`。 **解决方案**: - 检查 `functions[].directory` 路径是否正确 - 检查 `functions[].source` 文件是否存在 - 检查 `functions[].target` 导出的函数名是否正确 - 确保 `functionsRoot` 设置正确 ### 3. 路由冲突 **问题**:启动时报错:`Route conflict`。 **解决方案**: - 检查是否有多个路由规则指向同一路径 - 检查 `triggerPath` 和 `routes` 中是否有重复配置 - 确保每个路径只对应一个函数 ### 4. 默认函数不生效 **问题**:配置了 `path: "/"` 的默认路由,但访问时返回 404。 **解决方案**: - 确保默认路由配置在 `routes` 数组的**最后** - 检查是否有其他路由规则覆盖了默认路由 - 确保默认函数的 `directory` 和 `source` 配置正确 ### 5. 本地开发时热更新不生效 **问题**:修改函数代码后,需要重启服务才能生效。 **解决方案**: `@cloudbase/functions-framework` 框架暂不支持热更新,需要手动重启服务: ```bash # 停止服务(Ctrl+C) # 重新启动 ./scf_bootstrap ``` ## 相关文档 - [编写 HTTP 云函数](/cloud-function/develop/how-to-writing-functions-code) - [编写普通云函数](/cloud-function/how-coding) - [SSE 协议支持](/cloud-function/develop/sse) - [WebSocket 协议支持](/cloud-function/develop/websocket) - [云函数类型选择](/cloud-function/quickstart/select-types) - [Functions Framework 快速开始](/cloud-function/quickstart/httpfunc/func-framework) --- # 云函数/HTTP云函数管理/WebSocket 协议支持 > 当前文档链接: https://docs.cloudbase.net/cloud-function/develop/websocket 本文介绍如何在 Web 云函数中实现 WebSocket 长连接,支持服务端与客户端之间的双向实时通信。 ## 什么是 WebSocket 「WebSocket」是一种在单个 TCP 连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。与传统的 HTTP 请求-响应模式不同,WebSocket 建立连接后可以保持长连接,实现服务端和客户端之间的实时双向数据传输。 **主要特点**: - **全双工通信**:服务端和客户端可以同时发送和接收消息 - **持久连接**:建立连接后保持长连接,无需重复握手 - **低延迟**:消息传递实时高效,适合实时应用场景 - **协议切换**:通过 HTTP Upgrade 机制从 HTTP 协议升级为 WebSocket 协议 **典型应用场景**: - 实时聊天应用 - 在线协作编辑 - 实时数据监控 - 游戏服务器 - 实时通知推送 ## 工作原理 ### 服务启动 在支持 WebSocket 的 Web 云函数中,通过启动文件启动 WebSocket 服务器,服务器必须在 **9000 端口** 上监听。 ### 建立连接 1. 客户端通过 `ws://` 或 `wss://` 协议向云函数发起连接请求 2. 云函数平台将连接透传给运行环境中的 WebSocket 服务进程(9000 端口) 3. 连接协商及后续通信完全由您的 WebSocket 服务端代码处理 ### 连接生命周期 - **连接与实例映射**:一次 WebSocket 连接的生命周期等同于一次函数调用请求 - 连接建立 = 请求发起 - 连接断开 = 请求结束 - **实例映射**:函数实例与连接是一一对应的,同一实例在某一时刻仅处理一个 WebSocket 连接,新连接会启动新的实例 - **连接保持**:连接建立后,实例持续运行,处理双向数据传输 - **连接结束**:当 WebSocket 连接断开时,对应的函数实例停止运行 ## 使用限制 在使用 WebSocket 时,需要注意以下配额限制: | 限制项 | 说明 | | ------------------ | ------------------------------------------ | | **空闲超时时间** | 10 ~ 7200 秒,连接上无消息传输时的超时时间 | | **执行超时时间** | 必须 ≥ 空闲超时时间,函数的最大运行时长 | | **单次消息大小** | 最大 256KB | | **单连接传输速率** | 最大 128KB/s | | **单连接请求 QPS** | 最大 10 次/秒 | ## 连接断开与状态码 不同的断开情况对应不同的函数状态码: | 断开情况 | 函数表现 | 函数状态码 | | --------------------------------------------------- | ---------------- | ----------------------------------------------- | | **正常关闭**
状态码为 1000、1010、1011 | 函数正常执行结束 | **200** | | **异常关闭**
非标准关闭状态码 | 函数异常结束 | **439** (服务端关闭)
**456** (客户端关闭) | | **空闲超时**
连接无消息传输超过配置时间 | 函数异常结束 | **455** | | **执行超时**
连接持续时间超过函数最大运行时长 | 函数异常结束 | **433** | ## 操作步骤 ### 步骤1:开启 WebSocket 协议支持 在创建或编辑云函数时,需要在控制台开启 WebSocket 协议支持。 1. 登录 [云开发平台/云函数/函数列表](https://tcb.cloud.tencent.com/dev#/scf) 2. 点击「新建云函数」或选择已有函数点击「编辑」 3. 在「函数配置」中找到「WebSocket 协议」配置项 ![显示 WebSocket 协议开关和空闲超时时间配置](https://qcloudimg.tencent-cloud.cn/raw/af6358126f97825bf48d5b8be5fff064.png) 4. 开启「WebSocket 协议」开关 5. 设置「WebSocket 空闲超时时间」(范围:10-7200 秒) 6. 确保「执行超时时间」≥「空闲超时时间」 7. 点击「保存」完成配置 > ⚠️ 注意:WebSocket 协议一旦开启后不可取消,但空闲超时时间可以后续修改。 ### 步骤2:安装依赖并编写服务端代码 根据您使用的编程语言,安装对应的 WebSocket 库并编写服务端代码。WebSocket 服务器必须监听 **9000 端口**。 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; **安装依赖** 安装 `ws` 库: ```bash npm install ws ``` 在 `package.json` 中添加依赖: ```json { "dependencies": { "ws": "^8.0.0" } } ``` **编写服务端代码** 使用 `ws` 库实现 WebSocket 服务器: ```javascript const WebSocket = require('ws'); // 创建 WebSocket 服务器,必须监听 9000 端口 const wss = new WebSocket.Server({ port: 9000 }); wss.on('connection', (ws) => { console.log('新连接建立'); // 发送欢迎消息 ws.send('欢迎连接到 WebSocket 服务'); // 接收消息 ws.on('message', (message) => { console.log('收到消息:', message.toString()); // 回复消息 const response = { type: 'response', data: message.toString(), timestamp: Date.now(), }; ws.send(JSON.stringify(response)); }); // 连接关闭 ws.on('close', () => { console.log('连接已关闭'); }); // 错误处理 ws.on('error', (error) => { console.error('WebSocket 错误:', error); }); }); console.log('WebSocket 服务器已启动,监听端口 9000'); ``` **安装依赖** 在 `requirements.txt` 中添加 `websockets` 库: ``` websockets ``` **编写服务端代码** 使用 `websockets` 库实现 WebSocket 服务器: ```python import asyncio import websockets import json from datetime import datetime async def handle_connection(websocket, path): """处理 WebSocket 连接""" try: # 发送欢迎消息 await websocket.send("欢迎连接到 WebSocket 服务") # 持续接收消息 async for message in websocket: print(f"收到消息: {message}") # 回复消息 response = { "type": "response", "data": message, "timestamp": datetime.now().isoformat() } await websocket.send(json.dumps(response)) except websockets.exceptions.ConnectionClosed: print("连接已关闭") except Exception as e: print(f"错误: {e}") # 启动 WebSocket 服务 async def main(): # 必须监听 9000 端口 async with websockets.serve(handle_connection, "0.0.0.0", 9000): await asyncio.Future() # 保持运行 if __name__ == "__main__": asyncio.run(main()) ``` ### 步骤3:部署云函数 完成代码编写后,部署云函数: - 在控制台点击「部署」按钮 - 或使用 CloudBase CLI:`tcb fn deploy --httpFn` ### 步骤4:客户端连接测试 使用浏览器或 Node.js 客户端连接 WebSocket 服务: ```javascript // 浏览器端 const ws = new WebSocket('wss://your-function.run.tcloudbase.com'); ws.onopen = () => { console.log('连接已建立'); ws.send('Hello, server!'); }; ws.onmessage = (event) => { console.log('收到消息:', event.data); }; ws.onclose = () => { console.log('连接已关闭'); }; ws.onerror = (error) => { console.error('WebSocket 错误:', error); }; ``` ## 高级用法 ### 1. 广播消息 将消息发送给所有连接的客户端: ```javascript const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 9000 }); // 存储所有连接的客户端 const clients = new Set(); wss.on('connection', (ws) => { // 添加到客户端列表 clients.add(ws); console.log(`新连接建立,当前连接数: ${clients.size}`); ws.on('message', (message) => { const msg = message.toString(); console.log('收到消息:', msg); // 广播给所有客户端 clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send( JSON.stringify({ type: 'broadcast', data: msg, timestamp: Date.now(), }) ); } }); }); ws.on('close', () => { // 移除断开的客户端 clients.delete(ws); console.log(`连接已关闭,当前连接数: ${clients.size}`); }); }); ``` ```python import asyncio import websockets import json from datetime import datetime # 存储所有连接的客户端 clients = set() async def handle_connection(websocket, path): # 添加到客户端列表 clients.add(websocket) print(f"新连接建立,当前连接数: {len(clients)}") try: async for message in websocket: print(f"收到消息: {message}") # 广播给所有客户端 disconnected = set() for client in clients: try: await client.send(json.dumps({ "type": "broadcast", "data": message, "timestamp": datetime.now().isoformat() })) except websockets.exceptions.ConnectionClosed: disconnected.add(client) # 移除已断开的客户端 clients.difference_update(disconnected) finally: # 移除断开的客户端 clients.remove(websocket) print(f"连接已关闭,当前连接数: {len(clients)}") ``` ### 2. 心跳检测 实现心跳机制保持连接活跃: ```javascript const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 9000 }); wss.on('connection', (ws) => { let isAlive = true; // 心跳检测:每 30 秒发送 ping const heartbeatInterval = setInterval(() => { if (!isAlive) { console.log('心跳超时,关闭连接'); ws.terminate(); return; } isAlive = false; ws.ping(); }, 30000); // 收到 pong 响应 ws.on('pong', () => { isAlive = true; }); ws.on('message', (message) => { console.log('收到消息:', message.toString()); ws.send(`回复: ${message.toString()}`); }); ws.on('close', () => { console.log('连接已关闭'); clearInterval(heartbeatInterval); }); }); ``` ```python import asyncio import websockets async def handle_connection(websocket, path): try: # 启动心跳任务 heartbeat_task = asyncio.create_task(send_heartbeat(websocket)) async for message in websocket: print(f"收到消息: {message}") await websocket.send(f"回复: {message}") finally: heartbeat_task.cancel() print("连接已关闭") async def send_heartbeat(websocket): """每 30 秒发送心跳""" try: while True: await asyncio.sleep(30) await websocket.ping() except asyncio.CancelledError: pass ``` ### 3. 房间管理 实现简单的房间功能,支持多个客户端加入不同房间进行分组通信: ```javascript const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 9000 }); // 房间管理 const rooms = new Map(); wss.on('connection', (ws) => { let currentRoom = null; ws.on('message', (message) => { try { const data = JSON.parse(message.toString()); // 加入房间 if (data.action === 'join') { currentRoom = data.room; if (!rooms.has(currentRoom)) { rooms.set(currentRoom, new Set()); } rooms.get(currentRoom).add(ws); ws.send( JSON.stringify({ type: 'system', message: `已加入房间: ${currentRoom}`, roomSize: rooms.get(currentRoom).size, }) ); } // 发送消息到房间 if (data.action === 'message' && currentRoom) { const roomClients = rooms.get(currentRoom); roomClients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send( JSON.stringify({ type: 'message', room: currentRoom, content: data.content, timestamp: Date.now(), }) ); } }); } } catch (e) { console.error('消息处理错误:', e); } }); ws.on('close', () => { // 离开房间 if (currentRoom && rooms.has(currentRoom)) { rooms.get(currentRoom).delete(ws); // 清理空房间 if (rooms.get(currentRoom).size === 0) { rooms.delete(currentRoom); } } }); }); ``` ```python import asyncio import websockets import json from datetime import datetime # 房间管理 rooms = {} async def handle_connection(websocket, path): current_room = None try: async for message in websocket: try: data = json.loads(message) # 加入房间 if data.get('action') == 'join': current_room = data.get('room') if current_room not in rooms: rooms[current_room] = set() rooms[current_room].add(websocket) await websocket.send(json.dumps({ "type": "system", "message": f"已加入房间: {current_room}", "roomSize": len(rooms[current_room]) })) # 发送消息到房间 if data.get('action') == 'message' and current_room: room_clients = rooms.get(current_room, set()) disconnected = set() for client in room_clients: try: await client.send(json.dumps({ "type": "message", "room": current_room, "content": data.get('content'), "timestamp": datetime.now().isoformat() })) except websockets.exceptions.ConnectionClosed: disconnected.add(client) # 移除已断开的客户端 room_clients.difference_update(disconnected) except json.JSONDecodeError: print("消息解析错误") finally: # 离开房间 if current_room and current_room in rooms: rooms[current_room].discard(websocket) # 清理空房间 if len(rooms[current_room]) == 0: del rooms[current_room] ``` ## 客户端连接示例 ```javascript // 连接 WebSocket const ws = new WebSocket('wss://your-service.run.tcloudbase.com'); // 连接建立 ws.onopen = () => { console.log('WebSocket 连接已建立'); ws.send('Hello, server!'); }; // 接收消息 ws.onmessage = (event) => { console.log('收到消息:', event.data); try { const data = JSON.parse(event.data); console.log('解析的数据:', data); } catch (e) { // 处理非 JSON 消息 } }; // 连接关闭 ws.onclose = () => { console.log('WebSocket 连接已关闭'); }; // 错误处理 ws.onerror = (error) => { console.error('WebSocket 错误:', error); }; // 发送消息 function sendMessage(msg) { if (ws.readyState === WebSocket.OPEN) { ws.send(msg); } } ``` ```javascript const WebSocket = require('ws'); const ws = new WebSocket('wss://your-service.run.tcloudbase.com'); ws.on('open', () => { console.log('连接已建立'); ws.send('Hello, server!'); }); ws.on('message', (data) => { console.log('收到消息:', data.toString()); }); ws.on('close', () => { console.log('连接已关闭'); }); ws.on('error', (error) => { console.error('错误:', error); }); ``` ## 常见问题 ### 1. 连接无法建立怎么办? - 检查是否在控制台开启了 WebSocket 协议支持 - 确认 WebSocket 服务器正在监听 **9000 端口** - 确认使用正确的 WebSocket 地址(`ws://` 或 `wss://`) - 检查云函数是否正常部署和运行 - 查看云函数日志是否有错误信息 ### 2. 连接频繁断开怎么办? - 检查空闲超时时间配置是否合理(建议设置较大值,如 3600 秒) - 实现心跳机制保持连接活跃(每 30 秒发送一次 ping/pong) - 检查客户端网络是否稳定 - 查看云函数日志,确认是否有异常错误导致连接断开 - 确保执行超时时间 ≥ 空闲超时时间 ### 3. 如何实现多人聊天室? 参考本文「高级用法 - 房间管理」章节,使用 `Map` 或 `Set` 存储房间和连接信息。对于大规模应用,建议: - 使用 Redis 等外部存储管理连接状态和房间信息 - 实现连接池管理 - 考虑使用消息队列处理广播消息 ### 4. 如何处理大量并发连接? - **实例扩展**:云函数会自动为每个 WebSocket 连接创建独立实例 - **状态管理**:使用 Redis 等外部存储共享连接状态 - **消息队列**:使用消息队列(如 TDMQ)处理广播和异步消息 - **负载均衡**:多个云函数实例自动负载均衡 - **资源监控**:关注函数实例数量和资源使用情况 ### 5. 9000 端口被占用怎么办? 云函数运行环境中,9000 端口是专门为 WebSocket 保留的,不会被其他服务占用。如果本地开发时遇到端口占用: ```bash # 查找占用 9000 端口的进程 lsof -i :9000 # 终止进程(Mac/Linux) kill -9 ``` ### 6. 如何实现断线重连? 客户端实现自动重连机制: ```javascript class ReconnectingWebSocket { constructor(url, maxRetries = 5) { this.url = url; this.maxRetries = maxRetries; this.retries = 0; this.connect(); } connect() { this.ws = new WebSocket(this.url); this.ws.onopen = () => { console.log('连接已建立'); this.retries = 0; // 重置重试计数 }; this.ws.onclose = () => { console.log('连接已关闭'); this.reconnect(); }; this.ws.onerror = (error) => { console.error('WebSocket 错误:', error); }; this.ws.onmessage = (event) => { console.log('收到消息:', event.data); }; } reconnect() { if (this.retries < this.maxRetries) { this.retries++; const delay = Math.min(1000 * Math.pow(2, this.retries), 30000); console.log(`${delay}ms 后尝试重连 (${this.retries}/${this.maxRetries})`); setTimeout(() => { this.connect(); }, delay); } else { console.error('达到最大重试次数,停止重连'); } } send(data) { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(data); } else { console.warn('连接未打开,无法发送消息'); } } } // 使用 const ws = new ReconnectingWebSocket('wss://your-function.run.tcloudbase.com'); ``` ## 相关文档 - [编写 HTTP 云函数](/cloud-function/develop/how-to-writing-functions-code) - [使用 SSE](/cloud-function/function-calls/sse) - [云函数配置](/cloud-function/function-configuration/config) - [云函数部署](/cloud-function/manage) --- # 云函数/HTTP云函数管理/SSE 协议支持 > 当前文档链接: https://docs.cloudbase.net/cloud-function/develop/sse 本文介绍如何在 Web 云函数中实现 SSE(Server-Sent Events)服务端推送,支持服务端向客户端单向推送实时数据流。 ## 什么是 SSE 「SSE(Server-Sent Events)」是一种服务器向客户端推送实时数据的技术,基于 HTTP 协议实现。与 WebSocket 不同,SSE 是单向通信,只支持服务端向客户端推送数据。 **主要特点**: - **单向推送**:服务端主动向客户端推送数据,客户端只能接收 - **基于 HTTP**:使用标准 HTTP 协议,兼容性好,易于实现 - **自动重连**:客户端断线后会自动重新连接 - **文本格式**:传输的数据为文本格式(通常是 JSON) - **轻量实现**:相比 WebSocket,实现更简单,资源占用更少 **典型应用场景**: - AI 对话流式输出 - 实时日志推送 - 进度更新通知 - 服务端状态监控 - 实时数据看板 ## 工作原理 ### 协议启用 SSE 协议在 Web 云函数中**默认支持**,无需在控制台进行任何额外配置即可使用。 ### 建立连接 1. 客户端通过标准 HTTP 请求建立 SSE 连接 2. 服务端返回 `Content-Type: text/event-stream` 响应头 3. 连接保持打开状态,服务端持续推送数据 ### 连接生命周期 - **调用对应关系**:一次 SSE 连接的生命周期等同于一次函数调用请求 - 连接建立 = 请求发起 - 连接断开 = 请求结束 - **实例映射**:函数实例与 SSE 连接是一一对应的,同一实例在某一时刻仅处理一个 SSE 连接,新连接会启动新的实例 - **连接保持**:连接建立后,实例持续运行,通过流式响应推送数据 - **连接结束**:当 SSE 连接断开或服务端调用 `end()` 时,对应的函数实例停止运行 ## 使用限制 在使用 SSE 时,需要注意以下限制: | 限制项 | 说明 | | -------------------- | ------------------------------------------------ | | **执行超时时间** | 连接持续时间受函数最大运行时长限制 | | **并发连接** | 每个连接启动独立实例,受账户并发配额限制 | | **浏览器连接数限制** | 同一域名下浏览器对 SSE 连接数有限制(通常为 6 个) | | **数据格式** | 只能传输文本数据,复杂数据需序列化为 JSON | ## 操作步骤 ### 步骤1:编写服务端代码 SSE 协议默认支持,无需在控制台开启。根据您使用的编程语言和框架,编写 SSE 服务端代码。 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; **安装依赖** 安装 Express 框架: ```bash npm install express ``` 在 `package.json` 中添加依赖: ```json { "dependencies": { "express": "^4.18.0" } } ``` **编写服务端代码** 使用 Express 实现 SSE 流式响应: ```javascript const express = require('express'); const app = express(); // SSE 路由 app.get('/stream', (req, res) => { // 设置 SSE 响应头 res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); console.log('新的 SSE 连接建立'); // 流式推送数据 const msg = ['SSE', 'empowering', 'GPT', 'applications', '!', 'Happy', 'chatting', '!']; let index = 0; const intervalId = setInterval(() => { if (index < msg.length) { // 发送 SSE 消息 const data = { id: index, content: msg[index], }; res.write(`data: ${JSON.stringify(data)}\n\n`); index++; } else { // 完成推送,关闭连接 clearInterval(intervalId); res.end(); } }, 1000); // 客户端断开连接时清理 req.on('close', () => { clearInterval(intervalId); console.log('SSE 连接已关闭'); }); }); // 启动服务,监听 9000 端口 app.listen(9000, () => { console.log('SSE 服务已启动,监听端口 9000'); }); ``` **安装依赖** 安装 Koa 框架: ```bash npm install koa koa-router ``` **编写服务端代码** 使用 Koa 实现 SSE 流式响应: ```javascript const Koa = require('koa'); const Router = require('koa-router'); const app = new Koa(); const router = new Router(); router.get('/stream', async (ctx) => { // 设置 SSE 响应头 ctx.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }); console.log('新的 SSE 连接建立'); const msg = ['SSE', 'empowering', 'GPT', 'applications', '!', 'Happy', 'chatting', '!']; // 创建可读流 const stream = new require('stream').PassThrough(); ctx.body = stream; let index = 0; const intervalId = setInterval(() => { if (index < msg.length) { const data = { id: index, content: msg[index], }; stream.write(`data: ${JSON.stringify(data)}\n\n`); index++; } else { clearInterval(intervalId); stream.end(); } }, 1000); // 客户端断开连接时清理 ctx.req.on('close', () => { clearInterval(intervalId); console.log('SSE 连接已关闭'); }); }); app.use(router.routes()).use(router.allowedMethods()); app.listen(9000, () => { console.log('SSE 服务已启动,监听端口 9000'); }); ``` **安装依赖** 在 `requirements.txt` 中添加 Flask: ``` Flask ``` **编写服务端代码** 使用 Flask 实现 SSE 流式响应: ```python import json import time from flask import Flask, Response, stream_with_context app = Flask(__name__) @app.route('/stream') def stream_data(): """SSE 流式推送""" msg = ['SSE', 'empowering', 'GPT', 'applications', '!', 'Happy', 'chatting', '!'] def generate_response_data(): for i, word in enumerate(msg): json_data = json.dumps({'id': i, 'content': word}) yield f"data: {json_data}\n\n" time.sleep(1) return Response( stream_with_context(generate_response_data()), mimetype="text/event-stream", headers={ 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } ) if __name__ == '__main__': app.run(host='0.0.0.0', port=9000) ``` **安装依赖** 在 `requirements.txt` 中添加 FastAPI 和 uvicorn: ``` fastapi uvicorn ``` **编写服务端代码** 使用 FastAPI 实现 SSE 流式响应: ```python import json import asyncio from fastapi import FastAPI from fastapi.responses import StreamingResponse app = FastAPI() @app.get('/stream') async def stream_data(): """SSE 流式推送""" async def generate_response_data(): msg = ['SSE', 'empowering', 'GPT', 'applications', '!', 'Happy', 'chatting', '!'] for i, word in enumerate(msg): json_data = json.dumps({'id': i, 'content': word}) yield f"data: {json_data}\n\n" await asyncio.sleep(1) return StreamingResponse( generate_response_data(), media_type="text/event-stream", headers={ 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } ) if __name__ == '__main__': import uvicorn uvicorn.run(app, host='0.0.0.0', port=9000) ``` ### 步骤2:部署云函数 完成代码编写后,部署云函数: - 在控制台点击「部署」按钮 - 或使用 CloudBase CLI:`tcb fn deploy --httpFn` ### 步骤3:客户端连接测试 使用浏览器或命令行工具连接 SSE 服务: **使用 curl 测试** ```bash curl -v -H 'Accept:text/event-stream' https://your-function.run.tcloudbase.com/stream ``` **预期返回结果** 服务器将以流的形式分块返回数据,每条数据间隔约 1 秒: ```text data: {"id": 0, "content": "SSE"} data: {"id": 1, "content": "empowering"} data: {"id": 2, "content": "GPT"} ... ``` **使用浏览器 JavaScript 测试** ```javascript // 创建 SSE 连接 const eventSource = new EventSource('https://your-function.run.tcloudbase.com/stream'); eventSource.onmessage = (event) => { console.log('收到消息:', event.data); try { const data = JSON.parse(event.data); console.log('解析数据:', data); } catch (e) { // 处理非 JSON 数据 } }; eventSource.onerror = (error) => { console.error('SSE 错误:', error); }; ``` ## 高级用法 ### 1. AI 对话流式输出 实现类似 ChatGPT 的流式对话效果: ```javascript const express = require('express'); const app = express(); app.use(express.json()); // AI 对话流式输出 app.post('/chat', async (req, res) => { // 设置 SSE 响应头 res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const { message } = req.body; // 发送开始事件 res.write(`event: start\ndata: ${JSON.stringify({ status: '开始生成回复' })}\n\n`); // 模拟 AI 生成回复(实际应调用 AI 模型 API) const reply = '这是 AI 生成的回复内容,会逐字推送给用户。'; for (let i = 0; i < reply.length; i++) { const data = { content: reply[i], index: i, }; res.write(`event: message\ndata: ${JSON.stringify(data)}\n\n`); // 模拟生成延迟 await new Promise((resolve) => setTimeout(resolve, 50)); } // 发送完成事件 res.write( `event: done\ndata: ${JSON.stringify({ status: '生成完成', totalLength: reply.length })}\n\n` ); res.end(); }); app.listen(9000); ``` ```python import json import time from flask import Flask, Response, request, stream_with_context app = Flask(__name__) @app.route('/chat', methods=['POST']) def chat(): """AI 对话流式输出""" data = request.get_json() message = data.get('message', '') def generate_chat_response(): # 发送开始事件 yield f"event: start\ndata: {json.dumps({'status': '开始生成回复'})}\n\n" # 模拟 AI 生成回复 reply = "这是 AI 生成的回复内容,会逐字推送给用户。" for i, char in enumerate(reply): data = {'content': char, 'index': i} yield f"event: message\ndata: {json.dumps(data)}\n\n" time.sleep(0.05) # 发送完成事件 yield f"event: done\ndata: {json.dumps({'status': '生成完成', 'totalLength': len(reply)})}\n\n" return Response( stream_with_context(generate_chat_response()), mimetype="text/event-stream" ) if __name__ == '__main__': app.run(host='0.0.0.0', port=9000) ``` ### 2. 实时进度推送 推送任务执行进度: ```javascript const express = require('express'); const app = express(); app.get('/progress', async (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const totalSteps = 10; for (let step = 1; step <= totalSteps; step++) { const data = { step, total: totalSteps, percent: Math.round((step / totalSteps) * 100), message: `正在处理第 ${step} 步...`, }; res.write(`event: progress\ndata: ${JSON.stringify(data)}\n\n`); // 模拟任务执行 await new Promise((resolve) => setTimeout(resolve, 1000)); } // 任务完成 res.write( `event: complete\ndata: ${JSON.stringify({ status: '任务完成', timestamp: Date.now() })}\n\n` ); res.end(); }); app.listen(9000); ``` ```python import json import time from flask import Flask, Response, stream_with_context app = Flask(__name__) @app.route('/progress') def progress(): """实时进度推送""" def generate_progress(): total_steps = 10 for step in range(1, total_steps + 1): data = { 'step': step, 'total': total_steps, 'percent': round((step / total_steps) * 100), 'message': f'正在处理第 {step} 步...' } yield f"event: progress\ndata: {json.dumps(data)}\n\n" time.sleep(1) # 任务完成 yield f"event: complete\ndata: {json.dumps({'status': '任务完成', 'timestamp': int(time.time() * 1000)})}\n\n" return Response( stream_with_context(generate_progress()), mimetype="text/event-stream" ) if __name__ == '__main__': app.run(host='0.0.0.0', port=9000) ``` ### 3. 实时日志推送 推送服务端日志到客户端: ```javascript const express = require('express'); const app = express(); // 模拟日志队列 const logQueue = []; // 模拟生成日志 setInterval(() => { logQueue.push({ level: ['info', 'debug', 'warn'][Math.floor(Math.random() * 3)], message: `日志消息 ${Date.now()}`, timestamp: Date.now(), }); // 限制队列大小 if (logQueue.length > 100) { logQueue.shift(); } }, 2000); app.get('/logs', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); console.log('开始推送日志'); // 定期推送日志 const intervalId = setInterval(() => { if (logQueue.length > 0) { const logs = logQueue.splice(0, 5); // 每次推送最多 5 条 logs.forEach((log) => { res.write(`event: log\ndata: ${JSON.stringify(log)}\n\n`); }); } }, 1000); // 客户端断开时清理 req.on('close', () => { clearInterval(intervalId); console.log('停止推送日志'); }); }); app.listen(9000); ``` ```python import json import time import random from flask import Flask, Response, stream_with_context from collections import deque app = Flask(__name__) # 模拟日志队列 log_queue = deque(maxlen=100) def generate_logs(): """模拟生成日志""" while True: log_queue.append({ 'level': random.choice(['info', 'debug', 'warn']), 'message': f'日志消息 {int(time.time() * 1000)}', 'timestamp': int(time.time() * 1000) }) time.sleep(2) # 在后台线程生成日志 import threading threading.Thread(target=generate_logs, daemon=True).start() @app.route('/logs') def logs(): """实时日志推送""" def generate_log_stream(): while True: if len(log_queue) > 0: # 每次推送最多 5 条 logs_to_send = [log_queue.popleft() for _ in range(min(5, len(log_queue)))] for log in logs_to_send: yield f"event: log\ndata: {json.dumps(log)}\n\n" time.sleep(1) return Response( stream_with_context(generate_log_stream()), mimetype="text/event-stream" ) if __name__ == '__main__': app.run(host='0.0.0.0', port=9000) ``` ### 4. 服务端状态监控 实时推送服务器状态指标: ```javascript const express = require('express'); const app = express(); app.get('/metrics', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('X-Monitoring-Session', `session-${Date.now()}`); console.log('开始推送监控数据'); // 定期推送监控数据 const intervalId = setInterval(() => { const metrics = { cpu: Math.random() * 100, memory: Math.random() * 100, activeConnections: Math.floor(Math.random() * 1000), timestamp: Date.now(), }; res.write(`event: metrics\ndata: ${JSON.stringify(metrics)}\n\n`); }, 2000); // 5 分钟后超时 const timeoutId = setTimeout(() => { clearInterval(intervalId); res.write(`event: timeout\ndata: ${JSON.stringify({ message: '监控会话超时' })}\n\n`); res.end(); }, 300000); // 客户端断开时清理 req.on('close', () => { clearInterval(intervalId); clearTimeout(timeoutId); console.log('停止推送监控数据'); }); }); app.listen(9000); ``` ```python import json import time import random from flask import Flask, Response, stream_with_context app = Flask(__name__) @app.route('/metrics') def metrics(): """服务端状态监控""" def generate_metrics(): start_time = time.time() while True: # 5 分钟后超时 if time.time() - start_time > 300: yield f"event: timeout\ndata: {json.dumps({'message': '监控会话超时'})}\n\n" break metrics_data = { 'cpu': random.random() * 100, 'memory': random.random() * 100, 'activeConnections': random.randint(0, 1000), 'timestamp': int(time.time() * 1000) } yield f"event: metrics\ndata: {json.dumps(metrics_data)}\n\n" time.sleep(2) return Response( stream_with_context(generate_metrics()), mimetype="text/event-stream", headers={ 'X-Monitoring-Session': f'session-{int(time.time() * 1000)}' } ) if __name__ == '__main__': app.run(host='0.0.0.0', port=9000) ``` ## 客户端连接示例 ```javascript // 创建 SSE 连接 const eventSource = new EventSource('https://your-service.run.tcloudbase.com/stream'); // 监听默认消息事件 eventSource.onmessage = (event) => { console.log('收到消息:', event.data); try { const data = JSON.parse(event.data); console.log('解析的数据:', data); } catch (e) { // 处理非 JSON 数据 } }; // 监听自定义事件 eventSource.addEventListener('progress', (event) => { const data = JSON.parse(event.data); console.log(`进度: ${data.percent}%`); }); eventSource.addEventListener('done', (event) => { console.log('任务完成'); eventSource.close(); // 关闭连接 }); // 连接打开 eventSource.onopen = () => { console.log('SSE 连接已建立'); }; // 错误处理 eventSource.onerror = (error) => { console.error('SSE 错误:', error); // 可选择关闭连接 // eventSource.close(); }; // 手动关闭连接 function closeConnection() { eventSource.close(); console.log('SSE 连接已关闭'); } ``` ```javascript import { useEffect, useState } from 'react'; function StreamingChat() { const [messages, setMessages] = useState([]); const [currentMessage, setCurrentMessage] = useState(''); const [isStreaming, setIsStreaming] = useState(false); const startChat = () => { setIsStreaming(true); const eventSource = new EventSource('https://your-service.run.tcloudbase.com/chat'); eventSource.addEventListener('start', (event) => { console.log('开始接收消息'); setCurrentMessage(''); }); eventSource.addEventListener('message', (event) => { const data = JSON.parse(event.data); setCurrentMessage((prev) => prev + data.content); }); eventSource.addEventListener('done', (event) => { setMessages((prev) => [...prev, currentMessage]); setCurrentMessage(''); setIsStreaming(false); eventSource.close(); }); eventSource.onerror = (error) => { console.error('连接错误:', error); setIsStreaming(false); eventSource.close(); }; // 组件卸载时清理 return () => { eventSource.close(); }; }; return (
{messages.map((msg, index) => (
{msg}
))} {currentMessage &&
{currentMessage}
}
); } ```
```javascript const EventSource = require('eventsource'); const eventSource = new EventSource('https://your-service.run.tcloudbase.com/stream'); eventSource.onmessage = (event) => { console.log('收到消息:', event.data); try { const data = JSON.parse(event.data); console.log('解析的数据:', data); } catch (e) { // 处理非 JSON 数据 } }; eventSource.addEventListener('progress', (event) => { const data = JSON.parse(event.data); console.log(`进度: ${data.percent}%`); }); eventSource.onerror = (error) => { console.error('错误:', error); }; // 10 秒后关闭连接 setTimeout(() => { eventSource.close(); console.log('连接已关闭'); }, 10000); ```
## SSE 消息格式 SSE 协议使用特定的文本格式发送消息,每条消息由一个或多个字段组成: ```text event: message data: {"content": "Hello"} id: 123 retry: 1000 ``` ### 标准字段 | 字段 | 说明 | 是否必需 | | ------- | -------------------------------------- | -------- | | `data` | 消息数据内容 | 是 | | `event` | 事件类型(默认为 `message`) | 否 | | `id` | 事件 ID,用于客户端重连时恢复 | 否 | | `retry` | 重连间隔时间(毫秒) | 否 | | `:` 注释 | 注释行,客户端会忽略(用于保持连接) | 否 | ### 消息示例 **基本消息** ```text data: Hello World ``` **JSON 数据** ```text data: {"message": "Hello", "timestamp": 1234567890} ``` **带事件类型** ```text event: notification data: {"type": "info", "content": "新消息"} ``` **多行数据** ```text data: 第一行 data: 第二行 data: 第三行 ``` **完整消息** ```text event: update id: 123 retry: 1000 data: {"status": "success"} ``` ## 常见问题 ### 1. 连接无法建立怎么办? - 确认云函数已正确部署和运行 - 检查响应头是否设置了 `Content-Type: text/event-stream` - 确认使用正确的函数访问地址 - 查看云函数日志是否有错误信息 - 检查客户端浏览器是否支持 SSE(IE 不支持) ### 2. 连接频繁断开怎么办? - 增加函数执行超时时间配置 - 实现心跳机制,定期发送注释行保持连接:`res.write(': keepalive\n\n')` - 检查客户端网络是否稳定 - 查看云函数日志,确认是否有异常错误导致连接断开 - 客户端实现自动重连机制(EventSource 默认支持) ### 3. 如何实现断线重连? EventSource 客户端会自动重连,服务端可以通过 `id` 和 `retry` 字段配合: **服务端发送 ID 和重连间隔** ```javascript res.write(`id: ${messageId}\nretry: 3000\ndata: ${JSON.stringify(data)}\n\n`); ``` **客户端获取最后的事件 ID** ```javascript const eventSource = new EventSource('https://your-service.run.tcloudbase.com/stream'); eventSource.onopen = () => { console.log('连接已建立'); }; eventSource.onerror = () => { console.log('连接断开,自动重连中...'); }; ``` ### 4. 如何处理大量并发连接? - **实例扩展**:云函数会自动为每个 SSE 连接创建独立实例 - **连接数限制**:注意浏览器同域名连接数限制(通常为 6 个) - **状态管理**:使用 Redis 等外部存储共享连接状态 - **消息队列**:使用消息队列处理广播和异步消息 - **资源监控**:关注函数实例数量和资源使用情况 ### 5. SSE 和 WebSocket 如何选择? **选择 SSE 的场景**: - 只需要服务端向客户端推送数据 - 追求简单实现和快速开发 - 需要自动重连功能 - 兼容性要求较高(HTTP 协议) **选择 WebSocket 的场景**: - 需要双向实时通信 - 需要传输二进制数据 - 需要更低的延迟和更高的效率 - 客户端需要频繁向服务端发送数据 ### 6. 如何发送心跳保持连接? 定期发送注释行(以 `:` 开头)保持连接活跃: ```javascript const express = require('express'); const app = express(); app.get('/stream', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // 每 30 秒发送心跳 const heartbeatInterval = setInterval(() => { res.write(': heartbeat\n\n'); }, 30000); // 发送实际数据 const dataInterval = setInterval(() => { res.write(`data: ${JSON.stringify({ timestamp: Date.now() })}\n\n`); }, 5000); req.on('close', () => { clearInterval(heartbeatInterval); clearInterval(dataInterval); }); }); app.listen(9000); ``` ## SSE vs WebSocket | 特性 | SSE | WebSocket | | ------------ | ------------------------ | --------------------- | | 通信方式 | 单向(服务端→客户端) | 双向(服务端↔客户端) | | 协议 | HTTP | WebSocket 协议 | | 数据格式 | 文本(通常 JSON) | 文本或二进制 | | 自动重连 | 是 | 否(需手动实现) | | 浏览器兼容性 | 好(IE 除外) | 好 | | 实现复杂度 | 简单 | 相对复杂 | | 资源占用 | 较低 | 较高 | | 配置要求 | 无需配置 | 需要在控制台开启 | | 适用场景 | 单向数据推送 | 实时双向通信 | **选择建议**: - 只需要服务端推送数据:选择 SSE - 需要双向实时通信:选择 WebSocket - 追求简单实现:选择 SSE - 需要传输二进制数据:选择 WebSocket ## 相关文档 - [编写 HTTP 云函数](/cloud-function/develop/how-to-writing-functions-code) - [使用 WebSocket](/cloud-function/develop/websocket) - [云函数配置](/cloud-function/function-configuration/config) - [云函数部署](/cloud-function/manage) --- # 云函数/HTTP云函数管理/部署云函数 > 当前文档链接: https://docs.cloudbase.net/cli-v1/functions/deploy 使用 `tcb fn deploy` 命令可以快速部署云函数到云开发环境。 ```bash tcb fn deploy [options] [name] ``` ## 命令参数 | 参数 | 说明 | 必填 | | ---------------------------- | ------------------------------------------------------------------------ | ---- | | `-e, --env-id ` | 环境 Id | 否 | | `--httpFn` | 部署为 HTTP 云函数 | 否 | | `--ws` | 部署 HTTP 云函数时启用 WebSocket 协议 | 否 | | `--code-secret ` | 传入此参数将保护代码,格式为 36 位大小写字母和数字 | 否 | | `--force` | 如果存在同名函数,上传后覆盖同名函数 | 否 | | `--path ` | 自动创建 HTTP 访问服务访问路径 | 否 | | `--dir ` | 指定云函数的文件夹路径 | 否 | | `--all` | 部署配置文件中包含的全部云函数 | 否 | | `--deployMode ` | 上传模式:`cos`(默认推荐)、`zip`(不推荐,限 1.5 MB)、`image`(镜像) | 否 | | `--yes` | 跳过交互确认,直接执行部署 | 否 | | `-h, --help` | 查看命令帮助信息 | 否 | ## 基本用法 ### 方式一:使用配置文件(推荐) 在包含 `cloudbaserc.json` 配置文件的项目目录下执行: ```bash # 部署指定函数 tcb fn deploy # 部署配置文件中的所有函数 tcb fn deploy --all ``` ### 方式二:从当前目录部署 在函数代码目录下(包含 `package.json`),无需配置文件即可直接部署: ```bash cd my-function tcb fn deploy ``` CLI 会自动从 `package.json` 读取函数名称,并使用默认配置部署。 ### 部署 HTTP 函数 添加 `--httpFn` 参数即可部署为 HTTP 函数: ```bash tcb fn deploy --httpFn ``` ## 使用示例 ### 标准工作空间部署 ``` project/ ├── cloudbaserc.json └── functions/ └── hello/ ├── index.js └── package.json ``` ```bash tcb fn deploy hello ``` ### 自定义函数目录 ```json // cloudbaserc.json { "envId": "your-env-id", "functionRoot": "./functions", "functions": [ { "name": "api", "dir": "./src/cloud-functions/api", "runtime": "Nodejs20.19" } ] } ``` ```bash tcb fn deploy api ``` ### 命令行指定目录 ```bash tcb fn deploy hello --dir ./my-custom-path/hello ``` ## 部署路径解析 部署时,CLI 按以下优先级解析函数代码路径: ``` 1. --dir 参数(命令行指定) → 最高优先级 2. functions[].dir(配置文件) → 次优先级 3. functionRoot + 函数名 → 默认路径(functionRoot 默认为 ./functions) 4. 当前工作目录 → 无配置文件时的兜底 ``` | 场景 | 配置文件 | --dir | functions.dir | 最终路径 | | ---------- | -------- | ------- | ------------- | --------------------- | | 标准部署 | ✅ 存在 | ❌ | ❌ | `functionRoot/函数名` | | 自定义目录 | ✅ 存在 | ❌ | ✅ 指定 | `functions.dir` 路径 | | 命令行覆盖 | ✅ 存在 | ✅ 指定 | - | `--dir` 路径 | | 无配置文件 | ❌ | ❌ | - | `当前工作目录` | :::caution 注意 批量部署时(使用 `--all` 或不指定函数名),`--dir` 参数无效,会被忽略。 ::: ## 高级选项 ### 代码加密 通过 `--code-secret` 参数对代码进行加密,密钥需要使用 36 位大小写字母和数字组成: ```bash tcb fn deploy app --code-secret 7sGLwMnhgEfKmkqg2dMjB6xWk2hCxsAgGR6w ``` :::warning 注意 启用代码加密后,将无法在小程序 IDE、腾讯云控制台中查看云函数的代码和信息。 ::: ### 覆盖同名函数 如果云端已存在同名函数,CLI 会提示是否覆盖。如需强制覆盖,可使用 `--force` 参数: ```bash tcb fn deploy app --force ``` :::caution 重要提示 使用 `--force` 参数覆盖函数时,会同时覆盖函数的配置和触发器。 ::: ### 跳过交互确认 在自动化脚本或 CI/CD 流程中,可以使用 `--yes` 参数跳过所有交互式确认: ```bash # 部署时跳过确认 tcb fn deploy app --yes # 批量部署跳过确认 tcb fn deploy --all --yes # 强制覆盖并跳过确认 tcb fn deploy app --force --yes ``` :::tip 自动化部署建议 在 CI/CD 流程中,建议始终添加 `--yes` 参数以避免脚本阻塞等待用户输入。 ::: ## HTTP 函数 HTTP 函数是专门针对 Web 服务场景优化的云函数类型,支持标准 HTTP 请求响应模式。 ### 部署方式 #### 方式一:使用配置文件 在 `cloudbaserc.json` 中将 `type` 设置为 `HTTP`,然后执行: ```bash tcb fn deploy webFunction ``` #### 方式二:使用命令行参数 ```bash tcb fn deploy --httpFn ``` #### 方式三:从当前目录部署 ```bash cd my-web-function tcb fn deploy --httpFn ``` ### 启动脚本 HTTP 函数需要 `scf_bootstrap` 启动脚本来启动 Web Server。如果未创建,CLI 会提示自动生成。 :::tip 详细说明 关于启动脚本的完整说明(基本要求、示例模板、运行时路径、常见错误排查),请参考 [启动脚本](/cloud-function/develop/scf-bootstrap)。 ::: ### 相关指南 - [HTTP 函数项目结构和编写指南](/cloud-function/develop/how-to-writing-functions-code) - [云函数调用方式](https://docs.cloudbase.net/cloud-function/function-calls/) ## 镜像部署 云函数支持通过容器镜像部署,适用于需要自定义运行环境、使用非标准语言或依赖复杂系统库的场景。 ### 适用场景 - 需要自定义运行时环境 - 使用 Node.js/Python 以外的语言(如 Go、Rust) - 依赖特定系统库或二进制文件 - 需要预装大量依赖包 - 已有容器化应用迁移 ### 前置条件 使用镜像部署云函数前,请确保已完成以下准备工作: 1. **授权镜像拉取**:为云函数角色授权策略 `QcloudAccessForSCFRoleInPullImage`,一次性操作:[点击授权](https://console.cloud.tencent.com/cam/role/grant?roleName=SCF_QcsRole&policyName=QcloudAccessForSCFRoleInPullImage&principal=eyJzZXJ2aWNlIjoic2NmLnFjbG91ZC5jb20ifQ%3D%3D) 2. **准备镜像仓库**:在 [腾讯云容器镜像服务 TCR](https://console.cloud.tencent.com/tcr/repository) 创建仓库;参考 [获取访问凭证](https://cloud.tencent.com/document/product/1141/41829) 和 [推送镜像](https://cloud.tencent.com/document/product/1141/41811) 3. **镜像要求**:基于 Linux 的 `amd64` 镜像,容器内监听 **9000 端口** 4. **创建日志角色**(用于日志投递): - 在 [CAM 角色控制台](https://console.cloud.tencent.com/cam/role) 创建角色 - 选择**腾讯云产品服务**,角色载体选择 `scf` 和 `cls` - 关联[自定义策略](/quick-start/integrate-cloudbase#日志角色权限与多租户隔离)(推荐采用共享角色 + 只写权限方案,防止跨环境日志越权) - 记录角色名称(如 `SCF_CLSWriteOnly`),后续在配置文件中使用 > **多租户安全提示**:单租户场景可使用预置策略 `QcloudCLSFullAccess`。如果在同一账号下管理多个租户环境,建议使用自定义策略以防止跨环境日志越权,详见[日志角色权限与多租户隔离](/quick-start/integrate-cloudbase#日志角色权限与多租户隔离)。 ### 部署流程 #### 步骤 1:构建镜像 在函数代码目录下(包含 `Dockerfile`),构建 Docker 镜像: ```bash cd /path/to/your-function # 构建镜像(必须指定 linux/amd64 平台) docker build --platform linux/amd64 -t your-function:v1 . ``` :::warning 重要 在 ARM 架构机器(如 M1/M2 Mac)上构建时,**必须**添加 `--platform linux/amd64` 参数,否则镜像无法在云函数环境运行。 ::: #### 步骤 2:推送镜像 将镜像推送到腾讯云容器镜像服务(TCR): ```bash # 标记镜像 docker tag [imageId] <访问域名>/your-namespace/your-function:[tag] # 推送镜像 docker push <访问域名>/your-namespace/your-function:[tag] ``` #### 步骤 3:配置并部署 在 `cloudbaserc.json` 中配置 `imageConfig`: ```json { "envId": "your-env-id", "functions": [ { "name": "your-function", "imageConfig": { "imageType": "personal", "imageUri": "<访问域名>/your-namespace/your-function:v1" }, "role": "SCF_CLSFullAccess", "clsLogsetId": "your-logset-id", "clsTopicId": "your-topic-id" } ] } ``` :::warning 日志配置 镜像部署的云函数需要手动配置日志投递: 1. 在 [日志服务控制台](https://console.cloud.tencent.com/cls/topic) 创建日志集和日志主题,获取 `clsLogsetId` 和 `clsTopicId` 2. 在前置条件中创建的日志角色名称填入 `role` 字段 ::: 执行部署: ```bash tcb fn deploy --deployMode image ``` :::tip 配置详情 完整的镜像配置项说明,请参考 [镜像配置](/cli-v1/functions/configs#镜像配置)。 ::: ## 配置文件 云函数的配置通过 `cloudbaserc.json` 文件管理,包括函数名称、运行时、超时时间、环境变量等。 :::tip 配置详情 完整的配置项说明和示例,请参考 [云函数配置文件](/cli-v1/functions/configs)。 ::: ## 注意事项 :::caution 函数类型不可变更 已部署的函数类型(普通函数/HTTP 函数)不支持变更。如需变更类型,请先删除原函数后重新部署。 ::: :::caution 环境变量更新规则 - **@cloudbase/cli 2.12.0 及以上版本**:部署时支持选择**增量更新**或**覆盖更新**环境变量 - **@cloudbase/cli 2.12.0 以下版本**:`cloudbaserc.json` 中的环境变量配置会**完全覆盖**线上已配置的环境变量 **重要**:如果您使用的是 2.12.0 以下版本,且在控制台中手动配置了环境变量,请确保在 `cloudbaserc.json` 中也包含这些配置,否则部署后原有环境变量将会丢失。 ::: ## 相关文档 - [HTTP 函数开发指南](/cloud-function/web-func) - [云函数配置](/cli-v1/functions/configs) - [Access Token 获取](/http-api/basic/access-token) --- # 云函数/日志/监控/日志 > 当前文档链接: https://docs.cloudbase.net/cloud-function/debugging/log 本文介绍如何在云开发平台上配置及查询日志。 ## 日志采集 云函数默认采集服务中标准输出内容。 ## 日志检索 1. 进入[云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf)。 2. 选择需要查看的 `云函数`,点击进入 `详情页面`。 3. 点击 `日志` 切换到日志选项卡,即可查看当前服务日志。 ![云函数日志页面](https://qcloudimg.tencent-cloud.cn/raw/8db364eb1a8ee2f5ebdc6ad06640f392.png) --- # 云函数/日志/监控/监控 > 当前文档链接: https://docs.cloudbase.net/cloud-function/debugging/monitor 本文介绍如何查看服务相关的监控。 ## 操作步骤 1. 进入[云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf)。 2. 选择需要查看的 `云函数`,点击进入 `详情页面`。 3. 点击 `监控` 切换到日志选项卡,即可查看当前服务日志。 ![云函数监控页面](https://qcloudimg.tencent-cloud.cn/raw/d1778f3e5e837b187259281f654d910b.png) ## 监控字段说明 ### 选项卡 1. 在监控选项卡中,顶部时间筛选器对页面内所有监控数据生效。例如:在顶部选择时间"7 天",则统计卡片. 统计曲线,均展示过去 7 天的数据。 2. 监控曲线图中的“粒度”指每个监控数据对应的单位时间,会随着所选时间区间变化,时间跨度长则粒度粗。曲线图上所有数值需配合粒度解读。 ### 指标 * 云函数调用次数(次): 函数/地域级别的请求次数,按粒度(1分钟、5分钟)统计求和。 * 云函数资源使用量(GBs): 1 GiB 配置内存的云函数实例,运行 1 秒。 * 云函数资源使用量(CU): 1 GiB 配置内存的云函数实例,运行 1 秒。1 GBs = 1 CU。 * 云函数流量(KB): 在函数内访问外网资源时产生对外的流量,按粒度(1分钟、5分钟)统计求和。 * 云函数错误次数(次): 函数执行后产生的错误请求次数,当前包含客户的错误次数和平台错误次数之和,按粒度(1分钟、5分钟)统计求和。 * 云函数运行时间(ms): 函数/地域级别的运行时间,指用户的函数代码从执行开始到结束的时间,按粒度(1分钟、5分钟)统计求平均。 --- # 云函数/权限管理/安全规则 > 当前文档链接: https://docs.cloudbase.net/cloud-function/security-rules **安全规则** 是 CloudBase 云函数的 **函数级权限控制** 功能,通过简单的表达式精确控制哪些用户可以调用特定的云函数。 ## 配置入口 在 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf) 点击「权限控制」按钮进入配置页面。 :::warning 适用范围 安全规则**仅对客户端调用**(例如 SDK 的`callFunction` 方法)生效,不适用于以下场景: - 管理端 API 调用 - 定时触发器 - 数据库触发器 ::: ## 基本语法 云函数安全规则配置在环境级别,环境内所有函数共享一个配置文件。配置采用 JSON 格式: ```json { "函数名或通配符": { "invoke": "表达式或布尔值" } } ``` ### 配置结构说明 | 配置项 | 说明 | 示例 | | ------------ | ---------------------------------------- | ---------------------- | | **顶级 key** | 函数名(具体函数)或 `*`(通配所有函数) | `"*"`、`"getUserInfo"` | | **操作 key** | 固定为 `invoke`,表示调用权限 | `"invoke"` | | **规则值** | 布尔值或权限表达式 | 见下方支持的规则值说明 | :::tip 必读 1. 安全规则顶级配置**必须**包含 key 为 `*` 的通配配置 2. 每个函数配置中**必须**包含 `invoke` 操作配置 3. 规则值支持 `true`、`false`、`"auth != null"` 或 `"auth.loginType != 'ANONYMOUS' && auth != null"` 4. 默认情况下,建议配置为 `"auth.loginType != 'ANONYMOUS' && auth != null"`(仅登录用户可调用,且匿名用户不可调用) ::: ### 匹配优先级 1. 优先匹配具体的函数名配置 2. 如果没有匹配到具体函数名,则使用 `*` 通配配置 ### 支持的规则值 云函数安全规则支持以下规则值: | 规则值 | 说明 | 适用场景 | | ------------------------------------------------- | ---------------------------------- | ------------------------------ | | `true` | 允许所有用户调用(包括未登录用户) | 公开接口(获取公告、配置等) | | `false` | 禁止非管理员访问 | 已废弃的函数、仅内部调用的函数 | | `"auth != null"` | 仅登录用户可调用 | 需要用户身份验证的业务函数 | | `"auth.loginType != 'ANONYMOUS' && auth != null"` | 除匿名登录外的其他身份用户可调用 | 不对访客开放的业务函数 | **支持的表达式示例**: - ✅ `"auth != null"` - 仅登录用户 - ✅ `"auth.loginType != 'ANONYMOUS' && auth != null"` - 允许除了匿名登录外的其他身份用户调用 **不支持的表达式示例**: - ❌ `"auth.uid != null"` - 不支持单独的属性访问 - ❌ `"auth != null && auth.loginType == 'WECHAT'"` - 不支持指定特定登录方式 - ❌ `"auth.openid != null || auth.uid != null"` - 不支持 `||` 运算符 如需更精细的权限控制,请在云函数内部实现业务逻辑验证。 ### 常用模板 ```json { "*": { "invoke": "auth.loginType != 'ANONYMOUS' && auth != null" }, "getPublicData": { "invoke": true } } ``` - 默认排除匿名登录用户,仅允许显式登录的用户调用 - `getPublicData` 允许所有用户调用 ```json { "*": { "invoke": "auth != null" } } ``` 所有函数默认要求用户登录后才能调用,适用于需要身份验证的业务系统。 ```json { "*": { "invoke": "auth != null" }, "getAnnouncement": { "invoke": true }, "adminFunction": { "invoke": false } } ``` - 默认要求登录 - `getAnnouncement` 公开访问 - `adminFunction` 完全禁止客户端调用 ```json { "*": { "invoke": true } } ``` > ⚠️ 警告:所有函数允许匿名调用,存在安全风险,仅适用于完全公开的接口。 ```json { "*": { "invoke": false } } ``` 所有函数禁止非管理员调用。 ## 常见问题 ### 可以针对不同用户设置不同权限吗? 安全规则支持以下规则值:`true`、`false`、`"auth != null"` 和 `"auth.loginType != 'ANONYMOUS' && auth != null"`,可以基于登录状态和是否匿名登录进行权限控制。 **不支持**基于用户角色、用户组、特定用户 ID 等更复杂的权限控制。如需更精细的权限控制,可以在云函数内部进行验证。 ### 如何排查权限错误? 当遇到 `PERMISSION_DENIED` 错误时,请按以下步骤排查: 1. **检查安全规则配置**: - 前往 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf) 查看权限控制配置 2. **确认用户登录状态**: - 使用客户端 SDK 检查用户是否已登录 - 对于需要 `"auth != null"` 的函数,确保调用前已完成登录 3. **查看错误详情**: - 详细错误信息请参考 [PERMISSION_DENIED 错误码](/error-code/PERMISSION_DENIED) --- # 云函数/高级功能/定时触发器 > 当前文档链接: https://docs.cloudbase.net/cloud-function/timer-trigger 您可以使用定时触发器结合云函数,实现定时任务的功能。 ## 设置定时触发器 进入 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev#/scf?tab=function),选择要配置的函数,点击编辑,修改表单的定时触发器选项,可以上传配置文件或配置内容,格式如下: ```json { // triggers 字段是触发器数组,最多支持10个触发器 "triggers": [ { // name: 触发器的名字,规则见下方说明 "name": "myTrigger", // type: 触发器类型,目前仅支持 timer (即定时触发器) "type": "timer", // config: 触发器配置,在定时触发器下,config 格式为 cron 表达式,规则见下方说明 "config": "0 0 2 1 * * *" } ] } ``` 在需要添加触发器的云函数目录下新建文件 config.json,格式如下: ```json { // triggers 字段是触发器数组 "triggers": [ { // name: 触发器的名字,规则见下方说明 "name": "myTrigger", // type: 触发器类型,目前仅支持 timer (即 定时触发器) "type": "timer", // config: 触发器配置,在定时触发器下,config 格式为 cron 表达式,规则见下方说明 "config": "0 0 2 1 * * *" } ] } ``` 新建完成后需要右键云函数,点击「上传触发器」才可完成触发器部署 详见 [CloudBase CLI 文档](/cli-v1/functions/trigger)。 ## 配置详解 #### 字段规则 - 定时触发器名称(name) :最大支持 60 个字符,支持 `a-z`, `A-Z`, `0-9`, `-` 和 `_`。必须以字母开头,且一个函数下不支持同名的多个定时触发器。 - 定时触发器触发周期 (config):指定的函数触发时间。填写自定义标准的 Cron 表达式来决定何时触发函数。有关 Cron 表达式的更多信息,请参考以下内容。 #### Cron 表达式 Cron 表达式有七个必需字段,按空格分隔。其中,每个字段都有相应的取值范围: | 排序 | 字段 | 值 | | ------ | ---- | --------------------------------------------------------------------------------------- | | 第一位 | 秒 | 0 - 59 的整数 | | 第二位 | 分钟 | 0 - 59 的整数 | | 第三位 | 小时 | 0 - 23 的整数 | | 第四位 | 日 | 1 - 31 的整数(需要考虑月的天数) | | 第五位 | 月 | 1 - 12 的整数或 JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV 和 DEC | | 第六位 | 星期 | 0 - 6 的整数或 MON、TUE、WED、THU、FRI、SAT 和 SUN,其中 0 指周日,1 指星期一,以此类推 | | 第七位 | 年 | 1970 - 2099 的整数 | #### 通配符 | 通配符 | 含义 | | ------ | ------------------------------------------------------------------------------------------------------------------------------ | | `,` | 代表取用逗号隔开的字符的并集。例如:在“小时”字段中 1,2,3 表示 1 点、2 点和 3 点。 | | `-` | 包含指定范围的所有值。例如:在“日”字段中,1 - 15 包含指定月份的 1 号到 15 号。 | | `*` | 表示所有值。在“小时”字段中,表示每个小时。 | | `/` | 指定增量。在“分钟”字段中,输入 1/10 以指定从第一分钟开始的每隔十分钟重复。例如,第 11 分钟、第 21 分钟和第 31 分钟,以此类推。 | :::tip 注意 在 Cron 表达式中的“日”和“星期”字段同时指定值时,两者为“或”关系,即两者的条件均生效。 ::: #### cronjob 示例 下面列举一些 Cron 表达式和相关含义: - `*/5 * * * * * *` 表示每 5 秒触发一次 - `0 0 2 1 * * *` 表示在每月的 1 日的凌晨 2 点触发 - `0 15 10 * * MON-FRI *` 表示在周一到周五每天上午 10:15 触发 - `0 0 10,14,16 * * * *` 表示在每天上午 10 点,下午 2 点,下午 4 点触发 - `0 */30 9-17 * * * *` 表示在每天上午 9 点到下午 5 点内每半小时触发 - `0 0 12 * * WED *` 表示在每个星期三中午 12 点触发 --- # 云函数/高级功能/灰度发布 > 当前文档链接: https://docs.cloudbase.net/cloud-function/gray-release CloudBase 云函数支持多版本管理功能,通过灰度发布能力可以**调整不同版本间的请求流量比例**,实现线上业务的平滑过渡、灰度验证和快速回滚,确保业务发布的稳定性和安全性。 ## 核心概念 ### 版本管理 每个云函数可以发布多个**版本**,版本是函数在特定时刻的完整快照,包含: - 函数代码 - 配置信息(超时时间、环境变量、内存规格等) - 运行时环境 ### LATEST 版本 云函数始终存在一个 `LATEST` 版本,代表最新版本。当您上传、更新或部署云函数代码时,都是在修改 `LATEST` 版本。 ### 流量分配 您可以精确控制不同版本云函数的流量比例,CloudBase 会根据设定的比例自动分发请求流量到对应版本。 ### 一致性保证 CloudBase 使用用户的全局唯一标识来保证请求的一致性: - **有用户信息的请求**:同一用户的所有请求会始终路由到同一个版本,确保用户体验的一致性 - **无用户信息的请求**:采用随机调度策略进行流量分配 :::tip 示例说明 假设云函数的 `LATEST` 版本流量占比 10%,版本 1 占比 90%。如果某个用户被分配到版本 1,则该用户对此云函数的所有后续请求都会路由到版本 1,而不是按概率随机分配。 ::: ## 灰度发布流程 ### 推荐流程 1. **创建稳定版本**:从当前 `LATEST` 版本发布一个新版本(如版本 1) 2. **切换流量**:将 `LATEST` 版本流量设置为 0%,新版本流量设置为 100% 3. **更新代码**:在 `LATEST` 版本中部署新功能代码 4. **灰度验证**:逐步调整 `LATEST` 版本的流量比例进行灰度验证 1. 发布版本 1(保存当前稳定代码) 2. 设置版本 1 流量为 100%,`LATEST` 流量为 0% 3. 在 `LATEST` 版本中部署新功能代码 4. 设置 `LATEST` 流量为 10%,版本 1 流量为 90%,开始灰度验证 5. 根据验证结果调整流量比例或进行回滚 ## 操作指南 ### 创建版本 1. 进入 [云函数管理页面](https://tcb.cloud.tencent.com/dev#/scf?tab=function) 2. 选择需要进行灰度发布的函数 3. 在「灰度配置」模块中,点击「发布新版本」 ![](https://qcloudimg.tencent-cloud.cn/raw/89bb6a84fc0b45f774a66e168dd284b8.png) 4. 输入版本描述信息,确认发布 > ⚠️ 注意:版本发布后,该版本的代码和配置将被锁定,无法再次修改。 ### 配置流量比例 在灰度配置页面中,可以实时调整不同版本的流量分配比例: > ⚠️ 注意:流量比例调整后会立即生效,请谨慎操作并及时验证效果。 ## 最佳实践 ### 首次灰度发布 对于从未使用过灰度功能的云函数,推荐按以下步骤操作: 1. **发布基准版本** ``` 当前状态:只有 LATEST 版本(100% 流量) 操作:发布版本 1,设置版本 1 流量为 100% ``` 2. **部署新功能** ``` 当前状态:版本 1(100% 流量),LATEST(0% 流量) 操作:在 LATEST 版本中部署新功能代码 ``` 3. **开始灰度** ``` 设置流量分配:LATEST(10%),版本 1(90%) 结果:10% 用户体验新功能,90% 用户使用稳定版本 ``` ### 版本回滚与全量发布 #### 快速回滚 当发现新版本存在问题时,可以快速将流量切回稳定版本: ``` 紧急回滚:将稳定版本流量设置为 100%,问题版本流量设置为 0% ``` #### 全量发布 当新版本验证通过后,可以进行全量发布: ``` 全量发布:将新版本流量设置为 100%,旧版本流量设置为 0% ``` ### 灰度策略建议 | 阶段 | LATEST 流量 | 稳定版本流量 | 说明 | | -------- | ----------- | ------------ | ------------------ | | 初始验证 | 5% | 95% | 小范围验证基本功能 | | 扩大验证 | 20% | 80% | 验证性能和稳定性 | | 准备全量 | 50% | 50% | 大规模验证 | | 全量发布 | 100% | 0% | 完成发布 | :::tip 建议 - 建议在业务低峰期进行灰度发布操作 - 密切监控新版本的错误率、响应时间等关键指标 - 准备好快速回滚方案,确保业务稳定性 ::: --- # 云函数/高级功能/层管理 > 当前文档链接: https://docs.cloudbase.net/cloud-function/layer 云函数层(Layer)是一种代码共享机制,可以将依赖库、公共代码文件等资源独立管理,实现多个函数间的代码复用。 ## 什么是层 层是云函数的一个重要特性,允许您将依赖库、运行时环境、配置文件等打包成独立的层,供多个函数共享使用。使用层可以: - **减小部署包体积**:将依赖库从函数代码中分离,保持部署包轻量化 - **提高开发效率**:公共代码只需维护一份,多个函数可复用 - **支持在线编辑**:对于 Node.js、Python 和 PHP 函数,代码包保持在 10MB 以下时可在控制台在线编辑 ## 工作原理 ### 层的创建与版本管理 - 每个层都有独立的版本管理,创建层时会生成对应的版本号 - 层的内容以压缩包形式存储,支持版本迭代和回滚 - 层可以设置兼容的运行环境,确保与函数运行时匹配 ### 层的绑定机制 - 函数与层按照具体版本进行绑定,确保运行环境的稳定性 - 单个函数最多支持绑定 5 个层 - 绑定时可以指定层的加载顺序 ### 运行时加载流程 当绑定了层的函数被触发时,系统会按以下流程加载: 1. **函数代码加载**:函数代码解压至 `/var/user/` 目录 2. **层内容加载**:所有绑定的层按顺序解压至 `/opt` 目录 3. **文件访问规则**: - 层根目录的文件:通过 `/opt/文件名` 访问 - 层子目录的文件:通过 `/opt/目录名/文件名` 访问 ### 多层加载顺序 当函数绑定多个层时: - 按绑定时设置的序号从小到大依次加载 - 后加载的层会覆盖先加载层中的同名文件 - 所有层在函数实例启动前完成加载,函数初始化时即可使用 ## 使用场景与最佳实践 ### 适用场景 - **依赖库管理**:将第三方库、SDK 等依赖统一管理 - **公共代码复用**:多个函数共享的工具类、配置文件 - **静态资源存储**:模型文件、配置文件等不常变更的资源 ### 最佳实践 ```bash # 1. 创建依赖目录 mkdir python-layer cd python-layer # 2. 安装依赖到指定目录 pip install requests -t python/ # 3. 打包成 zip 文件 zip -r python-layer.zip python/ ``` 在函数中使用: ```python import requests # 直接导入层中的依赖 def main_handler(event, context): response = requests.get('https://api.example.com') return response.json() ``` ```bash # 1. 创建依赖目录 mkdir nodejs-layer cd nodejs-layer # 2. 初始化并安装依赖 npm init -y npm install axios lodash # 3. 打包 node_modules zip -r nodejs-layer.zip node_modules/ ``` 在函数中使用: ```javascript const axios = require('axios'); // 直接引用层中的依赖 const _ = require('lodash'); exports.main_handler = async (event, context) => { const response = await axios.get('https://api.example.com'); return _.pick(response.data, ['id', 'name']); }; ``` ### 层结构建议 ``` layer.zip ├── python/ # Python 依赖目录 │ ├── requests/ │ └── urllib3/ ├── lib/ # 共享库目录 │ └── utils.py └── config/ # 配置文件目录 └── settings.json ``` ## 注意事项 :::tip 重要提醒 - 层中的文件会解压到 `/opt` 目录,函数执行期间可直接访问 - 多个层绑定时按序号顺序合并,同名文件会被序号较大的层覆盖 - 层的总大小限制为 50MB,建议合理规划层的内容 - 层版本一旦创建不可修改,需要更新时请创建新版本 ::: ## 操作指南 ### 创建层 1. **进入层管理页面** 进入 [云开发控制台/层管理](https://tcb.cloud.tencent.com/dev#/scf?tab=layer) 2. **配置层信息** 点击「新建」,填写以下信息: | 配置项 | 说明 | | -------- | ---------------------------------- | | 层名称 | 自定义层名称,建议使用有意义的命名 | | 描述 | 层的用途和内容描述,便于团队协作 | | 层代码 | 上传 zip 压缩包,最大支持 500MB | | 运行环境 | 选择兼容的运行时环境,最多 8 个 | 3. **上传层代码** 点击「上传」按钮,选择准备好的 zip 压缩包,确认后点击「确定」完成创建。 ### 绑定层到函数 1. **选择目标函数** 在云函数列表中选择需要绑定层的函数,进入函数详情页。 2. **进入层管理** 切换到「层管理」标签页,点击「绑定」按钮。 ![](https://qcloudimg.tencent-cloud.cn/raw/6f45b146c4856debe7a28ffb6b9e678a.png) 3. **选择层版本** 在弹出窗口中选择要绑定的层名称和版本: ![](https://main.qcloudimg.com/raw/4220214d87b10e17baf67bde8c0033df.jpg) - 可以同时绑定多个层(最多 5 个) - 可以调整层的加载顺序 - 建议选择稳定的层版本 4. **确认绑定** 确认配置无误后,点击「确定」完成绑定。 ### 管理层版本 - **查看版本历史**:在层详情页可查看所有历史版本 - **创建新版本**:修改层内容时创建新版本,保持向后兼容 - **删除版本**:删除不再使用的版本,释放存储空间 ## 常见问题 ### 层无法正常加载 **可能原因**: - 层的运行环境与函数不匹配 - 层文件结构不正确 - 层大小超过限制 **解决方案**: - 检查层的兼容运行环境设置 - 确认层文件按正确目录结构打包 - 优化层内容,控制在 500MB 以内 ### 依赖库导入失败 **可能原因**: - Python 依赖未安装到正确目录 - Node.js 模块路径不正确 **解决方案**: - Python:使用 `pip install -t python/` 安装到 python 目录 - Node.js:确保 node_modules 在层的根目录 ### 多层文件冲突 **可能原因**: - 不同层包含同名文件 - 层加载顺序不当 **解决方案**: - 合理规划层的内容分工 - 调整层的绑定顺序 - 使用不同目录避免文件名冲突 --- # 云函数/高级功能/预置并发 > 当前文档链接: https://docs.cloudbase.net/cloud-function/alias ## 什么是并发 **QPS(Queries Per Second)** 是衡量系统性能的重要指标,表示每秒请求数。 云函数遵循一个服务实例同一时刻仅处理一个事件的运行逻辑。 假设 A 请求平均耗时 0.02s,那么服务实例 1 秒内可以处理 1/0.02=50 个请求,即 QPS=50。如果同时有 100 个请求,则至少需要有两个服务实例同时处理请求,一个服务实例称为一个并发。 ### 并发计算公式 您可以通过以下公式估算并发数: **并发数 = 每秒请求数 × 函数运行时间(秒)** **示例:** 某业务每秒请求数为 2000,每个请求的平均耗时为 20ms(0.02s),则并发数为 2000 × 0.02 = 40,即需要有 40 个服务实例同时处理请求。 :::tip 提示 您可以在监控信息中的"运行时间"查看每个请求的平均耗时。 ::: ## 预置并发 云函数在没有请求时,会回收服务实例以节省资源。每次重新启动时会有冷启动耗时,在云函数日志中可以看到类似如下日志: ```bash Coldstart: xxxms ``` 如果您希望避免冷启动耗时,可以通过预置并发保留一个常驻实例。 预置并发是预先启动的并发实例,可以避免冷启动,提升函数响应速度。 ### 操作步骤 1. 登录 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf?tab=function) 2. 在函数列表中,单击目标函数 **操作** 列的 **预置管理** 3. 单击 **新增预置并发** 4. 选择 **函数版本**,单击 **下一步** 5. 设置 **并发实例数**,单击 **确认** :::tip 注意 从 $LATEST 版本发布新版本时,版本就是一个函数在生产版本时刻的快照,包含代码和配置(超时时间、环境变量等)。 ::: --- # 云函数/高级功能/通过 HTTP 访问云函数 > 当前文档链接: https://docs.cloudbase.net/service/access-cloud-function 通过 HTTP 网关,您可以将云函数配置为标准的 HTTP 接口,无需 SDK 即可通过 HTTP 请求调用云函数。本文档介绍如何配置云函数的 HTTP 访问以及如何处理请求和响应。 ## 前置条件 - 已创建云开发环境 - 已部署至少一个云函数 ## 配置 HTTP 访问 您可以在云函数详情页直接绑定域名(推荐),也可以在 HTTP 网关统一配置。 ### 方式一:详情页就地绑定(推荐) 1. 前往 [云函数](https://tcb.cloud.tencent.com/dev#/scf?tab=function),进入目标云函数的详情页 2. 在「自定义域名」模块点击「添加域名」按钮,弹出绑定窗口 3. 在「触发路径」中填写 HTTP 访问路径,支持 `/` 或自定义路径 4. 选择自定义域名后确认,已绑定的所有域名会展示在详情页的「域名管理」列表中 ### 方式二:在 HTTP 网关绑定 如果您更习惯从全局视角管理域名与资源的关系,也可以在 HTTP 网关统一配置: 1. 前往 [HTTP 网关](https://tcb.cloud.tencent.com/dev#/env/http-access) 2. 在「域名关联资源」模块点击「新建」按钮 3. 配置以下信息: | 配置项 | 说明 | 示例 | | ------------ | ---------------------------------------------- | ------------- | | 关联资源类型 | 选择「云函数」,然后选择目标云函数 | `hello-world` | | 域名 | 选择默认域名、自定义域名或 `*`(匹配所有域名) | 默认域名 | | 触发路径 | 设置 HTTP 访问路径,支持 `/` 或自定义路径 | `/api/hello` | > 💡 **提示**:生产环境建议绑定已备案的自定义域名,以获得完整的服务能力。配置方法请参考 [自定义域名配置](/service/custom-domain)。 ### 步骤 2:测试访问 配置完成后,访问 `域名 + 触发路径` 即可调用云函数: ```bash # 示例:访问默认域名的云函数 curl https://your-env-id..app.tcloudbase.com/api/hello ``` ## 发起 HTTP 请求 配置完成后,可以使用任何 HTTP 客户端访问云函数。 ```bash # GET 请求 curl https://your-domain/your-function-path # POST 请求 curl -X POST https://your-domain/your-function-path \ -H "Content-Type: application/json" \ -d '{"name": "CloudBase", "version": "1.0"}' ``` ```javascript // 使用 fetch API const response = await fetch('https://your-domain/your-function-path', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'CloudBase', version: '1.0' }) }); const data = await response.json(); console.log(data); ``` ```python import requests # POST 请求 response = requests.post( 'https://your-domain/your-function-path', json={'name': 'CloudBase', 'version': '1.0'} ) data = response.json() print(data) ``` ## 在云函数中处理 HTTP 请求 ### 接收请求信息 当通过 HTTP 访问云函数时,函数的 `event` 参数会包含完整的 HTTP 请求信息: ```javascript exports.main = async (event, context) => { // event 对象结构 const { path, // HTTP 请求路径,如 /api/hello httpMethod, // HTTP 请求方法,如 GET、POST、PUT、DELETE headers, // HTTP 请求头对象 queryStringParameters, // URL 查询参数对象,如 ?name=value body, // HTTP 请求体内容(字符串格式) isBase64Encoded, // body 是否为 Base64 编码 requestContext // 云开发环境相关信息 } = event; // 返回响应 return { message: 'success' }; }; ``` ### 示例:处理不同类型的请求 ```javascript exports.main = async (event) => { const { queryStringParameters } = event; // 获取查询参数 const name = queryStringParameters?.name || 'Guest'; return { statusCode: 200, body: JSON.stringify({ message: `Hello, ${name}!`, timestamp: Date.now() }) }; }; ``` **访问示例**: ```bash curl "https://your-domain/greet?name=CloudBase" # 响应: {"message":"Hello, CloudBase!","timestamp":1699999999999} ``` ```javascript exports.main = async (event) => { const { body } = event; // 解析 JSON 请求体 const data = JSON.parse(body); return { statusCode: 200, body: JSON.stringify({ received: data, processed: true }) }; }; ``` **访问示例**: ```bash curl -X POST https://your-domain/process \ -H "Content-Type: application/json" \ -d '{"userId": 123, "action": "update"}' ``` ```javascript exports.main = async (event) => { const { httpMethod, path, queryStringParameters, body } = event; // 路由处理 if (path === '/users' && httpMethod === 'GET') { // 获取用户列表 return { statusCode: 200, body: JSON.stringify([{ id: 1, name: 'User1' }]) }; } if (path.startsWith('/users/') && httpMethod === 'GET') { // 获取单个用户 const userId = path.split('/')[2]; return { statusCode: 200, body: JSON.stringify({ id: userId, name: 'User' }) }; } // 404 return { statusCode: 404, body: JSON.stringify({ error: 'Not Found' }) }; }; ``` ## 相关文档 - [HTTP 网关概述](/service/introduce) - 了解 HTTP 网关的核心功能和优势 - [快速开始](/service/quick-start) - 快速入门指南 - [路由配置](/service/routes) - 配置灵活的路由规则 - [自定义域名配置](/service/custom-domain) - 绑定自定义域名 - [云函数开发指南](/cloud-function/how-coding) - 云函数编写教程 --- # 云函数/资源集成/调用云开发资源 > 当前文档链接: https://docs.cloudbase.net/cloud-function/resource-integration/cloudbase 在云函数中可以便捷地调用云开发的各种资源,包括数据库、云存储、其他云函数等。Node.js 云函数使用 `@cloudbase/node-sdk`,其他语言使用 `HTTP API`。 ## Node.js 云函数 Node.js 云函数使用 `@cloudbase/node-sdk` 调用云开发资源,**在云函数环境中无需配置鉴权参数**,可直接调用。 ### 安装 SDK ```bash npm install @cloudbase/node-sdk --save ``` ### 初始化 在云函数中初始化 SDK,无需配置 `secretId`、`secretKey` 等鉴权参数: ```javascript const tcb = require('@cloudbase/node-sdk') const app = tcb.init({ env: tcb.SYMBOL_DEFAULT_ENV // 使用当前云函数默认环境 ID }) ``` ### 调用数据库示例 ```javascript const tcb = require('@cloudbase/node-sdk') const app = tcb.init({ env: tcb.SYMBOL_DEFAULT_ENV }) exports.main = async (event, context) => { const db = app.database() // 查询单条记录 const user = await db.collection('users') .doc('user-id-123') .get() // 查询多条记录 const todos = await db.collection('todos') .where({ completed: false }) .orderBy('createdAt', 'desc') .limit(10) .get() return { user: user.data, todos: todos.data } } ``` ```javascript const tcb = require('@cloudbase/node-sdk') const app = tcb.init({ env: tcb.SYMBOL_DEFAULT_ENV }) exports.main = async (event, context) => { const db = app.database() const result = await db.collection('todos').add({ title: '学习云开发', completed: false, createdAt: new Date() }) return { id: result.id, message: '新增成功' } } ``` ```javascript const tcb = require('@cloudbase/node-sdk') const app = tcb.init({ env: tcb.SYMBOL_DEFAULT_ENV }) exports.main = async (event, context) => { const db = app.database() // 更新单条记录 await db.collection('todos') .doc('todo-id-123') .update({ completed: true, updatedAt: new Date() }) // 批量更新 await db.collection('todos') .where({ completed: false }) .update({ priority: 'high' }) return { message: '更新成功' } } ``` ```javascript const tcb = require('@cloudbase/node-sdk') const app = tcb.init({ env: tcb.SYMBOL_DEFAULT_ENV }) exports.main = async (event, context) => { const db = app.database() // 删除单条记录 await db.collection('todos') .doc('todo-id-123') .remove() // 批量删除 await db.collection('todos') .where({ completed: true }) .remove() return { message: '删除成功' } } ``` ## 其他语言云函数 其他语言(Python、Java、Go、PHP 等)的云函数需要使用「HTTP API」调用云开发资源。 HTTP API 提供了通过 HTTP 协议访问云开发平台功能的接口,详细接口文档请参考 [HTTP API 文档](/http-api/basic/overview) ### 示例:Python 云函数调用 MySQL 数据库 获取 `access_token` 请参考 [获取AccessToken](/http-api/basic/access-token) ```python import requests import os def main_handler(event, context): # 获取环境变量 env_id = os.environ.get('TCB_ENV') # 查询 film 表中导演为 Christopher Nolan 的电影 url = f'https://{env_id}.api.tcloudbasegateway.com/v1/rdb/rest/film' params = { 'director': 'eq.Christopher Nolan', 'select': 'title,director,release_year,duration' } headers = { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' } response = requests.get(url, params=params, headers=headers) if response.status_code == 200: return { 'code': 0, 'data': response.json() } else: return { 'code': -1, 'message': response.text } ``` ```python import requests import os def main_handler(event, context): env_id = os.environ.get('TCB_ENV') # 查询 2000 年后上映的电影,时长不超过 120 分钟 url = f'https://{env_id}.api.tcloudbasegateway.com/v1/rdb/rest/film' params = { 'release_year': 'gte.2000', 'duration': 'lt.120', 'select': 'title,director,release_year,duration' } headers = { 'Authorization': 'Bearer ' } response = requests.get(url, params=params, headers=headers) return response.json() ``` ```python import requests import os def main_handler(event, context): env_id = os.environ.get('TCB_ENV') # 查询播放时长最长的前 3 部电影 url = f'https://{env_id}.api.tcloudbasegateway.com/v1/rdb/rest/film' params = { 'order': 'duration.desc', 'limit': 3, 'select': 'title,director,duration,release_year' } headers = { 'Authorization': 'Bearer ', 'Prefer': 'count=exact' # 返回总数 } response = requests.get(url, params=params, headers=headers) # 从响应头获取总数 content_range = response.headers.get('content-range', '') total = content_range.split('/')[-1] if content_range else 0 return { 'data': response.json(), 'total': total } ``` > 💡 **提示**: > - 在云函数环境中,可通过环境变量 `TCB_ENV` 获取环境 ID,`TCB_CONTEXT_KEYS` 获取访问令牌 > - 对于 Python、Java、Go 等语言,建议使用 HTTP API 的方式调用云开发资源 > - 详细的操作符、查询参数请查阅 [MySQL 数据库查询文档](/database/configuration/db/tdsql/http/query) > - 完整的 HTTP API 接口请参考 [HTTP API 文档](/http-api/basic/overview) ## 参考文档 - [Node SDK 完整文档](/api-reference/server/node-sdk/initialization) - [数据库操作](/api-reference/server/node-sdk/database/fetch) - [云存储操作](/api-reference/server/node-sdk/storage) - [云函数调用](/api-reference/server/node-sdk/functions) - [HTTP API 文档](/http-api/basic/overview) --- # 云函数/资源集成/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cloud-function/resource-integration/mysql --- title: 调用 MySQL 数据库 description: 详细介绍在云函数中连接和操作 MySQL 数据库的多种方式,包括公网连接和内网互联,提供完整的配置步骤和最佳实践 keywords: [MySQL, 数据库, 云函数, 公网连接, 内网互联, 腾讯云, 连接池, Express] --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import DocCardList from '@theme/DocCardList'; # 调用 MySQL 数据库 ## 概述 「云函数」提供了多种方式连接和操作 MySQL 数据库,满足不同场景的应用需求。根据您的数据库部署位置和网络环境,选择合适的连接方式: | 连接方式 | 使用场景 | 优势 | | -------------------- | --------------------------------- | ---------------------- | | 云开发 MySQL(推荐) | 使用云开发环境自带的 MySQL 数据库 | 配置简单,自动内网直连 | | 内网互联 | 连接腾讯云其他 MySQL 实例 | 高性能,低延迟,安全 | | 公网连接 | 连接任意可公网访问的 MySQL 实例 | 灵活性高,适用范围广 | ## 快速开始 ### 步骤 1:准备数据库 1. 登录 [云开发平台/MySQL 数据库](https://tcb.cloud.tencent.com/dev?#/db/mysql/table/default/) 2. 根据您的情况选择: - **已有数据库**:参考 [MySQL 迁移至自有账号](/database/configuration/db/tdsql/migrate-to-user-account) 文档,将数据库迁移到您的自有腾讯云账号,迁移后可支持 VPC 内网连接 - **首次使用**:系统将提示您初始化数据库,选择私有网络及子网后确认 3. 在 [数据库设置](https://tcb.cloud.tencent.com/dev?#/db/mysql/setting) 页面复制「内网连接地址」 :::tip 连接字符串格式 格式:`mysql://root:密码@内网地址:3306/tcb` ::: 1. 登录 [腾讯云 MySQL 控制台](https://console.cloud.tencent.com/cdb) 2. 确认 MySQL 实例与云函数在同一地域(推荐上海) 3. 记录实例的内网地址、端口、用户名、密码 1. 确保 MySQL 数据库已开启公网访问 2. 配置防火墙规则,允许云函数访问 3. 记录数据库的公网地址、端口、用户名、密码 :::warning 安全提醒 公网连接存在安全风险,生产环境建议使用内网连接 ::: ### 步骤 2:安装数据库驱动 在云函数项目中安装 `mysql2`: ```bash npm install mysql2 --save ``` ```bash yarn add mysql2 ``` ```bash pnpm add mysql2 ``` ### 步骤 3:配置网络连接 1. 进入 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf?tab=function) 2. 选择云函数,进入「函数配置」页面 3. 开启「高级配置/私有网络」 4. **选择云开发 MySQL 数据库所在的 VPC** :::warning 重要 配置 VPC 后,云函数默认无法访问公网。如需同时访问公网和内网,请参考 [内网互联配置](/cloud-function/function-configuration/config#内网访问vpc-配置) ::: 1. 进入 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf?tab=function) 2. 选择云函数,进入「函数配置」页面 3. 开启「高级配置/私有网络」 4. **选择目标 VPC**(MySQL 实例所在的 VPC) :::tip 查看 VPC 信息 在腾讯云 MySQL 控制台的实例详情页面可查看 VPC 和子网信息 ::: 无需配置网络,云函数默认可访问公网 ### 步骤 4:编写云函数代码 ```javascript const mysql = require('mysql2/promise'); // 全局连接池 let pool; function getPool() { if (!pool) { pool = mysql.createPool({ host: process.env.DB_HOST, port: process.env.DB_PORT || 3306, user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD, database: process.env.DB_NAME || 'tcb', waitForConnections: true, connectionLimit: 5, queueLimit: 0, acquireTimeout: 60000, timeout: 60000, charset: 'utf8mb4' }); } return pool; } exports.main = async (event, context) => { try { const pool = getPool(); const connection = await pool.getConnection(); try { const [rows] = await connection.query('SELECT * FROM persons LIMIT 10'); return { success: true, data: rows }; } finally { connection.release(); } } catch (error) { console.error('数据库操作失败:', error); return { success: false, error: error.message }; } }; ``` ### 步骤 5:配置环境变量 在云函数的「环境变量」中配置: | 环境变量 | 说明 | 示例值 | | ------------- | ---------- | ------------------------------- | | `DB_HOST` | 数据库地址 | `gz-xxxxx.mysql.tencentcdb.com` | | `DB_PORT` | 端口 | `3306` | | `DB_USER` | 用户名 | `root` | | `DB_PASSWORD` | 密码 | `your_password` | | `DB_NAME` | 数据库名称 | `tcb` | :::tip 使用连接字符串 也可以配置 `CONNECTION_URI` 环境变量,直接使用完整连接字符串 ::: ### 步骤 6:测试连接 部署后,在云函数控制台点击「测试」按钮,如果连接成功,将返回数据库中的记录。 ## 连接方式详解 ### 配置差异对比 三种连接方式的**核心差异**仅在于网络配置,代码实现完全相同: | 连接方式 | 网络配置 | 数据库地址 | 适用场景 | | ------------ | -------------------------- | ---------- | ------------------ | | 云开发 MySQL | 配置 VPC(数据库所在 VPC) | 内网地址 | 云开发项目首选 | | 内网互联 | 开启内网互联(目标 VPC) | 内网地址 | 已有腾讯云 MySQL | | 公网连接 | 无需配置 | 公网地址 | 第三方或自建数据库 | :::tip 配置建议 - **云开发 MySQL**:直接使用「快速开始」即可,最简单 - **腾讯云 MySQL**:仅需调整网络配置为「内网互联」,其他步骤相同 - **公网 MySQL**:无需网络配置,但需确保数据库已开启公网访问 ::: ### 公网连接特殊说明 #### 安全配置 :::warning 安全提醒 公网连接存在安全风险,请确保: - 使用强密码 - 配置防火墙规则,限制访问来源 - 定期更新数据库版本和安全补丁 - 生产环境建议使用内网连接 ::: #### 示例表结构 如需创建测试表,可参考以下 SQL: ```sql CREATE TABLE persons ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, age INT NOT NULL, email VARCHAR(255) UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 插入测试数据 INSERT INTO persons (name, age, email) VALUES ('张三', 25, 'zhangsan@example.com'), ('李四', 30, 'lisi@example.com'); ``` ## 数据库操作 ### 基础 CRUD 操作 在云函数中实现完整的增删改查: ```javascript const mysql = require('mysql2/promise'); let pool; function getPool() { if (!pool) { pool = mysql.createPool({ host: process.env.DB_HOST, port: process.env.DB_PORT || 3306, user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD, database: process.env.DB_NAME || 'tcb', connectionLimit: 5, waitForConnections: true, charset: 'utf8mb4' }); } return pool; } exports.main = async (event, context) => { const { action, data } = event; try { const pool = getPool(); const connection = await pool.getConnection(); try { let result; switch (action) { case 'list': // 查询列表 const [rows] = await connection.query( 'SELECT * FROM persons ORDER BY created_at DESC LIMIT 10' ); result = { success: true, data: rows }; break; case 'create': // 创建记录 const { name, age, email } = data; const [insertResult] = await connection.query( 'INSERT INTO persons (name, age, email) VALUES (?, ?, ?)', [name, age, email] ); result = { success: true, data: { id: insertResult.insertId, name, age, email } }; break; case 'update': // 更新记录 const { id, ...updateData } = data; const [updateResult] = await connection.query( 'UPDATE persons SET ? WHERE id = ?', [updateData, id] ); result = { success: true, affectedRows: updateResult.affectedRows }; break; case 'delete': // 删除记录 const [deleteResult] = await connection.query( 'DELETE FROM persons WHERE id = ?', [data.id] ); result = { success: true, affectedRows: deleteResult.affectedRows }; break; default: result = { success: false, error: '不支持的操作' }; } return result; } finally { connection.release(); } } catch (error) { console.error('数据库操作失败:', error); return { success: false, error: error.message }; } }; ``` ### Web 应用集成(Express/Koa) ```javascript const express = require('express'); const mysql = require('mysql2/promise'); const router = express.Router(); // 创建连接池 const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, port: process.env.DB_PORT || 3306, connectionLimit: 10, charset: 'utf8mb4' }); // 健康检查 router.get('/health', async (req, res) => { try { const connection = await pool.getConnection(); await connection.ping(); connection.release(); res.json({ status: 'healthy' }); } catch (error) { res.status(500).json({ status: 'unhealthy', error: error.message }); } }); // 分页查询 router.get('/users', async (req, res) => { const { page = 1, limit = 10 } = req.query; const offset = (page - 1) * limit; try { const connection = await pool.getConnection(); try { const [countResult] = await connection.query('SELECT COUNT(*) as total FROM persons'); const [rows] = await connection.query( 'SELECT * FROM persons ORDER BY created_at DESC LIMIT ? OFFSET ?', [parseInt(limit), parseInt(offset)] ); res.json({ success: true, data: rows, pagination: { page: parseInt(page), limit: parseInt(limit), total: countResult[0].total } }); } finally { connection.release(); } } catch (error) { console.error('查询失败:', error); res.status(500).json({ success: false, error: '查询失败' }); } }); // 创建用户 router.post('/users', async (req, res) => { const { name, age, email } = req.body; if (!name || !age || !email) { return res.status(400).json({ error: '缺少必要参数' }); } try { const connection = await pool.getConnection(); try { const [result] = await connection.query( 'INSERT INTO persons (name, age, email) VALUES (?, ?, ?)', [name, age, email] ); res.status(201).json({ success: true, data: { id: result.insertId, name, age, email } }); } finally { connection.release(); } } catch (error) { if (error.code === 'ER_DUP_ENTRY') { return res.status(409).json({ error: '邮箱已存在' }); } res.status(500).json({ error: '创建失败' }); } }); // 更新用户 router.put('/users/:id', async (req, res) => { const { id } = req.params; const { name, age, email } = req.body; try { const connection = await pool.getConnection(); try { const [result] = await connection.query( 'UPDATE persons SET name = ?, age = ?, email = ? WHERE id = ?', [name, age, email, id] ); if (result.affectedRows === 0) { return res.status(404).json({ error: '用户不存在' }); } res.json({ success: true }); } finally { connection.release(); } } catch (error) { res.status(500).json({ error: '更新失败' }); } }); // 删除用户 router.delete('/users/:id', async (req, res) => { const { id } = req.params; try { const connection = await pool.getConnection(); try { const [result] = await connection.query('DELETE FROM persons WHERE id = ?', [id]); if (result.affectedRows === 0) { return res.status(404).json({ error: '用户不存在' }); } res.json({ success: true }); } finally { connection.release(); } } catch (error) { res.status(500).json({ error: '删除失败' }); } }); module.exports = router; ``` ```javascript const Koa = require('koa'); const Router = require('koa-router'); const bodyParser = require('koa-bodyparser'); const mysql = require('mysql2/promise'); const app = new Koa(); const router = new Router(); const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, port: process.env.DB_PORT || 3306, connectionLimit: 10 }); router.get('/users', async (ctx) => { try { const connection = await pool.getConnection(); try { const [rows] = await connection.query('SELECT * FROM persons LIMIT 10'); ctx.body = { success: true, data: rows }; } finally { connection.release(); } } catch (error) { ctx.status = 500; ctx.body = { success: false, error: '查询失败' }; } }); app.use(bodyParser()); app.use(router.routes()); module.exports = app; ``` ### 测试 API ```bash # 健康检查 curl https://your-domain.com/health # 获取用户列表 curl https://your-domain.com/users # 创建用户 curl -X POST https://your-domain.com/users \ -H "Content-Type: application/json" \ -d '{"name":"张三","age":25,"email":"test@example.com"}' # 更新用户 curl -X PUT https://your-domain.com/users/1 \ -H "Content-Type: application/json" \ -d '{"name":"李四","age":30,"email":"lisi@example.com"}' # 删除用户 curl -X DELETE https://your-domain.com/users/1 ``` ```javascript // 获取用户列表 async function getUsers() { const response = await fetch('https://your-domain.com/users'); const result = await response.json(); console.log(result); } // 创建用户 async function createUser() { const response = await fetch('https://your-domain.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: '新用户', age: 25, email: 'newuser@example.com' }) }); const result = await response.json(); console.log(result); } ``` ## 最佳实践 ### 连接池配置优化 ```javascript const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, port: process.env.DB_PORT ? Number(process.env.DB_PORT) : 3306, // 连接池配置 connectionLimit: 10, // 最大连接数 queueLimit: 0, // 队列限制,0 表示无限制 acquireTimeout: 60000, // 获取连接超时时间 timeout: 60000, // 查询超时时间 // 重连配置 reconnect: true, // 自动重连 // 字符集配置 charset: 'utf8mb4', // 支持 emoji 和特殊字符 // SSL 配置(生产环境推荐) ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, // 时区配置 timezone: '+08:00' }); ``` ### 错误处理和重试机制 ```javascript // 带重试的数据库操作 async function executeWithRetry(operation, maxRetries = 3) { let lastError; for (let i = 0; i < maxRetries; i++) { try { return await operation(); } catch (error) { lastError = error; // 判断是否为可重试的错误 if (isRetryableError(error) && i < maxRetries - 1) { const delay = Math.pow(2, i) * 1000; // 指数退避 await new Promise(resolve => setTimeout(resolve, delay)); continue; } throw error; } } throw lastError; } function isRetryableError(error) { const retryableCodes = [ 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ER_LOCK_WAIT_TIMEOUT', 'PROTOCOL_CONNECTION_LOST' ]; return retryableCodes.includes(error.code); } // 使用示例 router.get('/users', async (req, res) => { try { const result = await executeWithRetry(async () => { const connection = await pool.getConnection(); try { const [rows] = await connection.query('SELECT * FROM persons'); return rows; } finally { connection.release(); } }); res.json({ success: true, data: result }); } catch (error) { console.error('查询失败:', error); res.status(500).json({ success: false, error: '查询失败' }); } }); ``` ### 事务处理 ```javascript // 事务处理示例 async function transferMoney(fromUserId, toUserId, amount) { const connection = await pool.getConnection(); try { await connection.beginTransaction(); // 检查发送方余额 const [fromUser] = await connection.query( 'SELECT balance FROM users WHERE id = ? FOR UPDATE', [fromUserId] ); if (fromUser[0].balance < amount) { throw new Error('余额不足'); } // 扣除发送方余额 await connection.query( 'UPDATE users SET balance = balance - ? WHERE id = ?', [amount, fromUserId] ); // 增加接收方余额 await connection.query( 'UPDATE users SET balance = balance + ? WHERE id = ?', [amount, toUserId] ); // 记录转账日志 await connection.query( 'INSERT INTO transfer_logs (from_user_id, to_user_id, amount, created_at) VALUES (?, ?, ?, NOW())', [fromUserId, toUserId, amount] ); await connection.commit(); return { success: true, message: '转账成功' }; } catch (error) { await connection.rollback(); throw error; } finally { connection.release(); } } ``` ### 性能监控 ```javascript // 查询性能监控 function withPerformanceMonitoring(queryFunction) { return async function(...args) { const startTime = Date.now(); const queryId = Math.random().toString(36).substr(2, 9); console.log(`[${queryId}] 查询开始:`, args[0]); try { const result = await queryFunction.apply(this, args); const duration = Date.now() - startTime; console.log(`[${queryId}] 查询完成: ${duration}ms`); // 记录慢查询 if (duration > 1000) { console.warn(`[${queryId}] 慢查询检测: ${duration}ms`, { sql: args[0], params: args[1] }); } return result; } catch (error) { const duration = Date.now() - startTime; console.error(`[${queryId}] 查询失败: ${duration}ms`, { error: error.message, sql: args[0] }); throw error; } }; } // 包装连接的查询方法 const originalQuery = pool.query.bind(pool); pool.query = withPerformanceMonitoring(originalQuery); ``` ## 常见问题 ### 连接问题
连接超时怎么办? **可能原因:** 1. 网络延迟过高 2. 数据库服务器负载过高 3. 防火墙阻止连接 **解决方案:** ```javascript // 增加超时时间 const pool = mysql.createPool({ // ... 其他配置 acquireTimeout: 120000, // 增加到 2 分钟 timeout: 120000, // 查询超时 2 分钟 connectTimeout: 60000 // 连接超时 1 分钟 }); ```
连接数过多怎么处理? **解决方案:** 1. 优化连接池配置 2. 及时释放连接 3. 使用连接监控 ```javascript // 监控连接池状态 setInterval(() => { console.log('连接池状态:', { 总连接数: pool._allConnections.length, 空闲连接数: pool._freeConnections.length, 使用中连接数: pool._acquiringConnections.length }); }, 30000); ```
### 性能问题
查询速度慢怎么优化? **优化策略:** 1. 添加适当的索引 2. 优化 SQL 查询 3. 使用连接池 4. 实施查询缓存 ```sql -- 添加索引示例 CREATE INDEX idx_persons_email ON persons(email); CREATE INDEX idx_persons_age ON persons(age); CREATE INDEX idx_persons_created_at ON persons(created_at); ```
### 安全问题
如何防止 SQL 注入? **防护措施:** 1. 始终使用参数化查询 2. 验证输入数据 3. 使用最小权限原则 ```javascript // 正确的参数化查询 const [rows] = await connection.query( 'SELECT * FROM persons WHERE name = ? AND age > ?', [userName, minAge] ); // 错误的字符串拼接(容易受到 SQL 注入攻击) // const query = `SELECT * FROM persons WHERE name = '${userName}'`; ```
:::tip 性能建议 - 在生产环境中启用连接池以提高性能 - 对于频繁查询的字段添加索引 - 使用事务确保数据一致性 - 定期监控数据库性能和慢查询日志 ::: :::warning 安全提醒 - 避免在代码中硬编码数据库密码 - 使用参数化查询防止 SQL 注入 - 定期更新数据库版本和安全补丁 - 配置适当的防火墙规则 ::: --- # 云函数/资源集成/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cloud-function/resource-integration/tencentcloud --- title: 内网调用腾讯云其它资源 description: 详细介绍如何通过内网互联功能在云函数中安全访问腾讯云账号下的各种数据库资源,包括 MySQL、Redis、Kafka 等服务的配置和使用方法 keywords: [内网互联, 腾讯云, 数据库, MySQL, Redis, Kafka, VPC, 云函数, 安全访问] --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import DocCardList from '@theme/DocCardList'; # 内网调用腾讯云其它资源 ## 概述 如果您希望您的云函数可以安全地访问您的腾讯云账号下的其它资源,如 MySQL、Redis、Kafka,甚至其它 CVM 上部署的服务等,您可以使用云函数的「内网互联」功能。开启内网互联功能之后,即可在云函数中通过内网 IP 访问该 VPC 下的相关资源。 ### 内网互联的优势 - **安全性高**:数据传输不经过公网,降低安全风险 - **性能优异**:内网延迟低,带宽大,访问速度快 - **成本节约**:避免公网流量费用 - **稳定可靠**:内网环境更加稳定,减少网络波动影响 ## 配置内网互联 ### 前提条件 1. 您的腾讯云账号下已有 VPC 网络 2. 目标资源(如数据库实例)已部署在该 VPC 中 3. 云函数和目标资源在同一地域 ### 配置步骤 #### 1. 开启内网互联 在云函数控制台中配置内网互联: 1. 登录 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf?tab=function) 2. 选择对应的函数,进入**函数配置**页面 3. 在**网络配置**部分,点击**编辑** 4. 开启**内网互联**开关 5. 选择目标 VPC 和子网 6. 保存配置 #### 2. 配置安全组 确保安全组规则允许云函数访问目标资源: ```bash # 示例:允许云函数访问 MySQL(端口 3306) 入站规则: - 协议:TCP - 端口:3306 - 来源:云函数所在子网的 CIDR(如 10.0.1.0/24) # 示例:允许云函数访问 Redis(端口 6379) 入站规则: - 协议:TCP - 端口:6379 - 来源:云函数所在子网的 CIDR ``` #### 3. 获取内网地址 在对应的云服务控制台获取资源的内网访问地址: | 服务类型 | 控制台位置 | 内网地址示例 | | -------- | -------------------------- | ----------------- | | MySQL | 数据库 MySQL > 实例详情 | `10.0.1.100:3306` | | Redis | 数据库 Redis > 实例详情 | `10.0.1.101:6379` | | Kafka | 消息队列 CKafka > 实例详情 | `10.0.1.102:9092` | | CVM | 云服务器 CVM > 实例详情 | `10.0.1.103:80` | ## 访问数据库服务 ### MySQL 数据库 ```javascript const mysql = require('mysql2/promise'); // 使用内网地址连接 MySQL const pool = mysql.createPool({ host: '10.0.1.100', // MySQL 内网地址 port: 3306, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, connectionLimit: 10, acquireTimeout: 60000, timeout: 60000 }); exports.main = async (event, context) => { try { const connection = await pool.getConnection(); try { // 执行查询 const [rows] = await connection.query('SELECT * FROM users LIMIT 10'); return { statusCode: 200, body: { success: true, data: rows, message: '查询成功' } }; } finally { connection.release(); } } catch (error) { console.error('MySQL 连接失败:', error); return { statusCode: 500, body: { success: false, error: error.message } }; } }; ``` ```python import pymysql import json import os def main_handler(event, context): try: # 使用内网地址连接 MySQL connection = pymysql.connect( host='10.0.1.100', # MySQL 内网地址 port=3306, user=os.environ['DB_USER'], password=os.environ['DB_PASSWORD'], database=os.environ['DB_NAME'], charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor ) with connection: with connection.cursor() as cursor: # 执行查询 cursor.execute("SELECT * FROM users LIMIT 10") result = cursor.fetchall() return { 'statusCode': 200, 'body': json.dumps({ 'success': True, 'data': result, 'message': '查询成功' }, ensure_ascii=False) } except Exception as e: print(f'MySQL 连接失败: {str(e)}') return { 'statusCode': 500, 'body': json.dumps({ 'success': False, 'error': str(e) }, ensure_ascii=False) } ``` ### Redis 缓存 ```javascript const redis = require('redis'); // 创建 Redis 客户端(使用内网地址) const client = redis.createClient({ host: '10.0.1.101', // Redis 内网地址 port: 6379, password: process.env.REDIS_PASSWORD, db: 0, retry_strategy: (options) => { if (options.error && options.error.code === 'ECONNREFUSED') { return new Error('Redis 服务器拒绝连接'); } if (options.total_retry_time > 1000 * 60 * 60) { return new Error('重试时间超过 1 小时'); } if (options.attempt > 10) { return undefined; } return Math.min(options.attempt * 100, 3000); } }); exports.main = async (event, context) => { try { // 连接 Redis await client.connect(); const { action, key, value } = event; let result; switch (action) { case 'get': result = await client.get(key); break; case 'set': await client.set(key, value, 'EX', 3600); // 设置 1 小时过期 result = 'OK'; break; case 'del': result = await client.del(key); break; case 'exists': result = await client.exists(key); break; default: throw new Error('不支持的操作'); } return { statusCode: 200, body: { success: true, data: result, message: '操作成功' } }; } catch (error) { console.error('Redis 操作失败:', error); return { statusCode: 500, body: { success: false, error: error.message } }; } finally { await client.quit(); } }; ``` ```python import redis import json import os def main_handler(event, context): try: # 使用内网地址连接 Redis r = redis.Redis( host='10.0.1.101', # Redis 内网地址 port=6379, password=os.environ.get('REDIS_PASSWORD'), db=0, decode_responses=True, socket_timeout=5, socket_connect_timeout=5 ) action = event.get('action') key = event.get('key') value = event.get('value') if action == 'get': result = r.get(key) elif action == 'set': r.setex(key, 3600, value) # 设置 1 小时过期 result = 'OK' elif action == 'del': result = r.delete(key) elif action == 'exists': result = r.exists(key) else: raise ValueError('不支持的操作') return { 'statusCode': 200, 'body': json.dumps({ 'success': True, 'data': result, 'message': '操作成功' }, ensure_ascii=False) } except Exception as e: print(f'Redis 操作失败: {str(e)}') return { 'statusCode': 500, 'body': json.dumps({ 'success': False, 'error': str(e) }, ensure_ascii=False) } ``` ### Kafka 消息队列 ```javascript const { Kafka } = require('kafkajs'); // 创建 Kafka 客户端(使用内网地址) const kafka = Kafka({ clientId: 'scf-kafka-client', brokers: ['10.0.1.102:9092'], // Kafka 内网地址 sasl: { mechanism: 'plain', username: process.env.KAFKA_USERNAME, password: process.env.KAFKA_PASSWORD } }); exports.main = async (event, context) => { const { action, topic, message, groupId } = event; try { if (action === 'produce') { // 生产消息 const producer = kafka.producer(); await producer.connect(); await producer.send({ topic: topic, messages: [{ key: Date.now().toString(), value: JSON.stringify(message), timestamp: Date.now() }] }); await producer.disconnect(); return { statusCode: 200, body: { success: true, message: '消息发送成功' } }; } else if (action === 'consume') { // 消费消息 const consumer = kafka.consumer({ groupId: groupId || 'scf-group' }); await consumer.connect(); await consumer.subscribe({ topic: topic }); const messages = []; await consumer.run({ eachMessage: async ({ topic, partition, message }) => { messages.push({ topic, partition, offset: message.offset, key: message.key?.toString(), value: message.value?.toString(), timestamp: message.timestamp }); // 限制消息数量,避免超时 if (messages.length >= 10) { await consumer.stop(); } } }); // 等待一段时间收集消息 await new Promise(resolve => setTimeout(resolve, 5000)); await consumer.disconnect(); return { statusCode: 200, body: { success: true, data: messages, message: '消息消费成功' } }; } } catch (error) { console.error('Kafka 操作失败:', error); return { statusCode: 500, body: { success: false, error: error.message } }; } }; ``` ```python from kafka import KafkaProducer, KafkaConsumer import json import os from datetime import datetime def main_handler(event, context): action = event.get('action') topic = event.get('topic') try: if action == 'produce': # 生产消息 producer = KafkaProducer( bootstrap_servers=['10.0.1.102:9092'], # Kafka 内网地址 security_protocol='SASL_PLAINTEXT', sasl_mechanism='PLAIN', sasl_plain_username=os.environ['KAFKA_USERNAME'], sasl_plain_password=os.environ['KAFKA_PASSWORD'], value_serializer=lambda v: json.dumps(v).encode('utf-8') ) message = event.get('message', {}) message['timestamp'] = datetime.now().isoformat() future = producer.send(topic, message) producer.flush() producer.close() return { 'statusCode': 200, 'body': json.dumps({ 'success': True, 'message': '消息发送成功' }, ensure_ascii=False) } elif action == 'consume': # 消费消息 consumer = KafkaConsumer( topic, bootstrap_servers=['10.0.1.102:9092'], security_protocol='SASL_PLAINTEXT', sasl_mechanism='PLAIN', sasl_plain_username=os.environ['KAFKA_USERNAME'], sasl_plain_password=os.environ['KAFKA_PASSWORD'], group_id=event.get('groupId', 'scf-group'), value_deserializer=lambda m: json.loads(m.decode('utf-8')), consumer_timeout_ms=5000 # 5 秒超时 ) messages = [] for message in consumer: messages.append({ 'topic': message.topic, 'partition': message.partition, 'offset': message.offset, 'key': message.key.decode('utf-8') if message.key else None, 'value': message.value, 'timestamp': message.timestamp }) # 限制消息数量 if len(messages) >= 10: break consumer.close() return { 'statusCode': 200, 'body': json.dumps({ 'success': True, 'data': messages, 'message': '消息消费成功' }, ensure_ascii=False) } except Exception as e: print(f'Kafka 操作失败: {str(e)}') return { 'statusCode': 500, 'body': json.dumps({ 'success': False, 'error': str(e) }, ensure_ascii=False) } ``` ## 访问其他服务 ### CVM 服务器 ```javascript const axios = require('axios'); exports.main = async (event, context) => { try { // 访问 CVM 上的 HTTP 服务(使用内网地址) const response = await axios({ method: 'GET', url: 'http://10.0.1.103:8080/api/data', // CVM 内网地址 timeout: 10000, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.API_TOKEN}` } }); return { statusCode: 200, body: { success: true, data: response.data, message: 'CVM 服务调用成功' } }; } catch (error) { console.error('CVM 服务调用失败:', error); return { statusCode: 500, body: { success: false, error: error.message } }; } }; ``` ## 最佳实践 ### 连接池管理 ```javascript // 全局连接池,避免每次调用都创建新连接 let mysqlPool; let redisClient; function getMysqlPool() { if (!mysqlPool) { mysqlPool = mysql.createPool({ host: process.env.MYSQL_HOST, port: 3306, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE, connectionLimit: 5, // 云函数环境建议较小的连接数 acquireTimeout: 60000, timeout: 60000, reconnect: true }); } return mysqlPool; } function getRedisClient() { if (!redisClient) { redisClient = redis.createClient({ host: process.env.REDIS_HOST, port: 6379, password: process.env.REDIS_PASSWORD, retry_strategy: (options) => { if (options.attempt > 3) return undefined; return Math.min(options.attempt * 100, 3000); } }); } return redisClient; } ``` ### 错误处理和重试 ```javascript // 带重试机制的数据库操作 async function executeWithRetry(operation, maxRetries = 3) { let lastError; for (let i = 0; i < maxRetries; i++) { try { return await operation(); } catch (error) { lastError = error; // 判断是否为可重试的错误 if (isRetryableError(error) && i < maxRetries - 1) { const delay = Math.pow(2, i) * 1000; // 指数退避 await new Promise(resolve => setTimeout(resolve, delay)); continue; } throw error; } } throw lastError; } function isRetryableError(error) { const retryableCodes = [ 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ]; return retryableCodes.includes(error.code) || error.message.includes('Connection lost'); } ``` ### 安全配置 ```javascript // 环境变量配置示例 const config = { mysql: { host: process.env.MYSQL_HOST, port: parseInt(process.env.MYSQL_PORT) || 3306, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE }, redis: { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT) || 6379, password: process.env.REDIS_PASSWORD }, kafka: { brokers: process.env.KAFKA_BROKERS?.split(',') || [], username: process.env.KAFKA_USERNAME, password: process.env.KAFKA_PASSWORD } }; // 配置验证 function validateConfig() { const required = [ 'MYSQL_HOST', 'MYSQL_USER', 'MYSQL_PASSWORD', 'MYSQL_DATABASE', 'REDIS_HOST', 'REDIS_PASSWORD', 'KAFKA_BROKERS', 'KAFKA_USERNAME', 'KAFKA_PASSWORD' ]; const missing = required.filter(key => !process.env[key]); if (missing.length > 0) { throw new Error(`缺少必要的环境变量: ${missing.join(', ')}`); } } ``` ### 性能监控 ```javascript // 性能监控装饰器 function withMonitoring(operation, operationName) { return async function(...args) { const startTime = Date.now(); const requestId = Math.random().toString(36).substr(2, 9); console.log(`[${requestId}] ${operationName} 开始`); try { const result = await operation.apply(this, args); const duration = Date.now() - startTime; console.log(`[${requestId}] ${operationName} 完成: ${duration}ms`); // 记录慢操作 if (duration > 5000) { console.warn(`[${requestId}] 慢操作检测: ${operationName} ${duration}ms`); } return result; } catch (error) { const duration = Date.now() - startTime; console.error(`[${requestId}] ${operationName} 失败: ${duration}ms`, error.message); throw error; } }; } // 使用示例 const monitoredMysqlQuery = withMonitoring( async (sql, params) => { const pool = getMysqlPool(); const connection = await pool.getConnection(); try { return await connection.query(sql, params); } finally { connection.release(); } }, 'MySQL查询' ); ``` ## 故障排查 ### 常见问题
无法连接到内网资源 **可能原因:** 1. 内网互联未正确配置 2. 安全组规则阻止访问 3. 目标服务未启动或地址错误 **排查步骤:** 1. 检查云函数的 VPC 配置是否正确 2. 验证安全组规则是否允许相应端口 3. 确认目标资源的内网地址和端口 4. 在云函数中使用 ping 或 telnet 测试连通性 ```javascript // 连通性测试代码 const net = require('net'); function testConnection(host, port, timeout = 5000) { return new Promise((resolve, reject) => { const socket = new net.Socket(); socket.setTimeout(timeout); socket.on('connect', () => { socket.destroy(); resolve(true); }); socket.on('timeout', () => { socket.destroy(); reject(new Error('连接超时')); }); socket.on('error', (error) => { reject(error); }); socket.connect(port, host); }); } ```
连接频繁断开 **解决方案:** 1. 配置连接池和重连机制 2. 适当设置超时时间 3. 实现健康检查 ```javascript // 健康检查示例 async function healthCheck() { try { await testConnection(process.env.MYSQL_HOST, 3306); await testConnection(process.env.REDIS_HOST, 6379); return { status: 'healthy' }; } catch (error) { return { status: 'unhealthy', error: error.message }; } } ```
### 监控和日志 ```javascript // 统一日志记录 function logger(level, message, extra = {}) { const logEntry = { timestamp: new Date().toISOString(), level, message, requestId: context.requestId, ...extra }; console.log(JSON.stringify(logEntry)); } // 使用示例 exports.main = async (event, context) => { logger('INFO', '函数开始执行', { event }); try { // 业务逻辑 const result = await processRequest(event); logger('INFO', '函数执行成功', { result }); return result; } catch (error) { logger('ERROR', '函数执行失败', { error: error.message, stack: error.stack }); throw error; } }; ``` ## 相关文档 :::tip 提示 - 内网互联功能仅支持同一地域的资源访问 - 建议使用连接池来提高性能和资源利用率 - 重要操作务必实现重试机制和错误处理 - 定期监控连接状态和性能指标 ::: :::warning 注意 - 确保安全组规则配置正确,避免安全风险 - 内网地址可能会发生变化,建议使用域名或配置中心 - 注意云函数的执行时间限制,避免长时间连接 - 生产环境中务必配置适当的超时时间和重试策略 ::: --- # 云函数/框架及示例/Express > 当前文档链接: https://docs.cloudbase.net/cloud-function/frameworks-examples/express [Express](https://expressjs.com/) 是一个轻量级、灵活的 Node.js Web 框架,以其简单易用和高度可扩展著称。它提供了简洁的 API 设计,支持中间件机制,能快速构建 RESTful API 或全栈应用。Express 拥有丰富的插件生态,可轻松集成数据库、身份验证等功能,同时保持高性能和低学习成本,是 Node.js 开发者的首选框架之一。 本指南介绍如何在 CloudBase HTTP 云函数上部署 Express 应用程序。 ## 前置条件 在开始之前,请确保您已经: - 安装了 [Node.js 18.x](https://nodejs.org/) 或更高版本 - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Node.js 和 Express 开发知识 ## 第一步:创建 Express 应用 > 💡 **提示**:如果您已经有一个 Express 应用,可以跳过此步骤。 ### 创建项目目录 ```bash mkdir express-cloudbase cd express-cloudbase ``` ### 使用 Express Generator 创建应用 ```bash # 使用 Express Generator 创建应用 npx express-generator --view=pug express-app # 进入项目目录 cd express-app # 安装依赖 npm install ``` 这将创建一个使用 Pug 作为视图引擎的 Express 应用程序。 ### 本地测试应用 启动开发服务器: ```bash npm start ``` 打开浏览器访问 `http://localhost:3000`,您应该能看到 Express 欢迎页面。 ## 第二步:添加 API 路由 让我们创建一个 RESTful API 来演示 Express 的功能。 ### 创建用户路由 在 `routes` 目录下创建 `users.js` 文件: ```javascript const express = require("express"); const router = express.Router(); // 模拟用户数据 const users = [ { id: 1, name: "zhangsan", email: "zhangsan@example.com" }, { id: 2, name: "lisi", email: "lisi@example.com" }, { id: 3, name: "wangwu", email: "wangwu@example.com" }, ]; /* GET users listing */ router.get("/", function (req, res, next) { const { page = 1, limit = 10 } = req.query; const startIndex = (page - 1) * limit; const endIndex = startIndex + parseInt(limit); const paginatedUsers = users.slice(startIndex, endIndex); res.json({ success: true, data: { total: users.length, page: parseInt(page), limit: parseInt(limit), items: paginatedUsers, }, }); }); /* GET user by ID */ router.get("/:id", function (req, res, next) { const userId = parseInt(req.params.id); const user = users.find((u) => u.id === userId); if (!user) { return res.status(404).json({ success: false, message: "User not found", }); } res.json({ success: true, data: user, }); }); /* POST create user */ router.post("/", function (req, res, next) { const { name, email } = req.body; if (!name || !email) { return res.status(400).json({ success: false, message: "Name and email are required", }); } const newUser = { id: users.length + 1, name, email, }; users.push(newUser); res.status(201).json({ success: true, data: newUser, }); }); module.exports = router; ``` ### 创建健康检查路由 在 `routes` 目录下创建 `health.js` 文件: ```javascript const express = require("express"); const router = express.Router(); /* GET health check */ router.get("/", function (req, res, next) { res.json({ status: "healthy", timestamp: new Date().toISOString(), framework: "Express", version: process.env.npm_package_version || "1.0.0", node_version: process.version, }); }); module.exports = router; ``` ### 更新应用配置 编辑 `app.js` 文件,添加新的路由和中间件: ```javascript var createError = require("http-errors"); var express = require("express"); var path = require("path"); var cookieParser = require("cookie-parser"); var logger = require("morgan"); var indexRouter = require("./routes/index"); var usersRouter = require("./routes/users"); var healthRouter = require("./routes/health"); var app = express(); // view engine setup app.set("views", path.join(__dirname, "views")); app.set("view engine", "pug"); app.use(logger("combined")); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, "public"))); // 路由配置 app.use("/", indexRouter); app.use("/api/users", usersRouter); app.use("/health", healthRouter); // 404 错误处理 app.use(function (req, res, next) { next(createError(404)); }); // 全局错误处理 app.use(function (err, req, res, next) { // 设置错误信息,只在开发环境提供详细错误 res.locals.message = err.message; res.locals.error = req.app.get("env") === "development" ? err : {}; // 返回 JSON 格式的错误信息 if (req.path.startsWith("/api/") || req.path.startsWith("/health")) { res.status(err.status || 500).json({ success: false, message: err.message, error: req.app.get("env") === "development" ? err.stack : undefined, }); } else { // 渲染错误页面 res.status(err.status || 500); res.render("error"); } }); module.exports = app; ``` ### 修改启动配置 编辑 `bin/www` 文件,确保应用监听正确的端口: ```javascript #!/usr/bin/env node /** * Module dependencies. */ var app = require("../app"); var debug = require("debug")("express-app:server"); var http = require("http"); /** * Get port from environment and store in Express. */ var port = normalizePort(process.env.PORT || "9000"); app.set("port", port); /** * Create HTTP server. */ var server = http.createServer(app); /** * Listen on provided port, on all network interfaces. */ server.listen(port, "0.0.0.0"); server.on("error", onError); server.on("listening", onListening); /** * Normalize a port into a number, string, or false. */ function normalizePort(val) { var port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; } /** * Event listener for HTTP server "error" event. */ function onError(error) { if (error.syscall !== "listen") { throw error; } var bind = typeof port === "string" ? "Pipe " + port : "Port " + port; // handle specific listen errors with friendly messages switch (error.code) { case "EACCES": console.error(bind + " requires elevated privileges"); process.exit(1); break; case "EADDRINUSE": console.error(bind + " is already in use"); process.exit(1); break; default: throw error; } } /** * Event listener for HTTP server "listening" event. */ function onListening() { var addr = server.address(); var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port; debug("Listening on " + bind); console.log("Express server listening on " + bind); } ``` ## 第三步:本地测试 > ⚠️ **重要提示**:CloudBase HTTP 云函数要求应用监听 9000 端口。 启动应用: ```bash npm start ``` 测试 API 接口: ```bash # 测试健康检查 curl http://localhost:9000/health # 测试用户列表 curl http://localhost:9000/api/users # 测试分页 curl "http://localhost:9000/api/users?page=1&limit=2" # 测试获取单个用户 curl http://localhost:9000/api/users/1 # 测试创建用户 curl -X POST http://localhost:9000/api/users \ -H "Content-Type: application/json" \ -d '{"name":"新用户","email":"newuser@example.com"}' ``` ## 第四步:创建启动脚本 > 💡 **注意**: > > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 `nano scf_bootstrap` 或者 `vim scf_bootstrap` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 创建 `scf_bootstrap` 文件(无扩展名): ```bash #!/bin/bash export PORT=9000 npm start ``` 为启动脚本添加执行权限: ```bash chmod +x scf_bootstrap ``` > 💡 **说明**: > > - `scf_bootstrap` 是 CloudBase 云函数的启动脚本 > - 设置 `PORT=9000` 环境变量确保应用监听正确端口 > - 使用 `npm start` 启动应用 ## 第五步:准备部署文件 确保您的项目目录结构如下: ``` express-app/ ├── bin/ │ └── www # 启动文件 ├── public/ # 静态资源 ├── routes/ # 路由文件 │ ├── index.js │ ├── users.js │ └── health.js ├── views/ # 视图模板 ├── app.js # 应用主文件 ├── package.json # 项目配置 ├── package-lock.json # 依赖锁定文件 └── scf_bootstrap # 启动脚本 ``` ## 第六步:部署到 CloudBase HTTP 云函数 ### 通过控制台部署 1. 登录 [CloudBase 控制台](https://tcb.cloud.tencent.com/dev) 2. 选择您的环境,进入「云函数」页面 3. 点击「新建云函数」 4. 选择「HTTP 云函数」 5. 填写函数名称(如:`express-app`) 6. 选择运行时:**Node.js 18.x**(或其他支持的版本) 7. 提交方法选择:**本地上传文件夹** 8. 函数代码选择 `express-app` 目录进行上传 9. **自动安装依赖**:开启此选项 10. 点击「创建」按钮等待部署完成 ### 通过 CLI 部署 详情请参考 [部署HTTP云函数](/cli-v1/functions/deploy#http-函数) ### 打包部署 如果需要手动打包: ```bash # 创建部署包(排除开发文件) zip -r express-app.zip . -x "node_modules/*" ".git/*" "*.log" ``` ## 第七步:访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问`HTTP 云函数`。 您可以测试以下接口: - 根路径:`/` - Express 欢迎页面 - 健康检查:`/health` - 查看应用状态 - 用户列表:`/api/users` - 获取用户列表 - 用户详情:`/api/users/1` - 获取特定用户 - 创建用户:`POST /api/users` - 创建新用户 ## 常见问题 ### Q: 为什么必须使用 9000 端口? A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。 ### Q: 如何处理静态文件? A: Express 的静态文件中间件会自动处理 `public` 目录下的静态资源。 ### Q: 如何查看应用日志? A: 在 CloudBase 控制台的云函数页面,点击函数名称进入详情页查看运行日志。 ### Q: 支持哪些 Node.js 版本? A: CloudBase 支持 Node.js 16.x、18.x、20.x 等版本,建议使用最新的 LTS 版本。 ### Q: 如何处理 CORS 跨域问题? A: 可以使用 `cors` 中间件或手动设置响应头来处理跨域请求。 ## 最佳实践 ### 1. 环境变量管理 在 `app.js` 中添加环境变量支持: ```javascript // 加载环境变量 require("dotenv").config(); // 使用环境变量 const isDevelopment = process.env.NODE_ENV === "development"; const port = process.env.PORT || 9000; ``` ### 2. 添加 CORS 支持 安装并配置 CORS 中间件: ```bash npm install cors ``` ```javascript const cors = require("cors"); // 配置 CORS app.use( cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") || "*", credentials: true, }), ); ``` ### 3. 请求日志 使用 Morgan 中间件记录请求日志: ```javascript const morgan = require("morgan"); // 配置日志格式 app.use(morgan(process.env.NODE_ENV === "production" ? "combined" : "dev")); ``` ### 4. 错误处理 实现全局错误处理中间件: ```javascript // 全局错误处理 app.use((err, req, res, next) => { console.error(err.stack); res.status(err.status || 500).json({ success: false, message: err.message, ...(process.env.NODE_ENV === "development" && { stack: err.stack }), }); }); ``` ### 5. 安全配置 安装并配置 Helmet 中间件: ```bash npm install helmet ``` ```javascript const helmet = require("helmet"); // 安全头配置 app.use(helmet()); ``` ## 进阶功能 ### 数据库集成 集成 MongoDB 或 MySQL: ```bash npm install mongoose # 或 npm install mysql2 ``` ### 身份验证 添加 JWT 身份验证: ```bash npm install jsonwebtoken bcryptjs ``` ### API 文档 使用 Swagger 生成 API 文档: ```bash npm install swagger-jsdoc swagger-ui-express ``` ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/function-configuration/config) - 学习如何 [连接 CloudBase 数据库](/api-reference/server/node-sdk/mysql/fetch) --- # 云函数/框架及示例/Next.js > 当前文档链接: https://docs.cloudbase.net/cloud-function/frameworks-examples/next [Next.js](https://nextjs.org/) 是一个基于 React 的现代全栈开发框架,由 Vercel 团队开发和维护。它提供了开箱即用的功能,包括服务器端渲染(SSR)、静态站点生成(SSG)、增量静态再生(ISR)、API 路由、文件系统路由等,帮助开发者快速构建高性能的 Web 应用。 本指南将详细介绍如何在 CloudBase HTTP 云函数上开发和部署 Next.js 应用程序,充分利用 Next.js 的全栈能力。 ## 前提条件 在开始之前,请确保您已具备以下条件: - **Node.js 环境**:版本 18.18 或更高(推荐 20.x) - **npm、yarn 或 pnpm**:包管理工具 - **React 基础知识**:熟悉 React 组件开发 - **CloudBase 账号**:已注册腾讯云账号并创建云开发环境 ## 环境准备 ### 1. 检查 Node.js 版本 ```bash # 检查 Node.js 版本 node --version # 检查 npm 版本 npm --version ``` ### 2. 创建 Next.js 应用 如果您已有 Next.js 应用,可以跳过此步骤。 ```bash # 使用官方 CLI 创建 Next.js 应用 npx create-next-app@latest my-nextjs-app # 进入项目目录 cd my-nextjs-app ``` 创建过程中的配置选项: ``` ✔ Would you like to use TypeScript? … No / Yes ✔ Would you like to use ESLint? … No / Yes ✔ Would you like to use Tailwind CSS? … No / Yes ✔ Would you like to use `src/` directory? … No / Yes ✔ Would you like to use App Router? (recommended) … No / Yes ✔ Would you like to customize the default import alias (@/*)? … No / Yes ``` 推荐配置: - **TypeScript**: Yes(更好的类型安全) - **ESLint**: Yes(代码质量保证) - **Tailwind CSS**: Yes(快速样式开发) - **App Router**: Yes(Next.js 13+ 推荐) ## 应用开发 ### 1. 项目结构 使用 App Router 的项目结构: ``` my-nextjs-app/ ├── app/ │ ├── globals.css # 全局样式 │ ├── layout.tsx # 根布局 │ ├── page.tsx # 首页 │ ├── api/ # API 路由 │ │ └── users/ │ │ └── route.ts # /api/users 端点 │ └── about/ │ └── page.tsx # /about 页面 ├── components/ # 可复用组件 ├── lib/ # 工具函数 ├── public/ # 静态资源 ├── next.config.js # Next.js 配置 └── package.json ``` ### 2. 配置 Next.js 编辑 `next.config.js` 文件,为 CloudBase 部署进行优化: ```javascript /** @type {import('next').NextConfig} */ const nextConfig = { // 输出模式设置为 standalone,便于云函数部署 output: 'standalone', // 禁用 x-powered-by 头 poweredByHeader: false, // 压缩配置 compress: true, // 图片优化配置 images: { domains: ['example.com'], // 添加外部图片域名 unoptimized: true, // 在云函数环境中禁用图片优化 }, // 环境变量配置 env: { CUSTOM_KEY: process.env.CUSTOM_KEY, }, // 重定向配置 async redirects() { return [ { source: '/old-path', destination: '/new-path', permanent: true, }, ]; }, // 头部配置 async headers() { return [ { source: '/api/:path*', headers: [ { key: 'Access-Control-Allow-Origin', value: '*', }, { key: 'Access-Control-Allow-Methods', value: 'GET, POST, PUT, DELETE, OPTIONS', }, { key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization', }, ], }, ]; }, }; module.exports = nextConfig; ``` ### 3. 创建页面组件 #### 首页组件 编辑 `app/page.tsx`: ```tsx import Link from 'next/link'; import { Metadata } from 'next'; export const metadata: Metadata = { title: 'Next.js on CloudBase', description: 'A Next.js application running on CloudBase HTTP Functions', }; export default function HomePage() { return (

欢迎使用 Next.js on CloudBase

这是一个运行在 CloudBase HTTP 云函数上的 Next.js 应用

了解更多 测试 API
); } interface FeatureCardProps { title: string; description: string; icon: string; } function FeatureCard({ title, description, icon }: FeatureCardProps) { return (
{icon}

{title}

{description}

); } ``` #### 关于页面 创建 `app/about/page.tsx`: ```tsx import { Metadata } from 'next'; export const metadata: Metadata = { title: '关于我们 - Next.js on CloudBase', description: '了解更多关于 Next.js 和 CloudBase 的信息', }; export default function AboutPage() { return (

关于 Next.js on CloudBase

技术栈

  • Next.js 14+: React 全栈框架
  • TypeScript: 类型安全的 JavaScript
  • Tailwind CSS: 实用优先的 CSS 框架
  • CloudBase: 腾讯云原生应用开发平台

特性

前端特性

  • • 服务器端渲染 (SSR)
  • • 静态站点生成 (SSG)
  • • 增量静态再生 (ISR)
  • • 自动代码分割

后端特性

  • • API 路由
  • • 中间件支持
  • • 数据库集成
  • • 身份认证
); } ``` ### 4. 创建 API 路由 #### 用户 API 创建 `app/api/users/route.ts`: ```typescript import { NextRequest, NextResponse } from 'next/server'; // 模拟用户数据 let users = [ { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'admin' }, { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'user' }, { id: 3, name: 'Charlie Brown', email: 'charlie@example.com', role: 'user' }, ]; // GET /api/users - 获取所有用户 export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const role = searchParams.get('role'); let filteredUsers = users; if (role) { filteredUsers = users.filter(user => user.role === role); } return NextResponse.json({ success: true, data: filteredUsers, total: filteredUsers.length, }); } catch (error) { return NextResponse.json( { success: false, error: 'Failed to fetch users' }, { status: 500 } ); } } // POST /api/users - 创建新用户 export async function POST(request: NextRequest) { try { const body = await request.json(); const { name, email, role = 'user' } = body; // 简单验证 if (!name || !email) { return NextResponse.json( { success: false, error: 'Name and email are required' }, { status: 400 } ); } // 检查邮箱是否已存在 const existingUser = users.find(user => user.email === email); if (existingUser) { return NextResponse.json( { success: false, error: 'Email already exists' }, { status: 409 } ); } const newUser = { id: Math.max(...users.map(u => u.id), 0) + 1, name, email, role, }; users.push(newUser); return NextResponse.json( { success: true, data: newUser }, { status: 201 } ); } catch (error) { return NextResponse.json( { success: false, error: 'Failed to create user' }, { status: 500 } ); } } ``` #### 健康检查 API 创建 `app/api/health/route.ts`: ```typescript import { NextResponse } from 'next/server'; export async function GET() { return NextResponse.json({ status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime(), environment: process.env.NODE_ENV || 'development', version: process.env.npm_package_version || '1.0.0', }); } ``` ### 5. 添加中间件 创建 `middleware.ts`(项目根目录): ```typescript import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // 添加安全头 const response = NextResponse.next(); response.headers.set('X-Frame-Options', 'DENY'); response.headers.set('X-Content-Type-Options', 'nosniff'); response.headers.set('Referrer-Policy', 'origin-when-cross-origin'); // API 路由的 CORS 处理 if (request.nextUrl.pathname.startsWith('/api/')) { response.headers.set('Access-Control-Allow-Origin', '*'); response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // 处理预检请求 if (request.method === 'OPTIONS') { return new Response(null, { status: 200, headers: response.headers }); } } return response; } export const config = { matcher: [ '/((?!_next/static|_next/image|favicon.ico).*)', ], }; ``` ## 本地开发 ### 1. 安装依赖 ```bash # 安装项目依赖 npm install # 或使用 yarn yarn install # 或使用 pnpm pnpm install ``` ### 2. 启动开发服务器 ```bash # 开发模式启动 npm run dev # 指定端口启动 npm run dev -- -p 9000 ``` ### 3. 测试应用 访问以下 URL 测试应用: ```bash # 首页 curl http://localhost:3000 # 关于页面 curl http://localhost:3000/about # 健康检查 API curl http://localhost:3000/api/health # 用户 API curl http://localhost:3000/api/users # 创建用户 curl -X POST http://localhost:3000/api/users \ -H "Content-Type: application/json" \ -d '{"name":"David Wilson","email":"david@example.com","role":"admin"}' # 按角色筛选用户 curl "http://localhost:3000/api/users?role=admin" ``` ## 部署配置 ### 1. 构建应用 ```bash # 构建生产版本 npm run build # 启动生产服务器(本地测试) npm start ``` ### 2. 创建 scf_bootstrap 文件 > 💡 **注意**: > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 ```nano scf_bootstrap``` 或者 ```vim scf_bootstrap``` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 在项目根目录创建 `scf_bootstrap` 文件(无扩展名): ```bash #!/bin/sh # 设置端口为 9000(CloudBase HTTP 云函数要求) export PORT=9000 # 设置 Node.js 环境为生产模式 export NODE_ENV=production # 启动 Next.js 应用 npm start ``` ### 3. 设置文件权限 ```bash chmod +x scf_bootstrap ``` ### 4. 优化 package.json 确保 `package.json` 包含正确的脚本: ```json { "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "next": "14.0.0", "react": "^18", "react-dom": "^18" } } ``` ### 5. 环境变量配置 创建 `.env.local` 文件(本地开发): ```env # 数据库配置 DATABASE_URL=your_database_url # API 密钥 API_SECRET_KEY=your_secret_key # 外部服务配置 EXTERNAL_API_URL=https://api.example.com ``` ## 部署到 CloudBase ### 方式一:控制台部署 1. **打开 CloudBase 控制台** 访问 [腾讯云 CloudBase 控制台](https://tcb.cloud.tencent.com/dev#/scf/function/create?type=package) 2. **创建 HTTP 云函数** - 函数类型:选择「HTTP 云函数」 - 函数名称:输入 `nextjs-app`(或自定义名称) - 提交方法:选择「本地上传文件夹」 3. **配置部署参数** - 函数代码:选择您的 Next.js 项目根目录 - 运行环境:选择 `Node.js 20.19`(推荐)或 `Node.js 18.15` - 自动安装依赖:**开启** - 内存配置:建议 512MB 或更高 4. **完成部署** 点击「创建」按钮,等待部署完成 ### 方式二:CLI 部署 详情请参考 [部署HTTP云函数](/cli-v1/functions/deploy#http-函数) ## 访问应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问```HTTP 云函数```。 您可以测试以下接口: - 根路径:`/` - Express 欢迎页面 - 健康检查:`/health` - 查看应用状态 ## 性能优化 ### 1. 代码分割 ```tsx // 动态导入组件 import dynamic from 'next/dynamic'; const DynamicComponent = dynamic(() => import('../components/HeavyComponent'), { loading: () =>

Loading...

, ssr: false, // 禁用服务器端渲染 }); export default function Page() { return (
); } ``` ### 2. 图片优化 ```tsx import Image from 'next/image'; export default function OptimizedImage() { return ( ); } ``` ### 3. 缓存策略 ```typescript // app/api/data/route.ts import { NextResponse } from 'next/server'; export async function GET() { const data = await fetchData(); return NextResponse.json(data, { headers: { 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300', }, }); } ``` ## 最佳实践 ### 1. 错误处理 创建 `app/error.tsx`: ```tsx 'use client'; export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { return (

出错了!

{error.message}

); } ``` ### 2. 加载状态 创建 `app/loading.tsx`: ```tsx export default function Loading() { return (
); } ``` ### 3. 404 页面 创建 `app/not-found.tsx`: ```tsx import Link from 'next/link'; export default function NotFound() { return (

404

页面未找到

抱歉,您访问的页面不存在。

返回首页
); } ``` ## 常见问题 ### Q: 部署后页面样式丢失? **A:** 检查以下几点: - 确认 `next.config.js` 中的 `output: 'standalone'` 配置 - 检查静态资源路径是否正确 - 验证 CSS 文件是否正确打包 ### Q: API 路由返回 404 错误? **A:** 常见原因: - 检查 API 路由文件命名是否为 `route.ts` 或 `route.js` - 确认文件位置在 `app/api/` 目录下 - 验证 HTTP 方法是否正确导出 ### Q: 应用启动缓慢? **A:** 优化建议: - 使用 `output: 'standalone'` 减少包大小 - 启用代码分割和懒加载 - 优化图片和静态资源 - 考虑使用 ISR 或 SSG ### Q: 如何连接数据库? **A:** 推荐使用 CloudBase 数据库或其他云数据库: ```typescript // lib/database.ts import { MongoClient } from 'mongodb'; const client = new MongoClient(process.env.DATABASE_URL!); export async function connectToDatabase() { if (!client.topology?.isConnected()) { await client.connect(); } return client.db('nextjs_app'); } // app/api/users/route.ts import { connectToDatabase } from '@/lib/database'; export async function GET() { const db = await connectToDatabase(); const users = await db.collection('users').find({}).toArray(); return NextResponse.json({ data: users }); } ``` ### Q: 如何处理环境变量? **A:** 在 CloudBase 控制台配置环境变量,或使用 `.env` 文件: ```typescript // 在组件中使用环境变量 const apiUrl = process.env.NEXT_PUBLIC_API_URL; // 客户端可访问 const secretKey = process.env.SECRET_KEY; // 仅服务器端可访问 ``` ## 总结 通过本指南,您已经学会了如何在 CloudBase HTTP 云函数上开发和部署 Next.js 应用。Next.js 的全栈能力结合 CloudBase 的云原生特性,可以帮助您快速构建现代化的 Web 应用。 建议在实际项目中根据业务需求进一步优化应用性能、添加数据库集成、实现身份认证等功能,充分发挥 Next.js 和 CloudBase 的优势。 --- # 云函数/框架及示例/NestJS > 当前文档链接: https://docs.cloudbase.net/cloud-function/frameworks-examples/nest [NestJS](https://nestjs.com/) 是一个基于 TypeScript/Node.js 的企业级后端框架,采用模块化设计,融合了 OOP(面向对象编程)、FP(函数式编程)和微服务架构。它底层支持 Express/Fastify,提供依赖注入、装饰器路由、GraphQL 集成等特性,适合构建高效、可维护的服务器端应用,尤其契合全栈 TypeScript 开发。 本指南将详细介绍如何在 CloudBase HTTP 云函数上开发和部署 NestJS 应用程序。 ## 前提条件 在开始之前,请确保您已具备以下条件: - **Node.js 环境**:版本 16.13 或更高(推荐 18.15+) - **npm 或 yarn**:包管理工具 - **NestJS CLI**:全局安装 NestJS 命令行工具 - **CloudBase 账号**:已注册腾讯云账号并创建云开发环境 ## 环境准备 ### 1. 安装 NestJS CLI ```bash # 全局安装 NestJS CLI npm install -g @nestjs/cli # 验证安装 nest --version ``` ### 2. 创建 NestJS 应用 如果您已有 NestJS 应用,可以跳过此步骤。 ```bash # 创建新的 NestJS 应用 nest new nest-cloudbase-app # 进入项目目录 cd nest-cloudbase-app ``` 选择包管理器(推荐使用 npm): ``` ? Which package manager would you ❤️ to use? (Use arrow keys) ❯ npm yarn pnpm ``` ## 应用开发 ### 1. 项目结构 创建完成后,您将得到以下项目结构: ``` nest-cloudbase-app/ ├── src/ │ ├── app.controller.ts # 应用控制器 │ ├── app.module.ts # 应用模块 │ ├── app.service.ts # 应用服务 │ └── main.ts # 应用入口文件 ├── test/ # 测试文件 ├── package.json ├── tsconfig.json └── nest-cli.json ``` ### 2. 修改应用入口文件 编辑 `src/main.ts` 文件,配置端口为 9000(CloudBase HTTP 云函数要求): ```typescript import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 启用 CORS app.enableCors({ origin: true, credentials: true, }); // CloudBase HTTP 云函数要求使用 9000 端口 const port = process.env.PORT || 9000; await app.listen(port, '0.0.0.0'); console.log(`Application is running on: http://localhost:${port}`); } bootstrap(); ``` ### 3. 创建示例 API #### 创建用户模块 ```bash # 生成用户模块 nest generate module users nest generate controller users nest generate service users ``` #### 编辑用户控制器 编辑 `src/users/users.controller.ts`: ```typescript import { Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common'; import { UsersService } from './users.service'; export interface User { id: number; name: string; email: string; } @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Get() findAll(): User[] { return this.usersService.findAll(); } @Get(':id') findOne(@Param('id') id: string): User { return this.usersService.findOne(+id); } @Post() create(@Body() user: Omit): User { return this.usersService.create(user); } @Put(':id') update(@Param('id') id: string, @Body() user: Partial): User { return this.usersService.update(+id, user); } @Delete(':id') remove(@Param('id') id: string): { message: string } { this.usersService.remove(+id); return { message: `User ${id} deleted successfully` }; } } ``` #### 编辑用户服务 编辑 `src/users/users.service.ts`: ```typescript import { Injectable, NotFoundException } from '@nestjs/common'; export interface User { id: number; name: string; email: string; } @Injectable() export class UsersService { private users: User[] = [ { id: 1, name: 'John Doe', email: 'john@example.com' }, { id: 2, name: 'Jane Smith', email: 'jane@example.com' }, ]; findAll(): User[] { return this.users; } findOne(id: number): User { const user = this.users.find(user => user.id === id); if (!user) { throw new NotFoundException(`User with ID ${id} not found`); } return user; } create(userData: Omit): User { const newUser: User = { id: Math.max(...this.users.map(u => u.id), 0) + 1, ...userData, }; this.users.push(newUser); return newUser; } update(id: number, userData: Partial): User { const userIndex = this.users.findIndex(user => user.id === id); if (userIndex === -1) { throw new NotFoundException(`User with ID ${id} not found`); } this.users[userIndex] = { ...this.users[userIndex], ...userData }; return this.users[userIndex]; } remove(id: number): void { const userIndex = this.users.findIndex(user => user.id === id); if (userIndex === -1) { throw new NotFoundException(`User with ID ${id} not found`); } this.users.splice(userIndex, 1); } } ``` ### 4. 添加健康检查端点 编辑 `src/app.controller.ts`: ```typescript import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('health') getHealth(): { status: string; timestamp: string } { return { status: 'ok', timestamp: new Date().toISOString(), }; } } ``` ## 本地测试 ### 1. 启动开发服务器 ```bash # 开发模式启动 npm run start:dev # 或者生产模式启动 npm run build npm run start:prod ``` ### 2. 测试 API 端点 使用 curl 或 Postman 测试应用: ```bash # 健康检查 curl http://localhost:9000/health # 获取所有用户 curl http://localhost:9000/users # 获取单个用户 curl http://localhost:9000/users/1 # 创建新用户 curl -X POST http://localhost:9000/users \ -H "Content-Type: application/json" \ -d '{"name":"Alice Johnson","email":"alice@example.com"}' # 更新用户 curl -X PUT http://localhost:9000/users/1 \ -H "Content-Type: application/json" \ -d '{"name":"John Updated","email":"john.updated@example.com"}' # 删除用户 curl -X DELETE http://localhost:9000/users/1 ``` ## 部署配置 ### 1. 创建 scf_bootstrap 文件 > 💡 **注意**: > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 ```nano scf_bootstrap``` 或者 ```vim scf_bootstrap``` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 在项目根目录创建 `scf_bootstrap` 文件(无扩展名): ```bash #!/bin/sh # 设置端口为 9000(CloudBase HTTP 云函数要求) export PORT=9000 # 启动 NestJS 应用 npm run start:prod ``` ### 2. 设置文件权限 ```bash chmod +x scf_bootstrap ``` ### 3. 优化 package.json 确保 `package.json` 包含正确的启动脚本: ```json { "scripts": { "build": "nest build", "start": "node dist/main", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main" } } ``` ### 4. 创建 .gitignore ```gitignore # 依赖 node_modules/ # 构建输出 dist/ build/ # 日志 *.log npm-debug.log* # 环境变量 .env .env.local .env.*.local # IDE .vscode/ .idea/ # 操作系统 .DS_Store Thumbs.db ``` ## 部署到 CloudBase ### 方式一:控制台部署 1. **打开 CloudBase 控制台** 访问 [腾讯云 CloudBase 控制台](https://tcb.cloud.tencent.com/dev#/scf/function/create?type=package) 2. **创建 HTTP 云函数** - 函数类型:选择「HTTP 云函数」 - 函数名称:输入 `nest-app`(或自定义名称) - 提交方法:选择「本地上传文件夹」 3. **上传代码** - 函数代码:选择您的 NestJS 项目根目录 - 运行环境:选择 `Node.js 18.15`(推荐)或其他支持版本 - 自动安装依赖:**开启** 4. **完成部署** 点击「创建」按钮,等待部署完成 ### 方式二:CLI 部署 详情请参考 [部署HTTP云函数](/cli-v1/functions/deploy#http-函数) ## 访问应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问```HTTP 云函数```。 您可以测试以下接口: - 根路径:`/` - Express 欢迎页面 - 健康检查:`/health` - 查看应用状态 ## 最佳实践 ### 1. 环境变量管理 在 CloudBase 控制台配置环境变量: ```typescript // src/config/database.config.ts export const databaseConfig = { host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT) || 3306, username: process.env.DB_USERNAME || 'root', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'nestjs_app', }; ``` ### 2. 日志配置 ```typescript // src/main.ts import { Logger } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule, { logger: ['error', 'warn', 'log'], }); const logger = new Logger('Bootstrap'); // ... 其他配置 await app.listen(port, '0.0.0.0'); logger.log(`Application is running on port ${port}`); } ``` ### 3. 全局异常处理 ```typescript // src/filters/http-exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const status = exception.getStatus(); response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: exception.message, }); } } ``` ### 4. 数据验证 ```bash # 安装验证相关包 npm install class-validator class-transformer ``` ```typescript // src/users/dto/create-user.dto.ts import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDto { @IsNotEmpty() @IsString() name: string; @IsEmail() email: string; } ``` ## 常见问题 ### Q: 部署后访问 404 错误? **A:** 检查以下几点: - 确认 `scf_bootstrap` 文件存在且有执行权限 - 验证端口配置为 9000 - 检查应用是否正确构建(`npm run build`) ### Q: 应用启动失败? **A:** 常见原因: - Node.js 版本不兼容,建议使用 18.15+ - 依赖安装失败,检查 `package.json` 配置 - 内存不足,考虑优化代码或升级函数配置 ### Q: 如何查看应用日志? **A:** 在 CloudBase 控制台的「云函数」→「函数详情」→「日志」中查看运行日志。 ### Q: 如何连接数据库? **A:** 推荐使用 CloudBase 数据库或其他云数据库服务: ```typescript // 安装数据库相关包 npm install @nestjs/typeorm typeorm mysql2 // 配置数据库连接 @Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, autoLoadEntities: true, synchronize: process.env.NODE_ENV !== 'production', }), ], }) export class AppModule {} ``` ## 总结 通过本指南,您已经学会了如何在 CloudBase HTTP 云函数上开发和部署 NestJS 应用。NestJS 的模块化架构和强大的功能特性,结合 CloudBase 的云原生能力,可以帮助您快速构建高质量的企业级应用。 建议在实际项目中根据业务需求进一步优化应用架构、添加数据库集成、实现身份认证等功能。 --- # 云函数/框架及示例/Flask > 当前文档链接: https://docs.cloudbase.net/cloud-function/frameworks-examples/flask [Flask](https://flask.palletsprojects.com/en/stable/) 是一个轻量级、灵活的 Python Web 框架,以简洁和可扩展性为核心设计理念。它不强制依赖特定库或架构,仅提供核心功能(如路由、模板渲染),开发者可自由选配数据库、表单验证等组件。这种"微框架"特性使其学习成本极低,同时能通过扩展轻松构建复杂应用,特别适合快速开发小型项目或作为微服务基础。 本指南介绍如何在 CloudBase HTTP 云函数上部署 Flask 应用程序。 ## 前置条件 在开始之前,请确保您已经: - 安装了 [Python 3.10](https://www.python.org/downloads/) 或更高版本 - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Python 和 Flask 开发知识 ## 第一步:创建 Flask 应用 > 💡 **提示**:如果您已经有一个 Flask 应用,可以跳过此步骤。 ### 创建项目目录 ```bash mkdir flask-cloudbase cd flask-cloudbase ``` ### 设置虚拟环境 ```bash # 创建虚拟环境 python -m venv env # 激活虚拟环境 # Linux/macOS source env/bin/activate # Windows # env\Scripts\activate ``` ### 安装 Flask ```bash pip install flask ``` ## 第二步:编写应用代码 创建 `app.py` 文件作为应用的入口文件: > ⚠️ **重要提示**:CloudBase HTTP 云函数要求应用监听 9000 端口。 ```python import os from flask import Flask, jsonify, request app = Flask(__name__) # 配置应用 app.config['DEBUG'] = os.environ.get('DEBUG', 'False').lower() == 'true' app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'your-secret-key-here') # 模拟数据 users = [ {'id': 1, 'name': '张三', 'email': 'zhangsan@example.com'}, {'id': 2, 'name': '李四', 'email': 'lisi@example.com'}, {'id': 3, 'name': '王五', 'email': 'wangwu@example.com'} ] @app.route('/') def hello(): """根路径处理函数""" return jsonify({ 'message': 'Hello from Flask on CloudBase!', 'framework': 'Flask', 'version': '2.3.0' }) @app.route('/health') def health_check(): """健康检查接口""" return jsonify({ 'status': 'healthy', 'framework': 'Flask', 'python_version': os.sys.version }) @app.route('/api/users', methods=['GET']) def get_users(): """获取用户列表(支持分页)""" page = request.args.get('page', 1, type=int) limit = request.args.get('limit', 10, type=int) # 简单分页逻辑 start_index = (page - 1) * limit end_index = start_index + limit paginated_users = users[start_index:end_index] return jsonify({ 'success': True, 'data': { 'total': len(users), 'page': page, 'limit': limit, 'items': paginated_users } }) @app.route('/api/users/', methods=['GET']) def get_user(user_id): """根据 ID 获取用户""" user = next((u for u in users if u['id'] == user_id), None) if not user: return jsonify({ 'success': False, 'message': 'User not found' }), 404 return jsonify({ 'success': True, 'data': user }) @app.route('/api/users', methods=['POST']) def create_user(): """创建新用户""" data = request.get_json() if not data or 'name' not in data or 'email' not in data: return jsonify({ 'success': False, 'message': 'Name and email are required' }), 400 # 检查邮箱是否已存在 if any(u['email'] == data['email'] for u in users): return jsonify({ 'success': False, 'message': 'Email already exists' }), 400 # 创建新用户 new_user = { 'id': max(u['id'] for u in users) + 1 if users else 1, 'name': data['name'], 'email': data['email'] } users.append(new_user) return jsonify({ 'success': True, 'data': new_user }), 201 @app.route('/api/users/', methods=['PUT']) def update_user(user_id): """更新用户信息""" user_index = next((i for i, u in enumerate(users) if u['id'] == user_id), None) if user_index is None: return jsonify({ 'success': False, 'message': 'User not found' }), 404 data = request.get_json() if not data: return jsonify({ 'success': False, 'message': 'No data provided' }), 400 # 检查邮箱是否被其他用户使用 if 'email' in data and any(u['email'] == data['email'] and u['id'] != user_id for u in users): return jsonify({ 'success': False, 'message': 'Email already exists' }), 400 # 更新用户信息 if 'name' in data: users[user_index]['name'] = data['name'] if 'email' in data: users[user_index]['email'] = data['email'] return jsonify({ 'success': True, 'data': users[user_index] }) @app.route('/api/users/', methods=['DELETE']) def delete_user(user_id): """删除用户""" user_index = next((i for i, u in enumerate(users) if u['id'] == user_id), None) if user_index is None: return jsonify({ 'success': False, 'message': 'User not found' }), 404 deleted_user = users.pop(user_index) return jsonify({ 'success': True, 'message': f'User {deleted_user["name"]} deleted successfully' }) # 错误处理 @app.errorhandler(404) def not_found(error): return jsonify({ 'success': False, 'message': 'Resource not found' }), 404 @app.errorhandler(500) def internal_error(error): return jsonify({ 'success': False, 'message': 'Internal server error' }), 500 @app.errorhandler(400) def bad_request(error): return jsonify({ 'success': False, 'message': 'Bad request' }), 400 if __name__ == '__main__': # CloudBase HTTP 云函数要求监听 9000 端口 app.run(host='0.0.0.0', port=9000, debug=False) ``` ### 代码说明 让我们详细解释上述代码的关键部分: 1. **Flask 应用创建** ```python app = Flask(__name__) ``` 创建 Flask 应用实例,`__name__` 参数帮助 Flask 识别应用程序的位置。 2. **路由装饰器** ```python @app.route('/') def hello(): return jsonify({'message': 'Hello from Flask!'}) ``` `@app.route('/')` 装饰器定义 URL 路由,当访问根 URL 时执行对应函数。 3. **HTTP 方法支持** ```python @app.route('/api/users', methods=['GET', 'POST']) ``` 通过 `methods` 参数指定路由支持的 HTTP 方法。 4. **错误处理** ```python @app.errorhandler(404) def not_found(error): return jsonify({'error': 'Not Found'}), 404 ``` 使用 `@app.errorhandler` 装饰器处理特定的 HTTP 错误。 ## 第三步:本地测试 ### 启动应用 ```bash python app.py ``` ### 测试 API 接口 应用启动后,您可以通过以下方式测试: ```bash # 测试根路径 curl http://localhost:9000/ # 测试健康检查 curl http://localhost:9000/health # 测试用户列表 curl http://localhost:9000/api/users # 测试分页 curl "http://localhost:9000/api/users?page=1&limit=2" # 测试获取单个用户 curl http://localhost:9000/api/users/1 # 测试创建用户 curl -X POST http://localhost:9000/api/users \ -H "Content-Type: application/json" \ -d '{"name":"新用户","email":"newuser@example.com"}' # 测试更新用户 curl -X PUT http://localhost:9000/api/users/1 \ -H "Content-Type: application/json" \ -d '{"name":"更新用户","email":"updated@example.com"}' # 测试删除用户 curl -X DELETE http://localhost:9000/api/users/1 ``` ## 第四步:配置依赖项 生成 `requirements.txt` 文件: ```bash pip freeze > requirements.txt ``` > ⚠️ **注意**:只有在虚拟环境中运行上述命令才是安全的,否则它将生成系统上所有已安装的 Python 包,可能导致云函数无法正常启动。 典型的 `requirements.txt` 内容: ```txt blinker==1.7.0 click==8.1.7 Flask==2.3.3 itsdangerous==2.1.2 Jinja2==3.1.2 MarkupSafe==2.1.3 Werkzeug==2.3.7 ``` ## 第五步:创建启动脚本 > 💡 **注意**: > > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 `nano scf_bootstrap` 或者 `vim scf_bootstrap` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 创建 `scf_bootstrap` 文件(无扩展名): ```bash #!/bin/bash # 设置 Python 依赖加载路径,我们默认设置在 third_party 目录 export PYTHONPATH="./third_party:$PYTHONPATH" /var/lang/python310/bin/python3.10 app.py ``` 为启动脚本添加执行权限: ```bash chmod +x scf_bootstrap ``` > 💡 **说明**: > > - `scf_bootstrap` 是 CloudBase 云函数的启动脚本 > - 设置 Python 路径并启动 Flask 应用 > - 确保应用监听 9000 端口 ## 第六步:准备部署文件 部署前将依赖安装到 `third_party` 目录下: > ⚠️ **注意**: > > - HTTP 云函数并不会自动安装 Python 依赖,所以我们需要自己将依赖下载到代码包中 ```bash pip install -r requirements.txt -t third_party ``` 确保您的项目目录结构如下: ``` flask-cloudbase/ ├── third_party/ # 第三方依赖 ├── app.py # 应用主文件 ├── requirements.txt # 依赖列表 └── scf_bootstrap # 启动脚本 ``` ## 第七步:部署到 CloudBase HTTP 云函数 ### 通过控制台部署 1. 登录 [CloudBase 控制台](https://tcb.cloud.tencent.com/dev) 2. 选择您的环境,进入「云函数」页面 3. 点击「新建云函数」 4. 选择「HTTP 云函数」 5. 填写函数名称(如:`flask-app`) 6. 选择运行时:**Python 3.10** 7. 提交方法选择:**本地上传文件夹** 8. 函数代码选择项目根目录进行上传 9. **自动安装依赖**:开启此选项 10. 点击「创建」按钮等待部署完成 ### 通过 CLI 部署 详情请参考 [部署HTTP云函数](/cli-v1/functions/deploy#http-函数) 如果需要手动打包: ```bash # 创建部署包(排除虚拟环境) zip -r fastapi-app.zip third_party app.py scf_bootstrap ``` ## 第八步:访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问`HTTP 云函数`。 您可以测试以下接口: - 根路径:`/` - 欢迎信息 - 健康检查:`/health` - 应用状态 - 用户列表:`/api/users` - 获取用户列表 - 用户详情:`/api/users/1` - 获取特定用户 - 创建用户:`POST /api/users` - 创建新用户 ## 常见问题 ### Q: 为什么必须使用 9000 端口? A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。 ### Q: 如何处理静态文件? A: Flask 可以通过 `static` 文件夹处理静态文件,或者使用 CDN 服务。 ### Q: 如何查看应用日志? A: 在 CloudBase 控制台的云函数页面,点击函数名称进入详情页查看运行日志。 ### Q: 支持哪些 Python 版本? A: CloudBase 支持 Python 3.6、3.7、3.9、3.10 等版本,建议使用 Python 3.10。 ### Q: 如何处理 CORS 跨域问题? A: 可以使用 `flask-cors` 扩展或手动设置响应头来处理跨域请求。 ## 最佳实践 ### 1. 环境变量管理 ```python import os from flask import Flask app = Flask(__name__) # 使用环境变量 app.config['DEBUG'] = os.environ.get('DEBUG', 'False').lower() == 'true' app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'fallback-secret-key') app.config['DATABASE_URL'] = os.environ.get('DATABASE_URL') ``` ### 2. 添加 CORS 支持 ```bash pip install flask-cors ``` ```python from flask_cors import CORS app = Flask(__name__) CORS(app, origins=['https://yourdomain.com']) ``` ### 3. 日志配置 ```python import logging from flask import Flask app = Flask(__name__) # 配置日志 if not app.debug: logging.basicConfig(level=logging.INFO) app.logger.info('Flask app startup') ``` ### 4. 蓝图组织代码 ```python from flask import Blueprint # 创建蓝图 api_bp = Blueprint('api', __name__, url_prefix='/api') @api_bp.route('/users') def get_users(): return jsonify({'users': []}) # 注册蓝图 app.register_blueprint(api_bp) ``` ### 5. 数据库集成 ```bash pip install flask-sqlalchemy ``` ```python from flask_sqlalchemy import SQLAlchemy app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' db = SQLAlchemy(app) class User(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(80), nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) ``` ## 进阶功能 ### 1. 表单处理 ```bash pip install flask-wtf ``` ### 2. 身份验证 ```bash pip install flask-login flask-jwt-extended ``` ### 3. API 文档 ```bash pip install flask-restx ``` ### 4. 缓存支持 ```bash pip install flask-caching ``` ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/function-configuration/config) - 学习如何 [连接 CloudBase 数据库](/api-reference/server/node-sdk/mysql/fetch) --- # 云函数/框架及示例/Django > 当前文档链接: https://docs.cloudbase.net/cloud-function/frameworks-examples/django [Django](https://www.djangoproject.com/) 是一个功能强大的 Python Web 框架,遵循 "Batteries-included" 理念,提供开箱即用的全栈解决方案。它以高效开发和安全稳定著称,内置 ORM、Admin 后台、用户认证等模块,大幅减少重复代码。Django 采用清晰的 MVC(MTV)架构,支持高扩展性,适合从快速原型到企业级应用开发。 本指南介绍如何在 CloudBase HTTP 云函数上部署 Django 应用程序。 ## 前置条件 在开始之前,请确保您已经: - 安装了 [Python 3.10](https://www.python.org/downloads/) 或更高版本 - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Python 和 Django 开发知识 ## 第一步:创建 Django 应用 > 💡 **提示**:如果您已经有一个 Django 应用,可以跳过此步骤。 ### 设置开发环境 创建项目目录并设置虚拟环境: ```bash # 创建项目目录 mkdir django-cloudbase cd django-cloudbase # 创建虚拟环境 python -m venv env # 激活虚拟环境 # Linux/macOS source env/bin/activate # Windows # env\Scripts\activate ``` ### 安装 Django ```bash python -m pip install django ``` ### 创建 Django 项目 ```bash django-admin startproject django_app . ``` 此命令将在当前目录创建一个名为 `django_app` 的 Django 项目。 ### 配置 Django 设置 编辑 `django_app/settings.py` 文件,添加 CloudBase 云函数的必要配置: ```python import os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.environ.get('SECRET_KEY', 'your-secret-key-here') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true' ALLOWED_HOSTS = ['*'] # 允许所有主机访问 # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'django_app.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] # Database DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', } } # Static files (CSS, JavaScript, Images) STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # Default primary key field type DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' ``` ### 创建简单的视图 编辑 `django_app/urls.py` 文件: ```python from django.contrib import admin from django.urls import path from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt import json def home(request): """首页视图""" return JsonResponse({ 'message': 'Hello from Django on CloudBase!', 'method': request.method, 'path': request.path }) def health(request): """健康检查接口""" return JsonResponse({ 'status': 'healthy', 'framework': 'Django', 'version': '4.2' }) @csrf_exempt def api_info(request): """API 信息接口""" data = { 'method': request.method, 'path': request.path, 'headers': dict(request.headers), 'get_params': dict(request.GET), } if request.method == 'POST': try: data['post_data'] = json.loads(request.body) if request.body else {} except json.JSONDecodeError: data['post_data'] = request.POST.dict() return JsonResponse(data) urlpatterns = [ path('admin/', admin.site.urls), path('', home, name='home'), path('health/', health, name='health'), path('api/info/', api_info, name='api_info'), ] ``` ## 第二步:本地测试 在部署之前,先在本地测试应用: > ⚠️ **重要提示**:CloudBase HTTP 云函数要求应用监听 9000 端口。 ```bash # 运行数据库迁移 python manage.py migrate # 启动开发服务器(使用 9000 端口) python manage.py runserver 0.0.0.0:9000 ``` 打开浏览器访问 `http://127.0.0.1:9000`,您应该能看到 JSON 响应。 测试其他接口: - `http://127.0.0.1:9000/health` - 健康检查 - `http://127.0.0.1:9000/api/info/` - API 信息 ## 第三步:配置依赖项 生成 `requirements.txt` 文件: ```bash pip freeze > requirements.txt ``` > ⚠️ **注意**:只有在虚拟环境中运行上述命令才是安全的,否则它将生成系统上所有已安装的 Python 包,可能导致云函数无法正常启动。 典型的 `requirements.txt` 内容: ```txt # Django 4.2 LTS - 兼容 SQLite 3.26+, 云函数默认运行时 SQLite 版本有要求 Django==4.2.16 asgiref==3.7.2 psycopg2-binary==2.9.11 sqlparse==0.4.4 typing_extensions==4.15.0 ``` ## 第四步:创建启动脚本 > 💡 **注意**: > > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 `nano scf_bootstrap` 或者 `vim scf_bootstrap` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 创建 `scf_bootstrap` 文件(无扩展名): ```bash #!/bin/bash # 设置 Python 依赖加载路径,我们默认设置在 third_party 目录 export PYTHONPATH="./third_party:$PYTHONPATH" # 运行数据库迁移 /var/lang/python310/bin/python3.10 manage.py migrate --noinput # 收集静态文件 /var/lang/python310/bin/python3.10 manage.py collectstatic --noinput # 启动 Django 应用 /var/lang/python310/bin/python3.10 manage.py runserver 0.0.0.0:9000 ``` 为启动脚本添加执行权限: ```bash chmod +x scf_bootstrap ``` > 💡 **说明**: > > - `scf_bootstrap` 是 CloudBase 云函数的启动脚本 > - 脚本会自动运行数据库迁移和静态文件收集 > - 确保应用监听 9000 端口 ## 第五步:准备部署文件 部署前将依赖安装到 `third_party` 目录下: > ⚠️ **注意**: > > - HTTP 云函数并不会自动安装 Python 依赖,所以我们需要自己将依赖下载到代码包中 ```bash pip install -r requirements.txt -t third_party ``` 确保您的项目目录结构如下: ``` django-cloudbase/ ├── third_party/ # 第三方依赖 ├── django_app/ # Django 项目目录 │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py # Django 管理脚本 ├── requirements.txt # 依赖列表 ├── scf_bootstrap # 启动脚本 ``` ## 第六步:部署到 CloudBase HTTP 云函数 ### 通过控制台部署 1. 登录 [CloudBase 控制台](https://tcb.cloud.tencent.com/dev) 2. 选择您的环境,进入「云函数」页面 3. 点击「新建云函数」 4. 选择「HTTP 云函数」 5. 填写函数名称(如:`django-app`) 6. 选择运行时:**Python 3.10** 7. 提交方法选择:**本地上传文件夹** 8. 函数代码选择项目根目录进行上传 9. **自动安装依赖**:开启此选项 10. 点击「创建」按钮等待部署完成 ### 通过 CLI 部署 详情请参考 [部署HTTP云函数](/cli-v1/functions/deploy#http-函数) ### 打包部署 如果需要手动打包: ```bash # 创建部署包 zip -r django-app.zip third_party django_app manage.py scf_bootstrap ``` ## 第七步:访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问`HTTP 云函数`。 您可以测试以下接口: - 根路径:查看欢迎消息 - `/health/`:查看健康状态 - `/api/info/`:查看请求信息 - `/admin/`:访问 Django 管理后台 ## 常见问题 ### Q: 为什么必须使用 9000 端口? A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。 ### Q: 如何处理静态文件? A: 在 `settings.py` 中配置 `STATIC_ROOT`,并在 `scf_bootstrap` 中运行 `collectstatic` 命令。 ### Q: 数据库如何配置? A: 对于生产环境,建议使用 CloudBase 数据库或其他云数据库服务,而不是 SQLite。 ### Q: 如何查看应用日志? A: 在 CloudBase 控制台的云函数页面,点击函数名称进入详情页查看运行日志。 ### Q: Django 的 SECRET_KEY 如何管理? A: 建议使用环境变量管理敏感配置,在云函数中设置环境变量。 ## 最佳实践 1. **环境变量管理**:使用环境变量管理敏感配置信息 2. **数据库选择**:生产环境使用云数据库而非 SQLite 3. **静态文件处理**:正确配置静态文件的收集和服务 4. **安全配置**:在生产环境中关闭 DEBUG 模式 5. **日志记录**:配置适当的日志级别和输出格式 6. **错误处理**:实现全局异常处理和友好的错误页面 ## 进阶配置 ### 使用 WSGI 服务器 对于生产环境,建议使用 Gunicorn 等 WSGI 服务器: ```bash # 安装 Gunicorn pip install gunicorn # 更新 requirements.txt pip freeze > requirements.txt ``` 修改 `scf_bootstrap`: ```bash #!/bin/bash export PYTHONPATH="./env/lib/python3.10/site-packages:$PYTHONPATH" # 运行迁移 /var/lang/python310/bin/python3.10 manage.py migrate --noinput # 收集静态文件 /var/lang/python310/bin/python3.10 manage.py collectstatic --noinput # 使用 Gunicorn 启动 /var/lang/python310/bin/python3.10 -m gunicorn django_app.wsgi:application --bind 0.0.0.0:9000 ``` ### 环境变量配置 在云函数控制台设置环境变量: - `SECRET_KEY`: Django 密钥 - `DEBUG`: 调试模式(False) - `DATABASE_URL`: 数据库连接字符串(如果使用外部数据库) ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/function-configuration/config) - 学习如何 [连接 CloudBase 数据库](/api-reference/server/node-sdk/mysql/fetch) --- # 云函数/框架及示例/FastAPI > 当前文档链接: https://docs.cloudbase.net/cloud-function/frameworks-examples/fastapi [FastAPI](https://fastapi.tiangolo.com/) 是一个用于构建 API 的现代、快速(高性能)的 Web 框架,使用 Python 并基于标准的 Python 类型提示。具有以下特性: - **快速**:可与 NodeJS 和 Go 并肩的极高性能(归功于 Starlette 和 Pydantic) - **高效编码**:提高功能开发速度约 200% 至 300% - **更少 bug**:减少约 40% 的人为(开发者)导致错误 - **智能**:极佳的编辑器支持,处处皆可自动补全,减少调试时间 - **简单**:设计的易于使用和学习,阅读文档的时间更短 - **自动文档**:自动生成交互式 API 文档 本指南介绍如何在 CloudBase HTTP 云函数上部署 FastAPI 应用程序。 ## 前置条件 在开始之前,请确保您已经: - 安装了 [Python 3.10](https://www.python.org/downloads/) 或更高版本 - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Python 和 FastAPI 开发知识 ## 第一步:创建 FastAPI 项目 > 💡 **提示**:如果您已经有一个 FastAPI 项目,可以跳过此步骤。 ### 创建项目目录 ```bash mkdir fastapi-cloudbase cd fastapi-cloudbase ``` ### 创建应用文件 创建 `app.py` 文件,这是应用的入口文件: ```python from fastapi import FastAPI, HTTPException, Query from pydantic import BaseModel from typing import List, Optional import uvicorn app = FastAPI( title="FastAPI CloudBase Demo", description="A FastAPI application running on CloudBase HTTP Functions", version="1.0.0" ) # 数据模型 class User(BaseModel): id: int name: str email: str age: Optional[int] = None class UserCreate(BaseModel): name: str email: str age: Optional[int] = None # 模拟数据库 users_db = [ User(id=1, name="张三", email="zhangsan@example.com", age=25), User(id=2, name="李四", email="lisi@example.com", age=30), User(id=3, name="王五", email="wangwu@example.com", age=28) ] @app.get("/") async def root(): """根路径处理函数""" return { "message": "Hello from FastAPI on CloudBase!", "framework": "FastAPI", "docs": "/docs", "redoc": "/redoc" } @app.get("/health") async def health_check(): """健康检查接口""" return { "status": "healthy", "framework": "FastAPI", "version": "1.0.0" } @app.get("/api/users", response_model=List[User]) async def get_users( page: int = Query(1, ge=1, description="页码"), limit: int = Query(10, ge=1, le=100, description="每页数量") ): """获取用户列表(支持分页)""" start_index = (page - 1) * limit end_index = start_index + limit paginated_users = users_db[start_index:end_index] return paginated_users @app.get("/api/users/{user_id}", response_model=User) async def get_user(user_id: int): """根据 ID 获取用户""" user = next((user for user in users_db if user.id == user_id), None) if not user: raise HTTPException(status_code=404, detail="User not found") return user @app.post("/api/users", response_model=User, status_code=201) async def create_user(user: UserCreate): """创建新用户""" # 检查邮箱是否已存在 if any(u.email == user.email for u in users_db): raise HTTPException(status_code=400, detail="Email already registered") # 生成新 ID new_id = max(u.id for u in users_db) + 1 if users_db else 1 # 创建新用户 new_user = User(id=new_id, **user.dict()) users_db.append(new_user) return new_user @app.put("/api/users/{user_id}", response_model=User) async def update_user(user_id: int, user_update: UserCreate): """更新用户信息""" user_index = next((i for i, u in enumerate(users_db) if u.id == user_id), None) if user_index is None: raise HTTPException(status_code=404, detail="User not found") # 检查邮箱是否被其他用户使用 if any(u.email == user_update.email and u.id != user_id for u in users_db): raise HTTPException(status_code=400, detail="Email already registered") # 更新用户 updated_user = User(id=user_id, **user_update.dict()) users_db[user_index] = updated_user return updated_user @app.delete("/api/users/{user_id}") async def delete_user(user_id: int): """删除用户""" user_index = next((i for i, u in enumerate(users_db) if u.id == user_id), None) if user_index is None: raise HTTPException(status_code=404, detail="User not found") deleted_user = users_db.pop(user_index) return {"message": f"User {deleted_user.name} deleted successfully"} # 自定义异常处理 @app.exception_handler(404) async def not_found_handler(request, exc): return {"error": "Not Found", "message": "The requested resource was not found"} @app.exception_handler(500) async def internal_error_handler(request, exc): return {"error": "Internal Server Error", "message": "Something went wrong"} if __name__ == "__main__": # CloudBase HTTP 云函数要求监听 9000 端口 uvicorn.run(app, host="0.0.0.0", port=9000) ``` ### 创建依赖文件 创建 `requirements.txt` 文件: ```txt fastapi==0.104.1 uvicorn[standard]==0.24.0 pydantic==1.10.2 ``` > 💡 **说明**: > > - `fastapi`:FastAPI 框架 > - `uvicorn`:ASGI 服务器,用于运行 FastAPI 应用 > - `pydantic`:数据验证和序列化库,使用 1.x 版本以避免 pydantic_core 依赖 ## 第二步:本地测试 > ⚠️ **重要提示**:CloudBase HTTP 云函数要求应用监听 9000 端口。 ### 设置虚拟环境 ```bash # 创建虚拟环境 python -m venv venv # 激活虚拟环境 # Linux/macOS source venv/bin/activate # Windows # venv\Scripts\activate ``` ### 安装依赖 ```bash pip install -r requirements.txt ``` ### 启动应用 ```bash # 方式一:直接运行 python app.py # 方式二:使用 uvicorn 命令 uvicorn app:app --host 0.0.0.0 --port 9000 --reload ``` ### 测试 API 应用启动后,您可以通过以下方式测试: **访问文档:** - Swagger UI:`http://localhost:9000/docs` - ReDoc:`http://localhost:9000/redoc` **测试 API 接口:** ```bash # 测试根路径 curl http://localhost:9000/ # 测试健康检查 curl http://localhost:9000/health # 测试用户列表 curl http://localhost:9000/api/users # 测试分页 curl "http://localhost:9000/api/users?page=1&limit=2" # 测试获取单个用户 curl http://localhost:9000/api/users/1 # 测试创建用户 curl -X POST http://localhost:9000/api/users \ -H "Content-Type: application/json" \ -d '{"name":"新用户","email":"newuser@example.com","age":25}' # 测试更新用户 curl -X PUT http://localhost:9000/api/users/1 \ -H "Content-Type: application/json" \ -d '{"name":"更新用户","email":"updated@example.com","age":26}' ``` ## 第三步:创建启动脚本 > 💡 **注意**: > > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 `nano scf_bootstrap` 或者 `vim scf_bootstrap` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 创建 `scf_bootstrap` 文件(无扩展名): ```bash #!/bin/bash # 设置 Python 依赖加载路径,我们默认设置在 third_party 目录 export PYTHONPATH="./third_party:$PYTHONPATH" /var/lang/python310/bin/python3.10 -m uvicorn app:app --host 0.0.0.0 --port 9000 ``` 为启动脚本添加执行权限: ```bash chmod +x scf_bootstrap ``` > 💡 **说明**: > > - `scf_bootstrap` 是 CloudBase 云函数的启动脚本 > - 使用 uvicorn 启动 FastAPI 应用 > - 确保应用监听 9000 端口 ## 第四步:准备部署文件 部署前将依赖安装到 `third_party` 目录下: > ⚠️ **注意**: > > - HTTP 云函数并不会自动安装 Python 依赖,所以我们需要自己将依赖下载到代码包中 ```bash pip install -r requirements.txt -t third_party ``` 确保您的项目目录结构如下: ``` fastapi-cloudbase/ ├── third_party/ # 第三方依赖 ├── app.py # 应用主文件 ├── requirements.txt # 依赖列表 └── scf_bootstrap # 启动脚本 ``` ## 第五步:部署到 CloudBase HTTP 云函数 ### 通过控制台部署 1. 登录 [CloudBase 控制台](https://tcb.cloud.tencent.com/dev) 2. 选择您的环境,进入「云函数」页面 3. 点击「新建云函数」 4. 选择「HTTP 云函数」 5. 填写函数名称(如:`fastapi-app`) 6. 选择运行时:**Python 3.10** 7. 提交方法选择:**本地上传文件夹** 8. 函数代码选择项目根目录进行上传 9. **自动安装依赖**:开启此选项 10. 点击「创建」按钮等待部署完成 ### 通过 CLI 部署 详情请参考 [部署HTTP云函数](/cli-v1/functions/deploy#http-函数) ### 打包部署 如果需要手动打包: ```bash # 创建部署包(排除虚拟环境) zip -r fastapi-app.zip third_party app.py scf_bootstrap ``` ## 第六步:访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问`HTTP 云函数`。 您可以访问以下接口: - 根路径:`/` - 欢迎信息 - API 文档:`/docs` - Swagger UI 文档 - 替代文档:`/redoc` - ReDoc 文档 - 健康检查:`/health` - 应用状态 - 用户 API:`/api/users` - RESTful 用户接口 ## 常见问题 ### Q: 为什么必须使用 9000 端口? A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。 ### Q: FastAPI 的自动文档在云函数中可以正常使用吗? A: 是的,FastAPI 的 Swagger UI 和 ReDoc 文档在云函数中可以正常访问和使用。 ### Q: 如何处理静态文件? A: FastAPI 可以通过 `StaticFiles` 中间件处理静态文件,或者使用 CDN 服务。 ### Q: 如何查看应用日志? A: 在 CloudBase 控制台的云函数页面,点击函数名称进入详情页查看运行日志。 ### Q: 支持哪些 Python 版本? A: CloudBase 支持 Python 3.6、3.7、3.9、3.10 等版本,建议使用 Python 3.10。 ## 最佳实践 ### 1. 环境变量管理 ```python import os from fastapi import FastAPI # 使用环境变量 DEBUG = os.getenv("DEBUG", "False").lower() == "true" SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key") app = FastAPI(debug=DEBUG) ``` ### 2. 数据库集成 ```python # 使用 SQLAlchemy from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./test.db") engine = create_engine(DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() ``` ### 3. 中间件配置 ```python from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware # CORS 配置 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Gzip 压缩 app.add_middleware(GZipMiddleware, minimum_size=1000) ``` ### 4. 日志配置 ```python import logging # 配置日志 logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) ``` ### 5. 依赖注入 ```python from fastapi import Depends def get_db(): db = SessionLocal() try: yield db finally: db.close() @app.get("/api/users") async def get_users(db: Session = Depends(get_db)): return db.query(User).all() ``` ## 进阶功能 ### 1. 身份验证 ```bash pip install "python-jose[cryptography]" "passlib[bcrypt]" python-multipart ``` ### 2. 后台任务 ```python from fastapi import BackgroundTasks @app.post("/send-email/") async def send_email(background_tasks: BackgroundTasks): background_tasks.add_task(send_email_task, "user@example.com") return {"message": "Email sent in background"} ``` ### 3. WebSocket 支持 ```python from fastapi import WebSocket @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() while True: data = await websocket.receive_text() await websocket.send_text(f"Message: {data}") ``` ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/function-configuration/config) - 学习如何 [连接 CloudBase 数据库](/api-reference/server/node-sdk/mysql/fetch) --- # 云函数/框架及示例/Springboot > 当前文档链接: https://docs.cloudbase.net/cloud-function/frameworks-examples/springboot [Spring Boot](https://spring.io/projects/spring-boot) 是基于 Spring 框架的快速开发脚手架,它简化了 Spring 应用的创建和部署过程。Spring Boot 提供了自动配置、内嵌服务器、生产就绪的特性监控等功能,让开发者能够快速构建独立的、生产级别的 Spring 应用程序。 本指南介绍如何在 CloudBase HTTP 云函数上部署 Gin 应用程序。 示例源码请参考: [cloudrun-springboot](https://github.com/TencentCloudBase/cloudrun-springboot) ## 前置条件 在开始之前,请确保您已经: - 安装了 [JDK 8](https://www.oracle.com/java/technologies/downloads/) 或更高版本 - 安装了 [Maven 3.6+](https://maven.apache.org/download.cgi) 或 [Gradle](https://gradle.org/install/) - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Java 和 Spring Boot 开发知识 ## 第一步:创建 Spring Boot 应用 > 💡 **提示**:如果您已经有一个 Spring Boot 应用,可以跳过此步骤。 ### 使用 Spring Initializr 创建项目 1. 访问 [start.spring.io](https://start.spring.io/) 2. 选择以下配置: ``` Project: Maven Language: Java Spring Boot: 2.7.18 (或最新稳定版) Project Metadata: - Group: com.tencent - Artifact: cloudrun-springboot - Name: cloudrun-springboot - Description: Demo project for Spring Boot - Package name: com.tencent.cloudrun - Packaging: Jar - Java: 8 Dependencies: - Spring Web - Spring Boot Actuator (健康检查) ``` 3. 点击 **GENERATE** 下载项目压缩包 4. 解压到本地目录 ### 使用 Maven 命令创建(可选) ```bash mvn archetype:generate \ -DgroupId=com.tencent.cloudrun \ -DartifactId=cloudrun-springboot \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DinteractiveMode=false cd cloudrun-springboot ``` ### 配置 pom.xml 文件 如果使用 Maven 命令创建项目,需要手动配置 `pom.xml` 文件以支持 Spring Boot: ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 2.7.18 com.tencent.cloudrun cloudrun-springboot 1.0-SNAPSHOT cloudrun-springboot Demo project for Spring Boot 8 8 8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-maven-plugin ``` ### 创建主应用类 如果使用 Maven 命令创建项目,还需要创建 Spring Boot 主应用类。 在 `src/main/java/com/tencent/cloudrun` 目录下创建 `CloudrunApplication.java`: ```java package com.tencent.cloudrun; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class CloudrunApplication { public static void main(String[] args) { SpringApplication.run(CloudrunApplication.class, args); } } ``` ### 本地测试应用 进入项目目录并启动应用: ```bash cd cloudrun-springboot mvn spring-boot:run ``` 打开浏览器访问 `http://localhost:8080`,您应该能看到 Spring Boot 默认页面。 ## 第二步:添加 API 接口 让我们创建一些 RESTful API 来演示 Spring Boot 的功能。 ### 创建用户实体类 在 `src/main/java/com/tencent/cloudrun/entity` 目录下创建 `User.java`: ```java package com.tencent.cloudrun.entity; public class User { private Long id; private String name; private String email; // 构造函数 public User() {} public User(Long id, String name, String email) { this.id = id; this.name = name; this.email = email; } // Getter 和 Setter public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } ``` ### 创建响应包装类 在 `src/main/java/com/tencent/cloudrun/dto` 目录下创建 `ApiResponse.java`: ```java package com.tencent.cloudrun.dto; public class ApiResponse { private boolean success; private String message; private T data; public ApiResponse(boolean success, String message, T data) { this.success = success; this.message = message; this.data = data; } public static ApiResponse success(T data) { return new ApiResponse<>(true, "操作成功", data); } public static ApiResponse error(String message) { return new ApiResponse<>(false, message, null); } // Getter 和 Setter public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } } ``` ### 创建用户控制器 在 `src/main/java/com/tencent/cloudrun/controller` 目录下创建 `UserController.java`: ```java package com.tencent.cloudrun.controller; import com.tencent.cloudrun.dto.ApiResponse; import com.tencent.cloudrun.entity.User; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; @RestController @RequestMapping("/api/users") public class UserController { private final List users = new ArrayList<>(); private final AtomicLong counter = new AtomicLong(); public UserController() { // 初始化测试数据 users.add(new User(counter.incrementAndGet(), "张三", "zhangsan@example.com")); users.add(new User(counter.incrementAndGet(), "李四", "lisi@example.com")); users.add(new User(counter.incrementAndGet(), "王五", "wangwu@example.com")); } @GetMapping public ApiResponse> getAllUsers( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int limit) { int startIndex = (page - 1) * limit; int endIndex = Math.min(startIndex + limit, users.size()); if (startIndex >= users.size()) { return ApiResponse.success(new ArrayList<>()); } List paginatedUsers = users.subList(startIndex, endIndex); return ApiResponse.success(paginatedUsers); } @GetMapping("/{id}") public ApiResponse getUserById(@PathVariable Long id) { User user = users.stream() .filter(u -> u.getId().equals(id)) .findFirst() .orElse(null); if (user == null) { return ApiResponse.error("用户不存在"); } return ApiResponse.success(user); } @PostMapping public ApiResponse createUser(@RequestBody User user) { if (user.getName() == null || user.getEmail() == null) { return ApiResponse.error("姓名和邮箱不能为空"); } user.setId(counter.incrementAndGet()); users.add(user); return ApiResponse.success(user); } } ``` ### 创建健康检查控制器 在 `src/main/java/com/tencent/cloudrun/controller` 目录下创建 `HealthController.java`: ```java package com.tencent.cloudrun.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; @RestController public class HealthController { @GetMapping("/health") public Map health() { Map health = new HashMap<>(); health.put("status", "healthy"); health.put("timestamp", LocalDateTime.now()); health.put("framework", "Spring Boot"); health.put("version", getClass().getPackage().getImplementationVersion()); health.put("java_version", System.getProperty("java.version")); return health; } @GetMapping("/") public Map home() { Map response = new HashMap<>(); response.put("message", "欢迎使用 Spring Boot CloudBase 应用!"); response.put("status", "running"); return response; } } ``` ### 配置应用属性 编辑 `src/main/resources/application.properties`: ```properties # 服务器配置 server.port=${PORT:8080} server.servlet.context-path=/ # 应用配置 spring.application.name=cloudrun-springboot management.endpoints.web.exposure.include=health,info # 日志配置 logging.level.com.tencent.cloudrun=INFO logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n ``` ## 第三步:本地测试 ### 启动应用 ```bash mvn spring-boot:run ``` ### 测试 API 接口 ```bash # 测试健康检查 curl http://localhost:8080/health # 测试首页 curl http://localhost:8080/ # 测试用户列表 curl http://localhost:8080/api/users # 测试分页 curl "http://localhost:8080/api/users?page=1&limit=2" # 测试获取单个用户 curl http://localhost:8080/api/users/1 # 测试创建用户 curl -X POST http://localhost:8080/api/users \ -H "Content-Type: application/json" \ -d '{"name":"新用户","email":"newuser@example.com"}' ``` ## 第四步: 准备部署文件 HTTP 云函数需要 `scf_bootstrap` 启动脚本和特定的端口配置。 ### 1. 修改应用配置 编辑 `src/main/resources/application.properties`,确保云函数环境使用 9000 端口: ```properties # 云函数端口配置(默认 8080,云函数环境使用 9000) server.port=${PORT:8080} server.servlet.context-path=/ # 应用配置 spring.application.name=cloudrun-springboot management.endpoints.web.exposure.include=health,info ``` > ⚠️ **重要提示**:CloudBase HTTP 云函数要求应用监听 9000 端口,通过环境变量 `PORT=9000` 来控制。 ### 2. 创建启动脚本 > 💡 **注意**: > > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 `nano scf_bootstrap` 或者 `vim scf_bootstrap` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 创建 `scf_bootstrap` 文件(无扩展名): ```bash #!/bin/bash export PORT=9000 export JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC" java $JAVA_OPTS -jar *.jar ``` > 💡 **说明**: > > - 使用 `*.jar` 通配符来匹配当前目录下的 JAR 包 > - CloudBase 云函数会将上传的文件解压到工作目录,JAR 包直接位于当前目录下 为启动脚本添加执行权限: ```bash chmod +x scf_bootstrap ``` ### 3. 构建 JAR 包 ```bash mvn clean package -DskipTests ``` ### 4. 项目结构 ``` cloudrun-springboot/ ├── src/ │ └── main/ │ ├── java/ │ └── resources/ ├── target/ │ └── *.jar # 构建产物(如:cloudrun-springboot-1.0-SNAPSHOT.jar) ├── pom.xml ├── scf_bootstrap # 🔑 云函数启动脚本 └── README.md ``` > 💡 **说明**: > > - `scf_bootstrap` 是 CloudBase 云函数的启动脚本 > - 云函数会将上传的文件解压到工作目录,JAR 包直接位于当前目录下 > - 设置 `PORT=9000` 环境变量确保应用监听正确端口 > - 配置 JVM 参数优化内存使用 ## 第五步: 部署应用 ### 通过控制台部署 > ⚠️ **注意**: > > - java 服务启动较慢,建议将函数执行超时时间调整为 30s 1. 登录 [CloudBase 控制台](https://tcb.cloud.tencent.com/dev) 2. 选择您的环境,进入「云函数」页面 3. 点击「新建云函数」 4. 选择「HTTP 云函数」 5. 填写函数名称(如:`springboot-app`) 6. 选择运行时:**Java 8**(或其他支持的版本) 7. 提交方法选择:**本地上传代码包** 8. 上传构建后的 JAR 包和 `scf_bootstrap` 文件 9. **自动安装依赖**:关闭(Java 应用无需此选项) 10. 点击「创建」按钮等待部署完成 ### 通过 CLI 部署 详情请参考 [部署HTTP云函数](/cli-v1/functions/deploy#http-函数) ### 打包部署 如果需要手动打包: ```bash # 构建应用 mvn clean package -DskipTests # 创建部署包(使用 -j 参数避免目录结构) zip -j springboot-app.zip target/*.jar scf_bootstrap ``` ## 第六步:访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问 HTTP 云函数。 ### 示例请求 ```bash # 健康检查 curl https://your-app-url/health # 获取用户列表 curl https://your-app-url/api/users # 分页查询 curl "https://your-app-url/api/users?page=1&limit=2" # 创建新用户 curl -X POST https://your-app-url/api/users \ -H "Content-Type: application/json" \ -d '{"name":"测试用户","email":"test@example.com"}' ``` ## 常见问题 #### Q: 为什么使用 `*.jar` 而不是具体的 JAR 包名称? A: 使用通配符有以下优势: - 避免因项目版本号变化导致的路径错误 - 简化部署流程,无需每次修改启动脚本 - 确保能够找到构建后的 JAR 包,无论其具体名称如何 - 云函数会将文件解压到当前工作目录,使用相对路径即可 #### Q: 如果当前目录下没有 JAR 包怎么办? A: 检查以下几点: 1. 确保使用 `zip -j` 参数打包,避免目录结构问题 2. 验证 `mvn clean package` 命令执行成功 3. 检查 `target` 目录下是否生成了 JAR 包 4. 确保打包时包含了 JAR 包文件 #### Q: 为什么使用 `zip -j` 参数? A: `-j` 参数的作用是"junk paths"(忽略路径): - 使用 `zip -j` 会忽略文件的目录结构,只保存文件本身 - 这样 `target/cloudrun-springboot-1.0-SNAPSHOT.jar` 在解压后会直接变成 `cloudrun-springboot-1.0-SNAPSHOT.jar` - 避免了在云函数中出现 `/opt/target/` 的多层目录问题 - 简化了打包过程,无需手动复制和删除文件 #### Q: `scf_bootstrap` 中的 `/opt` 目录是什么? A: `/opt` 是 CloudBase 云函数运行时环境的标准工作目录: - 当您上传代码包到云函数时,所有文件会被解压到 `/opt` 目录 - JAR 包、配置文件等都会位于 `/opt` 目录下 - 这是云函数平台的标准约定,无需手动创建 - 启动脚本中必须使用绝对路径 `/opt/your-jar-file.jar` 来引用 JAR 包 #### Q: 为什么 HTTP 云函数必须使用 9000 端口? A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。应用通过环境变量 `PORT=9000` 来控制端口,本地开发时默认使用 8080 端口。 #### Q: Spring Boot 应用冷启动时间较长怎么办? A: 可以通过以下方式优化: - 启用 `spring.main.lazy-initialization=true` - 减少自动配置的组件 - 使用 GraalVM 原生镜像(实验性) - 合理设置 JVM 参数 #### Q: 运行 `mvn spring-boot:run` 时提示 "No plugin found for prefix 'spring-boot'" 怎么办? A: 这是因为项目缺少 Spring Boot Maven 插件配置。解决方案: 1. 确保 `pom.xml` 中包含 `spring-boot-starter-parent` 作为父项目 2. 在 `` 部分添加 `spring-boot-maven-plugin` 3. 参考文档中的完整 `pom.xml` 配置示例 #### Q: 如何处理 JAR 包过大的问题? A: - 使用 `spring-boot-maven-plugin` 的 `repackage` 目标 - 排除不必要的依赖 - 使用 `provided` 作用域排除运行时不需要的依赖 #### Q: 云函数中如何查看 Spring Boot 日志? A: 在 CloudBase 控制台的云函数页面,点击函数名称进入详情页查看运行日志。 ## 最佳实践 ### 1. 环境变量管理 在 `application.properties` 中使用环境变量: ```properties # 数据库配置 spring.datasource.url=${DB_URL:jdbc:h2:mem:testdb} spring.datasource.username=${DB_USERNAME:sa} spring.datasource.password=${DB_PASSWORD:} # 应用配置 app.jwt.secret=${JWT_SECRET:mySecret} app.upload.path=${UPLOAD_PATH:/tmp/uploads} ``` ### 2. 端口配置策略 为了同时支持两种部署方式,建议使用动态端口配置: ```properties # 支持多环境端口配置(默认 8080,云函数环境通过 PORT=9000 控制) server.port=${PORT:8080} # 云函数环境检测 spring.profiles.active=${SPRING_PROFILES_ACTIVE:default} ``` ### 3. 添加全局异常处理 创建 `GlobalExceptionHandler.java`: ```java @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ApiResponse handleException(Exception e) { return ApiResponse.error("系统错误:" + e.getMessage()); } @ExceptionHandler(IllegalArgumentException.class) public ApiResponse handleIllegalArgument(IllegalArgumentException e) { return ApiResponse.error("参数错误:" + e.getMessage()); } } ``` ### 4. 配置 CORS 支持 创建 `WebConfig.java`: ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*"); } } ``` ### 5. JVM 参数优化 针对不同部署方式的 JVM 优化: ```bash # HTTP 云函数(内存受限) JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:MaxGCPauseMillis=200" # 云托管(更多资源) JAVA_OPTS="-Xmx1g -Xms512m -XX:+UseG1GC" ``` ### 6. 健康检查增强 自定义健康检查指示器: ```java @Component public class CustomHealthIndicator implements HealthIndicator { @Override public Health health() { // 检查应用状态 boolean isHealthy = checkApplicationHealth(); if (isHealthy) { return Health.up() .withDetail("status", "应用运行正常") .withDetail("timestamp", LocalDateTime.now()) .build(); } else { return Health.down() .withDetail("status", "应用异常") .build(); } } private boolean checkApplicationHealth() { // 实现具体的健康检查逻辑 return true; } } ``` ### 7、部署前检查清单 - [ ] `scf_bootstrap` 文件存在且有执行权限 - [ ] 端口配置为 9000 - [ ] JAR 包构建成功且可执行 - [ ] JVM 参数适合云函数环境 - [ ] 启用懒加载优化冷启动 - [ ] 测试本地启动是否正常 ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/function-configuration/config) - 学习如何 [连接 CloudBase 数据库](/api-reference/server/node-sdk/mysql/fetch) --- # 云函数/框架及示例/Gin > 当前文档链接: https://docs.cloudbase.net/cloud-function/frameworks-examples/gin [Gin](https://gin-gonic.com/) 是一个用 Go 语言编写的 HTTP Web 框架。它具有类似 Martini 的 API,但性能比 Martini 快 40 倍。Gin 使用了自定义版本的 HttpRouter,因此它不仅提供了极快的路由,还提供了中间件支持。 本指南介绍如何在 CloudBase HTTP 云函数上部署 Gin 应用程序。 示例源码请参考: [cloudrun-gin](https://github.com/TencentCloudBase/cloudrun-gin) ## 前置条件 在开始之前,请确保您已经: - 安装了 [Go 1.23](https://golang.org/dl/) 或更高版本 - 拥有腾讯云账号并创建了云开发环境 - 了解基本的 Go 语言和 Gin 框架开发知识 ## 第一步:创建 Gin 应用 > 💡 **提示**:如果您已经有一个 Gin 应用,可以跳过此步骤。 ### 创建项目目录 ```bash mkdir cloudrun-gin cd cloudrun-gin ``` ### 初始化 Go 模块 ```bash go mod init cloudrun-gin go get -u github.com/gin-gonic/gin ``` ### 创建主应用文件 在 `cloudrun-gin` 目录下创建 `main.go` 文件: ```go package main import ( "net/http" "os" "github.com/gin-gonic/gin" ) func main() { // 设置 Gin 模式 if os.Getenv("GIN_MODE") == "" { gin.SetMode(gin.ReleaseMode) } router := gin.Default() // 基础路由 router.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "欢迎使用 Gin CloudBase 应用!", "status": "running", }) }) // 健康检查 router.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "healthy", "framework": "Gin", "go_version": "1.19+", "gin_version": gin.Version, }) }) // 获取端口,支持环境变量 port := os.Getenv("PORT") if port == "" { port = "8080" } // 启动服务器 router.Run(":" + port) } ``` ### 本地测试应用 启动应用: ```bash go run main.go ``` 打开浏览器访问 `http://localhost:8080`,您应该能看到 JSON 响应。 ## 第二步:添加 API 路由 让我们创建一个 RESTful API 来演示 Gin 的功能。 ### 创建用户模型 在项目根目录创建 `models` 目录,并创建 `user.go` 文件: ```go package models type User struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` } type ApiResponse struct { Success bool `json:"success"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` } ``` ### 创建用户控制器 在项目根目录创建 `controllers` 目录,并创建 `user.go` 文件: ```go package controllers import ( "net/http" "strconv" "sync" "cloudrun-gin/models" "github.com/gin-gonic/gin" ) var ( users []models.User usersMu sync.RWMutex nextID = 1 ) func init() { // 初始化测试数据 users = []models.User{ {ID: 1, Name: "张三", Email: "zhangsan@example.com"}, {ID: 2, Name: "李四", Email: "lisi@example.com"}, {ID: 3, Name: "王五", Email: "wangwu@example.com"}, } nextID = 4 } // GetUsers 获取用户列表 func GetUsers(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) usersMu.RLock() defer usersMu.RUnlock() startIndex := (page - 1) * limit endIndex := startIndex + limit if startIndex >= len(users) { c.JSON(http.StatusOK, models.ApiResponse{ Success: true, Message: "获取成功", Data: []models.User{}, }) return } if endIndex > len(users) { endIndex = len(users) } paginatedUsers := users[startIndex:endIndex] c.JSON(http.StatusOK, models.ApiResponse{ Success: true, Message: "获取成功", Data: gin.H{ "total": len(users), "page": page, "limit": limit, "items": paginatedUsers, }, }) } // GetUser 根据ID获取用户 func GetUser(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, models.ApiResponse{ Success: false, Message: "无效的用户ID", }) return } usersMu.RLock() defer usersMu.RUnlock() for _, user := range users { if user.ID == id { c.JSON(http.StatusOK, models.ApiResponse{ Success: true, Message: "获取成功", Data: user, }) return } } c.JSON(http.StatusNotFound, models.ApiResponse{ Success: false, Message: "用户不存在", }) } // CreateUser 创建用户 func CreateUser(c *gin.Context) { var newUser models.User if err := c.ShouldBindJSON(&newUser); err != nil { c.JSON(http.StatusBadRequest, models.ApiResponse{ Success: false, Message: "请求参数错误: " + err.Error(), }) return } if newUser.Name == "" || newUser.Email == "" { c.JSON(http.StatusBadRequest, models.ApiResponse{ Success: false, Message: "姓名和邮箱不能为空", }) return } usersMu.Lock() newUser.ID = nextID nextID++ users = append(users, newUser) usersMu.Unlock() c.JSON(http.StatusCreated, models.ApiResponse{ Success: true, Message: "创建成功", Data: newUser, }) } ``` ### 更新主应用文件 更新 `main.go` 文件,添加路由和中间件: ```go package main import ( "fmt" "net/http" "os" "time" "cloudrun-gin/controllers" "github.com/gin-gonic/gin" ) func main() { // 设置 Gin 模式 if os.Getenv("GIN_MODE") == "" { gin.SetMode(gin.ReleaseMode) } router := gin.Default() // 添加 CORS 中间件 router.Use(func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(http.StatusNoContent) return } c.Next() }) // 添加日志中间件 router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", param.ClientIP, param.TimeStamp.Format(time.RFC1123), param.Method, param.Path, param.Request.Proto, param.StatusCode, param.Latency, param.Request.UserAgent(), param.ErrorMessage, ) })) // 基础路由 router.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "欢迎使用 Gin CloudBase 应用!", "status": "running", }) }) // 健康检查 router.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "healthy", "timestamp": time.Now().Format(time.RFC3339), "framework": "Gin", "go_version": "1.19+", "gin_version": gin.Version, }) }) // API 路由组 api := router.Group("/api") { users := api.Group("/users") { users.GET("", controllers.GetUsers) users.GET("/:id", controllers.GetUser) users.POST("", controllers.CreateUser) } } // 获取端口,支持环境变量 port := os.Getenv("PORT") if port == "" { port = "8080" } // 启动服务器 router.Run(":" + port) } ``` ## 第三步:本地测试 ### 更新依赖 ```bash go mod tidy ``` ### 启动应用 ```bash go run main.go ``` ### 测试 API 接口 ```bash # 测试健康检查 curl http://localhost:8080/health # 测试首页 curl http://localhost:8080/ # 测试用户列表 curl http://localhost:8080/api/users # 测试分页 curl "http://localhost:8080/api/users?page=1&limit=2" # 测试获取单个用户 curl http://localhost:8080/api/users/1 # 测试创建用户 curl -X POST http://localhost:8080/api/users \ -H "Content-Type: application/json" \ -d '{"name":"新用户","email":"newuser@example.com"}' ``` ## 第四步:准备部署文件 ### 1. 创建启动脚本 > 💡 **注意**: > > - 在 windows 下创建 `scf_bootstrap` 文件时,优先使用 `nano scf_bootstrap` 或者 `vim scf_bootstrap` 创建 > - 在 windows 下使用 vscode 创建 `scf_bootstrap` 文件时,部署到 HTTP 云函数可能会报错: `scf_bootstrap` 文件不存在 > - 这个错误是因为脚本文件包含了 Windows 格式的回车符(^M),导致 Linux 无法正确识别解释器路径。这是 WSL 中常见的问题 创建 `scf_bootstrap` 文件(无扩展名): ```bash #!/bin/bash export PORT=9000 export GIN_MODE=release ./main ``` 为启动脚本添加执行权限: ```bash chmod +x scf_bootstrap ``` ### 2. 编译应用 编译 Go 应用为 Linux 二进制文件: ```bash # 交叉编译为 Linux 64位 GOOS=linux GOARCH=amd64 go build -o main . ``` ### 3. 项目结构 ``` cloudrun-gin/ ├── controllers/ │ └── user.go ├── models/ │ └── user.go ├── main.go ├── go.mod ├── go.sum ├── main # 编译后的二进制文件 └── scf_bootstrap # 🔑 云函数启动脚本 ``` > 💡 **说明**: > > - `scf_bootstrap` 是 CloudBase 云函数的启动脚本 > - 需要将 Go 应用编译为 Linux 二进制文件 > - 设置 `PORT=9000` 环境变量确保应用监听正确端口 > - 设置 `GIN_MODE=release` 优化性能 ## 第五步: 部署到 CloudBase ### 通过控制台部署 1. 登录 [CloudBase 控制台](https://tcb.cloud.tencent.com/dev) 2. 选择您的环境,进入「云函数」页面 3. 点击「新建云函数」 4. 选择「HTTP 云函数」 5. 填写函数名称(如:`gin-app`) 6. 选择运行时:**Go 1.x**(或其他支持的版本) 7. 提交方法选择:**本地上传代码包** 8. 上传编译后的二进制文件和 `scf_bootstrap` 文件 9. **自动安装依赖**:关闭(Go 应用无需此选项) 10. 点击「创建」按钮等待部署完成 ### 打包部署 如果需要手动打包: ```bash # 编译应用 GOOS=linux GOARCH=amd64 go build -o main . # 创建部署包 zip -j gin-app.zip main scf_bootstrap ``` ## 第六步: 访问您的应用 部署成功后,您可以参考[通过 HTTP 访问云函数](/service/access-cloud-function)设置自定义域名访问`HTTP 云函数`。 ### 示例请求 ```bash # 健康检查 curl https://your-app-url/health # 获取用户列表 curl https://your-app-url/api/users # 分页查询 curl "https://your-app-url/api/users?page=1&limit=2" # 创建新用户 curl -X POST https://your-app-url/api/users \ -H "Content-Type: application/json" \ -d '{"name":"测试用户","email":"test@example.com"}' ``` ## 常见问题 ### Q: 为什么 HTTP 云函数必须使用 9000 端口? A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。应用通过环境变量 `PORT=9000` 来控制端口,本地开发时默认使用 8080 端口。 ### Q: Go 应用如何进行交叉编译? A: 使用以下命令进行交叉编译: ```bash GOOS=linux GOARCH=amd64 go build -o main . ``` ### Q: 如何优化 Go 应用的冷启动时间? A: - 减少依赖包数量 - 使用 `gin.ReleaseMode` 模式 - 避免在启动时进行重复的初始化操作 - 合理设置内存配置 ### Q: scf_bootstrap 文件有什么作用? A: `scf_bootstrap` 是云函数的启动脚本,用于设置环境变量和启动编译后的二进制文件。 ## 下一步 - 了解更多 [HTTP 云函数配置选项](/cloud-function/function-configuration/config) - 学习如何 [连接 CloudBase 数据库](/api-reference/server/node-sdk/mysql/fetch) --- # 云函数/常见问题/常见问题 > 当前文档链接: https://docs.cloudbase.net/cloud-function/faq ## 性能相关 ### 调用云函数耗时较长 可以通过排查云函数日志分析耗时位置。如果日志包含 `Coldstart: xxxms` 类日志,则为云函数冷启动耗时。如果希望解决该部分耗时,可以参考 [预置并发](/cloud-function/alias)。 ### 云函数执行超时怎么处理? 超时客户端会直接断开连接并报错,建议控制函数执行时间,尽量不要把耗时长的操作放在客户端直接调用的云函数内。 ### 如果需要在云函数执行一些长耗时的处理怎么办? 为了保证客户端的体验,不允许直接调用长耗时的云函数。 - 建议在云控制台调整为需要的超时时间(上限为 60s),在云函数内使用 callback() 返回成功,客户端不等待执行结果。 - 云函数会在超时时间内继续执行直到完毕或超时,将执行结果写入数据库等存储服务,再在客户端获取该结果。 ## 配置相关 ### 调整云函数内存配置不生效 云函数配置对 `$LATEST` 版本生效。如果请求的不是 `$LATEST` 版本,则配置修改不会生效。 **解决方案:** - 确保请求的是 `$LATEST` 版本 - 或者基于 `$LATEST` 版本发布新版本,新版本会使用当前的快照,包含代码和配置(超时时间、环境变量、内存配置等) ### 云函数内置模块怎么使用? - 云函数内置模块已经集成于运行环境,可以直接使用。 - 如果内置模块的版本不能满足需求,可以自行安装模块到云函数内,默认会被优先使用。 > 目前已支持的内置模块为 request 2.87.1 。 ### 云函数内怎么保存密钥等配置信息 云函数是无状态的,配置信息建议存储在云开发的数据库服务 ## 部署相关 ### 为什么云函数更新时,返回 exit status 11? 请检查一下函数的打包方式和入口方法。 - 云函数创建时,默认执行方法是 index.main ,其入口文件为 index.js ,且 index.js 必须用 export 暴露出 main 方法。 - 云函数更新时,如果选择用 zip 包上传,打包的方式需要注意,要保证 zip 包解压后的第一级内容必须包含入口文件(常见的错误打包方式是,将某个云函数代码放在某个文件夹内,并针对这个文件夹进行压缩,这样解压出来的文件是一个文件夹,不包含入口文件 index.js )。 ### Windows 环境下打包云函数层部署失败 在 Windows 环境下打包云函数层(Layer)的 zip 文件后,部署到云函数时可能会遇到 `An internal error has occurred` 报错。 **问题现象** 在 Windows 系统下使用系统自带的压缩工具或 PowerShell 的 `Compress-Archive` 命令打包云函数层后,上传部署时报错: ``` 函数更新失败 An internal error has occurred. Retry your request, but if the problem persists, contact us with details ``` **问题原因** Windows 系统打包的 zip 文件,内部的路径分隔符使用的是反斜杠 `\`,而云函数的构建环境是 Linux 系统,要求路径分隔符为正斜杠 `/`。 这会导致 Linux 环境无法正确解析 zip 包中的文件路径,从而引发部署失败。 **解决方案** **方案一:使用 7-Zip 打包(推荐)** 7-Zip 是一个开源的压缩工具,打包时会使用 Unix 风格的路径分隔符。 1. 下载安装 [7-Zip](https://www.7-zip.org/) 2. 右键选择要打包的文件夹 3. 选择 "7-Zip" -> "添加到压缩包" 4. 压缩格式选择 "zip" **方案二:使用 WSL 打包** 如果已安装 WSL (Windows Subsystem for Linux),可以在 WSL 环境中使用 `zip` 命令打包: ```bash # 进入 WSL 环境 wsl # 进入层文件所在目录 cd /mnt/c/your/layer/path # 使用 zip 命令打包 zip -r layer.zip ./* ``` **方案三:使用 Git Bash 打包** Git for Windows 自带的 Git Bash 也可以使用 Unix 风格的 zip 命令: ```bash # 打开 Git Bash,进入层文件目录 cd /c/your/layer/path # 打包 zip -r layer.zip ./* ``` ## 调试相关 ### 云函数测试时,部分日志丢失了? - 云函数测试时,如果以同步调用的方式(超时时间小于 20 秒),返回的日志最多为 4k,超过 4k 的日志不显示。 - 如果以异步调用的方式(超时时间大于或等于 20 秒),返回的日志最多为 6M,超过 6M 的日志不显示。 ### 为什么云函数中调用其他云函数报错 socket timeout,但是被调用的云函数实际是有执行成功并且返回数据的? 您可在页面上的函数配置-基本信息中,为云函数设置超时时长。同时,如果使用到 SDK,SDK 默认的超时时间是 15 秒,两者以最短时间为准。 ## 使用相关 ### 云函数如何调用云函数 目前只可以通过云开发提供的 SDK 调用,没有其它方式 ### 不同的云函数可以共用代码文件(目录)吗 未上线 ### Node.js 云函数同时支持异步和同步写法吗 是的,推荐使用同步(async/await)写法。 ### 云函数 HTTP 访问配置身份验证后报 MISSING_CREDENTIALS 错误 当云函数配置了 HTTP 访问服务后,在环境配置中对该云函数开启身份认证,会导致原先可直接访问的 URL 报 `MISSING_CREDENTIALS` 错误。 **问题现象** - 云函数已配置 HTTP 访问服务路径,可通过 URL 直接访问 - 在环境配置中对该云函数开启身份认证后 - 访问该 URL 返回 `MISSING_CREDENTIALS` 错误 **问题原因** 开启身份验证后,所有请求都需要携带有效的身份凭证(如 Access Token 或 API Key)才能访问云函数。这是云开发的安全机制,用于保护云函数不被未授权访问。 **解决方案** **方案一:使用 Access Token 认证** 在请求头中携带 Access Token: ```javascript // 先通过登录获取 Access Token const response = await fetch( "https://your-env-id.api.tcloudbasegateway.com/v1/functions/your-function-name", { headers: { Authorization: "Bearer your-access-token", }, }, ); ``` Access Token 获取方法请参考 [AccessToken 使用指南](/http-api/basic/access-token) **方案二:使用 API Key 认证** 如果是服务端调用,可以使用 API Key: ```javascript const response = await fetch( "https://your-env-id.api.tcloudbasegateway.com/v1/functions/your-function-name", { headers: { Authorization: "Bearer your-api-key", }, }, ); ``` **方案三:使用 Publishable Key 认证** 如果是客户端公开访问场景,可以使用 Publishable Key: ```javascript const response = await fetch( "https://your-env-id.api.tcloudbasegateway.com/v1/functions/your-function-name", { headers: { Authorization: "Bearer your-publishable-key", }, }, ); ``` **方案四:关闭身份验证** 如果该云函数需要公开访问,可以关闭身份验证: 1. 登录 [云开发控制台](https://tcb.cloud.tencent.com/dev) 2. 进入【环境】-【HTTP 访问服务】 3. 找到对应的云函数路径,关闭身份验证 :::warning 注意 关闭身份验证意味着任何人都可以访问该云函数,请确保云函数内部有适当的安全校验逻辑。 ::: ## 在线开发相关 ### 在线编辑器的 Python 运行时环境与云函数实际运行环境不一致 **问题原因** 在线编辑器的开发环境是独立环境,与云函数的实际运行环境是分离的。在线编辑器环境主要用于代码编写和简单开发,不会因为删除或重新创建云函数而自动同步运行时版本。 同时,在线编辑器需要从运行环境下载代码后才能进行在线开发,因此可能会存在多个云函数代码同时下载到在线编辑器的情况。 **解决方案** 1. **在线开发时保持在当前函数目录操作** - 在线编辑器中可能存在多个云函数的代码,请确保在当前云函数所在的代码目录进行操作即可,不影响云函数的实际运行 2. **本地开发部署(推荐)** - 如果对 Python 小版本有强依赖要求,建议在本地开发环境中使用对应的 Python 版本进行开发,然后通过控制台或命令行工具部署到云函数 :::tip 提示 云函数的实际运行环境与在线编辑器的开发环境是独立的。创建 Python 3.10 的云函数后,该函数在云端的实际运行环境就是 Python 3.10,不受在线编辑器环境版本的影响。 ::: ### 云函数等在线开发功能无法正常使用 **问题原因** 云开发平台中的在线开发使用的编辑器是 CloudStudio,并依赖于"Tencent CloudBase Toolkit V2"扩展来实现云开发的在线开发能力。因此需要确保"Tencent CloudBase Toolkit V2"这一扩展始终是最新版本 **解决方式** 1. 尝试重新打开在线编辑器 当您重新打开在线编辑器时,系统会自动检测并更新扩展,更新成功后会提示刷新,如下图所示: ![](https://qcloudimg.tencent-cloud.cn/raw/b4157e12162f5ffd0177526b520b3675.png) ![](https://qcloudimg.tencent-cloud.cn/raw/e2d5651379ab175fd3d1a6af1678589d.png) 2. 手动更新扩展 有时候,由于扩展市场的网络问题可能导致自动更新失败,在这种情况下,您可以尝试手动更新扩展,如下图所示: ![](https://qcloudimg.tencent-cloud.cn/raw/4c206aa83602a909f9acb9a16fbfff33.png) 如果更新依然失败,可以按照提示将扩展下载到本地,然后将其拖到编辑器中进行安装,如下图所示: ![](https://qcloudimg.tencent-cloud.cn/raw/4febb2e79a9fdbc5d9f0b6bee5ff1320.png) ### 在云开发平台删除云函数后,在线编辑器仍存在这个云函数 **原因分析** 在云开发平台中,云函数属于部署服务,而在线编辑器用于存放云函数的代码。这两者相互独立,删除云函数部署并不会自动删除对应的代码 打个比方,我们在本地编写一个服务的代码,然后将其部署到线上环境。当不再需要这个服务时,我们可以把服务下线,也就是删除部署。但此时,本地的服务代码依然会保留,并不会因为线上服务的删除而自动消失。 云开发平台的在线编辑器,其实就相当于本地编辑器的远程版本,本质上只是换了个地方编写代码,其代码存储机制和本地编写、部署的模式原理一致 **解决方法** 若你已明确不再需要某个云函数的代码,直接在在线编辑器中删除该云函数代码就行 ### 在云开发平台删除并重新创建了同名的云函数后,在线编辑器仍显示之前的版本 **原因分析** 这与在线编辑器的自动下载策略有关。当工作空间中已经存在同名的云函数时,为了避免代码被覆盖,在线编辑器会自动跳过下载过程,并提示用户手动下载更新 ![](https://qcloudimg.tencent-cloud.cn/raw/af910b11a45c699ec12544d973baa500.png) **解决方法** 通过手动下载方式同步云函数代码即可 具体步骤参考[云函数在线开发](/cloud-function/online-dev#下载)手册中的下载部分 ### 创建了函数型云托管后,进入在线开发时下载失败 **原因分析** 函数型云托管是基于云托管的。在没有部署历史的情况下,在线开发无法下载到对应的代码包,所以会出现以下提示 ![](https://qcloudimg.tencent-cloud.cn/raw/7366a1e56047056ec9c9c338598a9d74.png) **解决方法** 请耐心等待云函数的部署完成,然后再进入在线开发进行操作 ### 在线开发的功能操作菜单在哪里? 请参考 [云函数在线开发](/cloud-function/online-dev#下载) ### pnpm 安装依赖时报错 ![](https://qcloudimg.tencent-cloud.cn/raw/5789ee6efee29e25f42a98279f67e35a.png) **原因分析** 项目的 package.json 中声明的 pnpm 版本与当前环境安装的 pnpm 版本不一致 **解决方法** 打开终端,执行命令 `corepack enable` 即可 ### 在线编辑器提示磁盘使用率过高 当在线编辑器的磁盘空间使用率超过 85% 时,您将会看到以下提示: 您可以点击【自动清理缓存】按钮,清理 npm / yarn / pnpm / pip 等依赖缓存。同时请检查工作空间中是否存在较大的文件(如压缩包、二进制安装文件等),建议仅保留必要的代码文件。 ![](https://qcloudimg.tencent-cloud.cn/raw/d00ed6f81dc5b2e5e22fab4bf9e5fc9b.png) 若磁盘空间使用率超过 90%,为了确保在线编辑器的正常运行,系统将自动进行缓存清理。 ![](https://qcloudimg.tencent-cloud.cn/raw/c8c4c998ac1be9e0328a2a50212202a7.png) 您可以通过执行命令 "df -h" 来查看工作空间的磁盘使用情况。 ![](https://qcloudimg.tencent-cloud.cn/raw/19306132bf3e30a002519190776386b9.png) ### 获取临时凭证超时,在线编辑器部分功能将无法正常使用 ![](https://qcloudimg.tencent-cloud.cn/raw/3e99f5cb0b94104c8ed00999ac32d2d2.png) **原因分析** 当遇到上述提示时,首先请检查在线编辑器中是否已安装"Tencent CloudBase Toolkit V2"扩展。如果未安装,请在扩展市场中搜索关键字"Tencent CloudBase Toolkit V2"来查找并安装该扩展,然后刷新页面重试 如果不是以上问题,那么很可能是在线编辑器的磁盘空间不足,导致扩展执行异常,请根据以下解决方法进行处理 **解决方法** 通常,进入在线编辑器时会有相关提示,参考[在线编辑器提示磁盘使用率过高](#在线编辑器提示磁盘使用率过高) 如果没有提示,您可以先打开终端,执行命令 `df -h` 查看磁盘的剩余空间 ![](https://qcloudimg.tencent-cloud.cn/raw/c12e5d3edf9554bce614d63496a6360c.png) 如果发现磁盘空间已经所剩无几,可以先尝试运行以下命令清除缓存,同时清理工作空间中非必要的大文件 ```sh npm cache clean --force pnpm store prune yarn cache clean pip cache purge ``` 最后,再次通过 `df -h` 命令检查磁盘空间使用率是否有所降低。如果空间使用率有所降低,可重新进入在线编辑器进行操作 ### 打开云函数调试服务链接时提示登录 ![](https://qcloudimg.tencent-cloud.cn/raw/30315b7e72c708ce0c236ce989999aee.png) **原因分析** 调试链接仅在在线编辑器内有效。为了防止外部访问,调试链接只能在【测试面板】内进行访问,其他位置均无法访问 **解决方法** 请使用在线编辑器调试时自动打开的【测试面板】进行调试操作,如下图所示: ![](https://qcloudimg.tencent-cloud.cn/raw/57378b1236cf285ef193f565d9f5adeb.png) --- # 云函数/常见问题/HTTP 云函数相关问题 > 当前文档链接: https://docs.cloudbase.net/cloud-function/faq/web-func 参考: [HTTP 云函数相关问题](https://cloud.tencent.com/document/product/583/56129) --- # 云函数/常见问题/网络相关问题 > 当前文档链接: https://docs.cloudbase.net/cloud-function/faq/network 参考: [网络相关问题](https://cloud.tencent.com/document/product/583/55718) --- # 云函数/常见问题/事件处理相关 > 当前文档链接: https://docs.cloudbase.net/cloud-function/faq/event 参考: [事件处理相关](https://cloud.tencent.com/document/product/583/18264) --- # run Documentation > 云开发托管服务,支持容器化应用部署,提供自动伸缩、负载均衡、版本管理等企业级功能 # 云托管/未命名文档 > 当前文档链接: https://docs.cloudbase.net/run/introduction --- sidebar_label: 概述 --- import AIDevelopmentPrompt from '../ai/cloudbase-ai-toolkit/components/AIDevelopmentPrompt'; # 云托管 云托管(Tencent CloudBase Run)是新一代云原生应用引擎,支持托管用任意语言和框架编写的容器化应用。无需关心服务器运维,即可快速部署和运行您的应用,享受自动弹性伸缩、按量计费的云服务体验。 ## 💡 为什么选择云托管? 传统应用部署需要购买服务器、配置环境、处理运维问题,而云托管让您: - **专注业务开发**:无需关心底层基础设施,将精力投入到产品创新 - **快速上线应用**:从代码到上线只需几分钟,支持多种部署方式 - **降低运营成本**:按实际使用付费,流量低谷时自动缩容到 0,避免资源浪费 - **应对流量波动**:自动扩缩容应对突发流量,保障服务稳定性 - **服务资源集成** - 与数据库、存储、腾讯云资源无缝集成 ## 🎯 适用场景 云托管适合各类 Web 应用和后端服务: - **Web 应用**:企业官网、电商平台、管理后台、SaaS 服务 - **API 服务**:RESTful API、微服务架构 - **实时应用**:WebSocket 服务、SSE 推送、实时通讯 - **定时任务**:数据处理、报表生成、定时同步 - **AI 应用**:模型推理服务、智能客服、内容生成 :::tip 快速开始 如果您是首次使用云托管,建议先阅读 [快速开始](./quick-start/introduce.md) 文档,5 分钟快速体验云托管的核心功能。 ::: ## 核心特性 ### 🚀 零运维,开箱即用 无需购买、配置、维护服务器,平台自动处理: - 服务器资源调度与管理 - 操作系统更新与安全补丁 - 负载均衡与健康检查 - SSL 证书自动续期 **实际收益**:运维成本降低 80%,让 2 人团队也能运营大规模应用。 ### 📈 智能弹性伸缩 根据实际负载自动调整实例数量: - **流量高峰**:秒级扩容,最多支持 n 个实例 - **流量低谷**:自动缩容到 0,不产生费用 - **灵活策略**:支持基于 CPU、内存、并发数的扩缩容规则 - **定时伸缩**:工作时间保持实例,非工作时间自动释放 **实际收益**:某电商应用在促销期间自动扩容应对 10 倍流量,活动结束后自动缩容,成本节省 60%。 ### 💰 按量计费,用多少付多少 - **精细计费**:最小粒度 0.25 核 0.5GB,按秒计费 - **零流量零费用**:无请求时缩容到 0,不产生计算费用 - **透明可控**:实时查看资源使用和费用明细 - **成本优化**:支持预留实例,长期使用更优惠 **实际收益**:相比传统云主机,中小型应用平均节省 50-70% 成本。 ### 🛠️ 全栈语言支持 支持任意语言和框架,只要能容器化就能部署: - **Node.js**:Express、Nest.js、Next.js、Koa - **Python**:Flask、Django、FastAPI - **Java**:Spring Boot、Quarkus - **Go**:Gin、Echo、Fiber - **PHP**:Laravel、ThinkPHP、Symfony - **.NET**:ASP.NET Core - **其他**:Ruby、Rust、Elixir 等 ### 🔄 多种部署方式 灵活选择最适合您的部署方式: | 部署方式 | 适用场景 | 优势 | |------------|-----------|------| | **代码部署** | 快速原型、小型项目 | 无需编写 Dockerfile,平台自动构建 | | **镜像部署** | 生产环境、复杂应用 | 完全控制构建过程,支持私有镜像仓库 | | **Git 部署** | 团队协作、持续集成 | 代码推送自动部署,支持 GitHub/GitLab/Gitee | | **Cli 部署** | 快速部署、持续集成 | 云开发官方提供的命令行工具,帮助您快速管理和部署云开发资源 | ## 技术选型指南 云开发平台提供多种计算服务,满足不同场景需求。本节帮助您选择最适合的技术方案。 ### 🤔 如何选择? **选择云托管,如果您:** - ✅ 需要使用 Java、Go、PHP、.NET 等非 Node.js 语言 - ✅ 有现有应用需要迁移上云 - ✅ 需要长连接支持(WebSocket、SSE) - ✅ 需要高并发处理能力 - ✅ 需要访问 VPC 内资源(Redis、MySQL 等) - ✅ 需要完全控制运行环境和依赖 **选择云函数,如果您:** - ✅ 开发简单的 API 接口或定时任务 - ✅ 需要事件驱动(URL、MQ 触发) - ✅ 追求极简部署,无需关心容器 - ✅ 需要访问 VPC 内资源(Redis、MySQL 等) - ✅ 流量极低,希望最大化节省成本 ### 📊 详细对比 | 对比维度 | 事件函数 | HTTP 函数 | 函数型开发框架 | 云托管 ⭐ | |---------|---------|----------|--------------|----------| | **概述** | [事件函数](../cloud-function/introduce) | [HTTP 函数](../cloud-function/introduce) | [函数型开发框架](../cbrf/intro) | [云托管](../run/introduction) | | **设计思想** | 纯托管 FaaS
事件驱动 | Serverless Web
HTTP 直连 | Node.js 函数框架
部署在云托管 | 云原生应用引擎
容器化托管 | | **核心场景** | 对象存储、消息队列
定时任务等非 HTTP 业务 | Web 建站、API 服务
HTTP/SSE/WebSocket | Node.js 环境服务
HTTP/SSE/WebSocket | 任意语言/框架
微服务、Web 应用
HTTP/SSE/WebSocket | | **触发方式** | 定时、MQ、COS
云 API 等多种触发器 | HTTP URL
WebSocket / SSE | HTTP URL
WebSocket / SSE | HTTP URL
WebSocket / SSE | | **编程范式** | JSON 入参
(event/context) | 原生 Request
HTTP / WebSocket | JSON 入参
(event/context) | 原生 Request
HTTP / WebSocket | | **运行时** | 固定语言版本
或镜像自定义 | 固定语言版本
或镜像自定义 | 仅 Node.js | **任意语言版本/环境** | | **部署方式** | 模板/代码/镜像 | 模板/代码/镜像 | 模板 | **模板/代码/镜像/Git** | | **端口监听** | ❌ 无需配置 | ✅ 需监听 9000 | ✅ 需监听 3000 | ✅ **自定义端口** | | **启动文件** | ⚠️ 代码部署不支持自定义
镜像支持 | ⚠️ 代码部署不支持自定义
镜像支持 | ❌ 不支持自定义 | ✅ **支持自定义** | | **Dockerfile** | ❌ 不感知 | ⚠️ 仅镜像部署时感知 | ❌ 不感知 | ✅ **完全支持** | | **本地调试** | ❌ 不支持 | ✅ 支持 | ✅ 支持 | ✅ **支持** | | **冷启动** | 存在冷启动延迟 | 存在冷启动延迟 | 可配最小实例常驻
(几乎无延迟) | **可配最小实例常驻**
(几乎无延迟) | | **并发支持** | ❌ 不支持 (单并发) | ✅ 支持多并发 | ✅ 支持多并发 | ✅ **支持多并发** | | **云开发数据库** | ❌ 内网网络不通 | ❌ 内网网络不通 | ✅ 内网连通
(需开启私有网络) | ✅ **内网连通**
(需开启私有网络) | | **VPC 访问** | ❌ 不支持 | ❌ 不支持 | ✅ 支持 | ✅ **支持** | | **适合团队** | 个人开发者 | 个人/小团队 | Node.js 开发者 | **所有开发团队** | :::tip 推荐 对于大多数 Web 应用和后端服务,**云托管**是最佳选择,提供最大的灵活性和可扩展性。 ::: ## 资源模型 云托管采用三层资源模型:**服务 → 版本 → 实例**,提供灵活的部署和管理能力。 ### 📦 服务(Service) 服务是您的业务单元,通常对应一个独立的应用或微服务。 **特点:** - 每个服务有独立的访问域名(支持自定义域名) - 独立的配置和资源限制 - 独立的日志和监控 **示例场景:** - `user-service`:用户管理服务 - `order-service`:订单处理服务 - `api-gateway`:API 网关服务 ### 🔖 版本(Revision) 版本是服务的不同部署版本,支持多版本并存。 **特点:** - 每次部署创建新版本,版本不可变 - 支持流量分配,实现灰度发布 - 可快速回滚到历史版本 **示例场景:** - **灰度发布**:v1 版本 90% 流量,v2 版本 10% 流量 - **A/B 测试**:v1 和 v2 各 50% 流量,对比效果 - **蓝绿部署**:新版本验证通过后,一键切换 100% 流量 ### 🖥️ 实例(Instance) 实例是实际运行的容器,数量根据负载自动伸缩。 **特点:** - 每个实例是一个独立的容器 - 自动健康检查,异常实例自动重启 - 支持 0 ~ n 个实例弹性伸缩 **示例场景:** - **低流量时**:0-1 个实例,成本最低 - **正常流量**:2-5 个实例,保障稳定性 - **高峰流量**:自动扩容到 10+ 个实例 ### 🏗️ 架构示意图 ![资源模型](https://main.qcloudimg.com/raw/3227b80bcd9de2a1c546c6973d1a8e0a.png) 上图展示了三个服务(TestA、TestB、TestC)的资源模型。每个服务包含多个版本,每个版本运行 0 ~ N 个实例。 ### 🌐 访问与流量分配 **域名访问:** - **默认域名**:`{service-name}-{random}.{region}.app.tcloudbase.com` - **自定义域名**:支持绑定您的域名,自动配置 SSL 证书 **流量分配策略:** | 策略 | 说明 | 适用场景 | |-----|------|--------| | **单版本 100%** | 所有流量到一个版本 | 生产环境稳定运行 | | **按比例分配** | 多版本按百分比分配流量 | 灰度发布、A/B 测试 | | **URL 参数路由** | 根据 URL 参数路由到指定版本 | 内部测试、特定用户 | **自动伸缩机制:** ``` 请求量增加 → 触发扩容条件 → 启动新实例 → 加入负载均衡 请求量减少 → 触发缩容条件 → 停止空闲实例 → 释放资源 ``` - **扩容速度**:秒级启动新实例 - **缩容策略**:实例空闲 30 分钟后自动回收 - **最小实例**:可配置最小实例数,避免冷启动 ## 服务运行模式 云托管提供 [5 种运行模式](./deploy/configuring/autoscaling/about-instance-autoscaling),满足不同业务场景的需求。 ### 🔄 始终自动扩缩容(推荐) **特点**:根据 CPU、内存、并发数等指标自动调整实例数量 **适用场景**: - 流量波动较大的应用 - 成本敏感的项目 - 新上线的应用(流量不确定) **配置示例**: ```yaml 最小实例数: 0 最大实例数: 10 扩容条件: CPU > 60% 或 并发数 > 100 缩容条件: CPU < 30% 且 并发数 < 50 ``` **成本优势**:无流量时缩容到 0,成本最低。 ### 🏃 持续运行 **特点**:保持固定数量的实例持续运行 **适用场景**: - 流量稳定的生产应用 - 对响应时间要求极高(无冷启动) - 需要保持长连接或内存状态 **配置示例**: ```yaml 实例副本数: 3 ``` **性能优势**:无冷启动,响应速度最快。 ### 🌓 白天持续运行,夜间自动扩缩容 **特点**:工作时间保持固定实例,非工作时间自动伸缩 **适用场景**: - 办公类应用(工作日 9:00-18:00 流量高) - 电商应用(白天流量高,夜间流量低) - 教育类应用(上课时间流量高) **配置示例**: ```yaml 工作时间: 09:00-18:00 固定实例数: 5 非工作时间: 18:00-09:00 最小实例数: 0 最大实例数: 10 ``` **成本优势**:兼顾性能和成本,夜间自动缩容节省费用。 ### ⚙️ 自定义 **特点**:灵活配置自动扩缩容和定时扩缩容策略 **适用场景**: - 有特殊业务规律(如每周一流量高峰) - 需要精细化成本控制 - 多种策略组合使用 **配置示例**: ```yaml 策略 1 - 自动扩缩容: 最小实例数: 1 最大实例数: 20 扩容条件: CPU > 70% 策略 2 - 定时扩容: 时间: 每周一 08:00 实例数: 10 策略 3 - 定时缩容: 时间: 每周一 20:00 实例数: 2 ``` ### 🖐️ 手工启停实例 **特点**:完全手动控制实例数量 **适用场景**: - 开发测试环境 - 临时演示环境 - 需要完全可控的场景 **操作方式**:通过控制台或 API 手动调整实例数量。 ### 📊 运行模式对比 | 运行模式 | 成本 | 性能 | 灵活性 | 推荐场景 | |---------|-----|------|-------|--------| | 始终自动扩缩容 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | 流量波动大的应用 | | 持续运行 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | 生产环境稳定应用 | | 白天持续夜间自动 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | 办公/电商类应用 | | 自定义 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 复杂业务场景 | | 手工启停 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ | 开发测试环境 | ## 资源集成 云托管深度集成 CloudBase 和腾讯云生态,让您轻松构建功能完整的应用。 ### 🗄️调用MySQL数据库 云托管提供了多种方式连接和操作 MySQL 数据库,满足不同场景下的应用需求。本文档将详细介绍以下几种连接方式: - [调用MySQL数据库](./develop/resource-integration/mysql) ### 🔗 私有网络(VPC) 通过配置 [VPC 网络](./deploy/networking/internal-link),云托管实例可以访问 VPC 内的各种资源: - **Redis**:缓存、会话存储 - **MySQL/PostgreSQL**:关系型数据库 - **Elasticsearch**:全文搜索 - **Kafka**:消息队列 - **其它腾讯云资源产品**:其它腾讯云资源产品 **配置步骤**: 1. 在服务设置中开启「私有网络」 2. 选择目标 VPC 和子网 3. 配置安全组规则 4. 重新部署服务生效 ## 🚀 持续交付(CI/CD) 支持从代码仓库自动部署,实现 DevOps 最佳实践。 **支持的代码仓库**: - GitHub - GitLab - Gitee(码云) **工作流程**: ``` 代码推送 → 自动触发构建 → 运行测试 → 构建镜像 → 自动部署 ``` **配置方式**: 1. 在服务设置中绑定代码仓库 2. 选择分支和构建配置 3. 推送代码自动触发部署 支持基于Cli来部署,来实现命令行部署 **支持的能力如下**: - [初始化项目](../cli-v1/cloudrun/init) - [本地运行](../cli-v1/cloudrun/run) - [代码部署](../cli-v1/cloudrun/deploy) - [服务列表](../cli-v1/cloudrun/list) - [下载代码](../cli-v1/cloudrun/download) - [删除服务](../cli-v1/cloudrun/download) ## 📊 可观测性 **日志服务** - 实时查看容器日志 - 支持关键词搜索、时间筛选 - 支持上传到 CLS(日志服务) **访问路径**:[服务详情] → [日志] → 选择实例和时间范围 **监控指标** - **资源监控**:CPU、内存、网络、磁盘 - **业务监控**:QPS、响应时间、错误率 - **实例监控**:实例数量、健康状态 - **自定义监控**:支持上报自定义指标 **访问路径**:[服务详情] → [监控] → 查看各项指标 --- # 云托管/快速开始/概述 > 当前文档链接: https://docs.cloudbase.net/run/quick-start/introduce 云托管支持丰富的部署方式,你可以使用[应用模板](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=template)、[容器镜像](/run/deploy/deploy/deploying-image)、[Git 仓库](run/deploy/deploy/continuous-deployment-with-cloud-build)、[cli](/cli-v1/cloudrun/init) 等方式部署应用。无论你选择哪种方式,云托管都能为你提供一个完整的应用运行环境。 如果你需要了解从零手动根据源码部署一个云托管服务可以请根据需要,选择适合您的快速开始文档: - [Python](./dockerize-python) - [Node.js](./dockerize-node) - [Go](./dockerize-go) - [Java](./dockerize-java) - [PHP](./dockerize-php) - [.NET](./dockerize-dotnet) 服务部署完成后,你可以在各端直接通过 HTTP 域名访问服务,也可以通过 SDK 访问接口数据。 比如可以通过微信小程序内置 SDK 访问云托管服务,这样可以避免公网访问,直接通过内网访问云托管服务。 ```js // 确认已经在 onLaunch 中调用过 wx.cloud.init 初始化环境(任意环境均可,可以填空) const res = await wx.cloud.callContainer({ config: { env: "填入云环境ID", // 微信云托管的环境ID }, path: "/xxx", // 填入业务自定义路径和参数,根目录,就是 / method: "POST", // 按照自己的业务开发,选择对应的方法 header: { "X-WX-SERVICE": "xxx", // xxx中填入服务名称(微信云托管 - 服务管理 - 服务列表 - 服务名称) // 其他header参数 }, // dataType:'text', // 默认不填是以JSON形式解析返回结果,若不想让SDK自己解析,可以填text // 其余参数同 wx.request }); console.log(res); ``` 以下是一些常见的访问方式: - [HTTP](../develop/access/client) - [微信小程序](../develop/access/mini) - [SDK/API](../develop/access/sdk-api) - [内网访问](../develop/access/internal) - [WebSocket](../develop/access/websocket) --- # 云托管/快速开始/Python 快速开始 > 当前文档链接: https://docs.cloudbase.net/run/quick-start/dockerize-python 本文档介绍从零开始手动将一个 Python 应用容器化,并部署到 CloudBase 云托管的过程。该项目使用 flask 作为应用运行框架,并使用 gunicorn 作为生产环境的 WSGI 服务器。 代码示例: [https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudbaserun/python](https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudbaserun/python) 或者一键部署到云托管: [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/cloudbase-examples&repoBranch=master&serverName=example-python&port=80&buildDir=cloudbaserun/python) ## 项目结构 项目根目录下必须包含以下文件: ``` ├── .dockerignore ├── Dockerfile ├── README.md ├── main.py └── requirements.txt ``` - `.dockerignore`: 用于指定在构建 Docker 镜像时需要排除的文件和目录,通过使用 `.dockerignore` 文件,可以减少镜像的大小和构建时间。 - `Dockerfile`: 是一个文本文件,包含了一系列指令,用于定义如何构建一个 Docker 镜像。通过编写 Dockerfile,可以自动化地创建包含应用程序及其运行环境的镜像,实现环境一致性和快速部署。 - `main.py`: 应用服务的启动文件,业务逻辑实现。 - `requirements.txt`: 列出了应用所需的 Python 包和版本信息,用于在构建 Docker 镜像时安装依赖。 ## 第 1 步:编写基础应用 创建名为 `helloworld-python` 的新目录,并进入此目录中: ```sh mkdir helloworld-python cd helloworld-python ``` 创建名为 `main.py` 的文件,并将以下代码粘贴到其中: ```python import os from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == "__main__": app.run(debug=True, host='0.0.0.0', port=80) ``` 以上代码会创建一个基本的 Web 服务器,并监听 `80` 端口。 ## 第 2 步:管理项目依赖 在项目根目录下,创建一个名为 `requirements.txt` 的文件,并添加以下内容: ``` Flask==3.1.1 gunicorn==23.0.0 ``` 如果您需要其他依赖,请在 `requirements.txt` 中添加相应的包和版本。 另外你也可以使用 Python 虚拟环境来管理项目依赖,确保项目的依赖不会与系统的其他 Python 包冲突: - 创建虚拟环境 ```sh python -m venv env source env/bin/activate # 激活虚拟环境 ``` - 安装依赖并生成 requirements.txt ```sh pip install Flask gunicorn pip freeze > requirements.txt ``` :::tip 提示 使用 `pip freeze` 生成 requirements.txt 文件必须在 python 虚拟环境下进行,否则生成的 requirements.txt 中会包含机器下的所有依赖,可能会导致部署失败。 ::: ## 第 3 步:将应用容器化 在项目根目录下,创建一个名为 `Dockerfile` 的文件,内容如下: ```dockerfile # 使用官方 Python 轻量级镜像 # https://hub.docker.com/_/python FROM python:3-alpine # 容器默认时区为UTC,如需使用上海时间请启用以下时区设置命令 # 设置时区,容器默认时区为UTC RUN apk add tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo Asia/Shanghai > /etc/timezone ENV APP_HOME /app WORKDIR $APP_HOME # 将本地代码拷贝到容器内 COPY . . # 设置环境变量 ENV PYTHONUNBUFFERED=1 ENV GUNICORN_WORKERS=1 ENV GUNICORN_THREADS=4 ENV GUNICORN_TIMEOUT=60 # 安装依赖到指定的/install文件夹 # 选用国内镜像源以提高下载速度 RUN pip config set global.index-url http://mirrors.cloud.tencent.com/pypi/simple && \ pip config set global.trusted-host mirrors.cloud.tencent.com && \ pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir -r requirements.txt EXPOSE 80 # 启动 Web 服务 # 如果您的容器实例拥有多个 CPU 核心,我们推荐您把线程数设置为与 CPU 核心数一致 CMD exec gunicorn --bind :80 --workers ${GUNICORN_WORKERS} --threads ${GUNICORN_THREADS} --timeout ${GUNICORN_TIMEOUT} main:app ``` 添加一个 `.dockerignore` 文件,以从容器映像中排除文件: ``` Dockerfile README.md *.pyc *.pyo *.pyd __pycache__ .pytest_cache env/ ``` ## 第 4 步(可选):本地构建和运行 如果您本地已经安装了 Docker,可以运行以下命令,在本地构建 Docker 镜像: ```sh docker build -t helloworld-python . ``` 构建成功后,运行 `docker images`,可以看到构建出的镜像: ``` REPOSITORY TAG IMAGE ID CREATED SIZE helloworld-python latest df1440c18b5c 8 seconds ago 75.9 MB ``` 随后您可以将此镜像上传至您的镜像仓库。 执行以下命令来运行容器: ```sh docker run -p 80:80 helloworld-python ``` 访问 `http://localhost`,您应该能看到 "Hello World!" 的输出。 ## 第 5 步:部署到 CloudBase 云托管 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。更多部署方式请参考 [部署服务](/run/quick-start/introduce)。 ## 配置规范 - 配置一般放到项目目录中,或者使用环境变量配置 - 服务部署时,在云托管上指定服务的启动端口即可 - 建议使用环境变量来管理不同环境的配置 ## 最佳实践 1. 使用虚拟环境管理依赖 2. 使用国内镜像源加速依赖安装 3. 合理设置容器时区 4. 使用 .dockerignore 排除不必要的文件 5. 使用 gunicorn 作为生产环境的 WSGI 服务器 Python 框架项目示例可以参考: - [创建一个 Flask 应用](../develop/languages-frameworks/flask) - [创建一个 Django 应用](../develop/languages-frameworks/django) - [创建一个 FastAPI 应用](../develop/languages-frameworks/fastapi) --- # 云托管/快速开始/Node.js 快速开始 > 当前文档链接: https://docs.cloudbase.net/run/quick-start/dockerize-node 本文档介绍从零开始手动将一个 Node.js 应用容器化,并部署到 CloudBase 云托管服务。 代码示例: [https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudbaserun/node](https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudbaserun/node) 或者一键部署到云托管: [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/cloudbase-examples&repoBranch=master&serverName=example-node&port=80&buildDir=cloudbaserun/node) ## 第 1 步:编写基础应用 创建名为 `helloworld` 的新目录,并转到此目录中: ```sh mkdir helloworld cd helloworld ``` 创建一个包含以下内容的 `package.json` 文件: ```json { "name": "helloworld", "description": "Simple hello world sample in Node", "version": "1.0.0", "type": "module", "main": "index.js", "scripts": { "start": "node index.js" }, "author": "Tencent CloudBase", "license": "Apache-2.0" } ``` 在同一目录中,创建一个 `index.js` 文件,并将以下代码行复制到其中: ```js import { createServer } from "node:http"; import { Readable } from "node:stream"; const server = createServer(async (req, res) => { if (req.url === "/") { res.writeHead(200); res.end("Hello World!"); } else if (req.url === "/myip") { // 设置 CORS 头,允许跨域请求 res.setHeader("Access-Control-Allow-Origin", "*"); try { // 使用 fetch 获取远程数据(这里使用 ipinfo.io 作为示例) const response = await fetch("https://ipinfo.io", { headers: { Accept: "application/json", }, }); Readable.fromWeb(response.body).pipe(res); } catch (error) { console.error(error); res.writeHead(500); res.end(JSON.stringify({ error: "Failed to fetch remote data" })); } } else { res.writeHead(404); res.end(JSON.stringify({ error: "Not Found" })); } }); const port = 80; server.listen(port, () => { console.log(`Server running at http://localhost:${port}/`); console.log( `Try accessing http://localhost:${port}/myip to see your IP info` ); }); ``` 此代码会创建一个基本的 Web 服务器,侦听 `80` 端口。 ## 第 2 步:将应用容器化 在项目根目录下,创建一个名为 `Dockerfile` 的文件,内容如下: ```dockerfile # 使用官方 Node.js 轻量级镜像. # https://hub.docker.com/_/node FROM node:22-alpine # 设置时区 RUN apk add tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo Asia/Shanghai > /etc/timezone && \ apk del tzdata # 定义工作目录 WORKDIR /app # 将依赖定义文件拷贝到工作目录下 COPY package*.json ./ # 使用国内镜像源安装依赖 # RUN npm config set registry https://mirrors.cloud.tencent.com/npm/ && \ # npm install --only=production && \ # npm cache clean --force # 将本地代码复制到工作目录内 COPY . . # 暴露端口 EXPOSE 80 # 启动服务 CMD [ "node", "index.js" ] ``` 添加一个 `.dockerignore` 文件,以从容器映像中排除文件: ``` Dockerfile .dockerignore node_modules npm-debug.log ``` ## 第 3 步(可选):本地构建和运行 如果您本地已经安装了 Docker,可以运行以下命令,在本地构建 Docker 镜像: ```sh docker build -t helloworld-nodejs . ``` 构建成功后,运行 `docker images`,可以看到构建出的镜像: ``` REPOSITORY TAG IMAGE ID CREATED SIZE helloworld-nodejs latest 1c8dfb88c823 8 seconds ago 163MB ``` 随后您可以将此镜像上传至您的镜像仓库。 ```sh docker run -p 80:80 helloworld-nodejs ``` 访问 `http://localhost`,您应该能看到 "Hello World!" 的输出,访问 `http://localhost/myip`,您应该能看到您的 IP 信息。 ## 第 4 步:部署到 CloudBase 云托管 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。更多部署方式请参考 [部署服务](/run/quick-start/introduce)。 ## 配置规范 - 配置一般放到项目目录中,或者使用环境变量配置 - 服务部署时,在云托管上指定服务的启动端口即可 - 建议使用环境变量来管理不同环境的配置 ## 最佳实践 1. 只安装生产环境依赖以减小镜像体积 2. 使用国内镜像源加速依赖安装 3. 合理设置容器时区 4. 使用 .dockerignore 排除不必要的文件 Nodejs 框架项目示例可以参考: - [创建一个 express 应用](../develop/languages-frameworks/express) - [创建一个 nest.js 应用](../develop/languages-frameworks/nest) --- # 云托管/快速开始/Go 快速开始 > 当前文档链接: https://docs.cloudbase.net/run/quick-start/dockerize-go 本文档介绍从零开始手动将一个 Go 应用容器化,并部署到腾讯云托管(CloudBase Run)。 代码示例: [https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudbaserun/go](https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudbaserun/go) 或者一键部署到云托管: [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/cloudbase-examples&repoBranch=master&serverName=example-go&port=80&buildDir=cloudbaserun/go) ## 第 1 步:编写基础应用 创建名为 `helloworld` 的新目录,并转到此目录中: ```sh mkdir helloworld cd helloworld ``` 创建一个包含以下内容的 `go.mod` 文件: ```go module helloworld go 1.24 ``` 在同一目录中,创建一个 `main.go` 文件,并将以下代码行复制到其中: ```go package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", handler) port := "80" if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatal(err) } } func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!\n") } ``` 此代码会创建一个基本的 Web 服务器,侦听 `80` 端口。 ## 第 2 步:将应用容器化 在项目根目录下,创建一个名为 `Dockerfile` 的文件,内容如下: ```dockerfile # 使用官方 Golang 镜像作为构建环境 FROM golang:1.24-alpine as builder # 设置工作目录 WORKDIR /app # 复制源代码 COPY . . RUN go mod download # 构建二进制文件 RUN CGO_ENABLED=0 GOOS=linux go build -mod=readonly -ldflags="-s -w" -v -o server # 选用运行时所用基础镜像(GO语言选择原则:尽量体积小、包含基础linux内容的基础镜像) # https://hub.docker.com/_/alpine FROM alpine:latest # 安装基本工具和时区数据 RUN apk add --no-cache tzdata ca-certificates && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone && \ apk del tzdata # 设置工作目录 WORKDIR /app # 将构建好的二进制文件拷贝进镜像 COPY --from=builder /app/server /app/server EXPOSE 80 # 启动 Web 服务 CMD ["/app/server"] ``` 添加一个 `.dockerignore` 文件,以从容器映像中排除文件: ``` vendor/ Dockerfile README.md .dockerignore .gcloudignore .gitignore ``` ## 第 3 步(可选):本地构建和运行 如果您本地已经安装了 Docker,可以运行以下命令,在本地构建 Docker 镜像: ```sh docker build -t helloworld-go . ``` 构建成功后,运行 `docker images`,可以看到构建出的镜像: ``` REPOSITORY TAG IMAGE ID CREATED SIZE helloworld-go latest 6948f1ebee94 8 seconds ago 15 MB ``` 随后您可以将此镜像上传至您的镜像仓库。 执行以下命令来运行容器: ```sh docker run -p 80:80 helloworld-python ``` 访问 `http://localhost`,您应该能看到 "Hello World!" 的输出。 ## 第 4 步:部署到 CloudBase 云托管 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。更多部署方式请参考 [部署服务](/run/quick-start/introduce)。 Go 框架项目的示例可参考: - [创建一个 Gin 应用](../develop/languages-frameworks/gin) --- # 云托管/快速开始/Java 快速开始 > 当前文档链接: https://docs.cloudbase.net/run/quick-start/dockerize-java 本文档介绍从零开始,如何将一个 Java 应用容器化,并部署到 CloudBase 云托管。 代码示例: [https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudbaserun/java](https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudbaserun/java) 或者一键部署到云托管: [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/cloudbase-examples&repoBranch=master&serverName=example-java&port=80&buildDir=cloudbaserun/java) ## 第 1 步:编写基础应用(创建 Spring Boot 项目) 请确保已安装 curl 和 unzip。在终端执行以下命令,快速生成一个 Spring Boot Web 项目: ```sh curl https://start.spring.io/starter.zip \ -d type=maven-project \ -d dependencies=web \ -d javaVersion=1.8 \ -d bootVersion=2.3.3.RELEASE \ -d name=helloworld \ -d artifactId=helloworld \ -d baseDir=helloworld \ -o helloworld.zip unzip helloworld.zip cd helloworld ``` 上述命令将创建一个 Spring Boot 项目。 :::tip 提示 如需在 Windows 系统上使用上述 curl 命令,您需要以下命令行之一: - [WSL(推荐)](https://docs.microsoft.com/en-us/windows/wsl/install-win10) - [cygwin](https://cygwin.com/install.html) 或者选择性地使用 [Spring Initializr(预加载配置)](https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.3.3.RELEASE&packaging=jar&jvmVersion=1.8&groupId=com.example&artifactId=helloworld&name=helloworld&description=&packageName=com.example.helloworld&dependencies=web) 生成项目: ::: 将 `src/main/java/com/example/helloworld/HelloworldApplication.java` 内容更新如下: ```java package com.example.helloworld; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication public class HelloworldApplication { @RestController class HelloworldController { @GetMapping("/") String hello() { return "Hello World!"; } } public static void main(String[] args) { SpringApplication.run(HelloworldApplication.class, args); } } ``` 在 `src/main/resources/application.properties` 中,将服务器端口设置成 `80`: ``` server.port=80 ``` 以上代码会创建一个基本的 Web 服务器,并监听 `80` 端口。 ## 第 2 步:将应用容器化 在项目根目录下,创建一个名为 `Dockerfile` 的文件,内容如下: ```dockerfile # 使用官方 maven/Java 8 镜像作为构建环境 # https://hub.docker.com/_/maven FROM maven:3.6-jdk-11 as builder # 将代码复制到容器内 WORKDIR /app COPY pom.xml . COPY src ./src # 构建项目 RUN mvn package -DskipTests # 使用 AdoptOpenJDK 作为基础镜像 # https://hub.docker.com/r/adoptopenjdk/openjdk8 # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds FROM adoptopenjdk/openjdk11:alpine-slim # 将 jar 放入容器内 COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar # 启动服务 CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/helloworld.jar"] ``` 添加一个 `.dockerignore` 文件,以从容器映像中排除文件: ``` Dockerfile .dockerignore target/ README.md HELP.md ``` ## 第 3 步(可选):本地构建和运行 如果您本地已经安装了 Docker,可以运行以下命令,在本地构建 Docker 镜像: ```sh docker build -t helloworld-java . ``` 构建成功后,运行 `docker images`,可以看到构建出的镜像: ``` REPOSITORY TAG IMAGE ID CREATED SIZE helloworld-java latest c994813b495b 8 seconds ago 271MB ``` 随后您可以将此镜像上传至您的镜像仓库。 执行以下命令来运行容器: ```sh docker run -p 80:80 helloworld-python ``` 访问 `http://localhost`,您应该能看到 "Hello World!" 的输出。 ## 第 4 步:部署到 CloudBase 云托管 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ```sh tcb cloudrun deploy ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。更多部署方式请参考 [部署服务](/run/quick-start/introduce)。 Java 框架项目示例可以参考: - [创建一个 Spring Boot Web 应用](../develop/languages-frameworks/springboot) --- # 云托管/快速开始/.NET 快速开始 > 当前文档链接: https://docs.cloudbase.net/run/quick-start/dockerize-dotnet 本文档介绍从零开始手动将一个 .NET 应用容器化,并部署到腾讯云托管(CloudBase Run)。 ## 第 1 步:编写基础应用 安装 [.NET Core SDK 3.1](https://www.microsoft.com/net/core)。在 Console 中,使用 dotnet 命令新建一个空 Web 项目: ```sh dotnet new web -o helloworld-csharp cd helloworld-csharp ``` 更新 `Program.cs` 中的 `CreateHostBuilder` 定义,侦听 `80` 端口: ```cs using System; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; namespace helloworld_csharp { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) { string port = "80"; string url = String.Concat("http://0.0.0.0:", port); return Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup().UseUrls(url); }); } } } ``` 将 `Startup.cs` 的内容更新为如下: ```cs using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace helloworld_csharp { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!\n"); }); }); } } } ``` 以上代码会创建一个基本的 Web 服务器,并监听 `80` 端口。 ## 第 2 步:将应用容器化 在项目根目录下,创建一个名为 `Dockerfile` 的文件,内容如下: ```dockerfile # 使用微软官方 .NET 镜像作为构建环境 # https://hub.docker.com/_/microsoft-dotnet-core-sdk/ FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build WORKDIR /app # 安装依赖 COPY *.csproj ./ RUN dotnet restore # 将本地代码拷贝到容器内 COPY . ./ WORKDIR /app # 构建项目 RUN dotnet publish -c Release -o out # 使用微软官方 .NET 镜像作为运行时镜像 # https://hub.docker.com/_/microsoft-dotnet-core-aspnet/ FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine AS runtime WORKDIR /app COPY --from=build /app/out ./ # 启动服务 ENTRYPOINT ["dotnet", "helloworld-csharp.dll"] ``` 添加一个 `.dockerignore` 文件,以从容器映像中排除文件: ``` **/obj/ **/bin/ ``` ## 第 3 步(可选):本地构建镜像 如果您本地已经安装了 Docker,可以运行以下命令,在本地构建 Docker 镜像: ```sh docker build -t helloworld-csharp . ``` 构建成功后,运行 `docker images`,可以看到构建出的镜像: ``` REPOSITORY TAG IMAGE ID CREATED SIZE helloworld-csharp latest 1c8dfb88c823 8 seconds ago 105MB ``` 随后您可以将此镜像上传至您的镜像仓库。 执行以下命令来运行容器: ```sh docker run -p 80:80 helloworld-csharp ``` 访问 `http://localhost`,您应该能看到 "Hello World!" 的输出。 ## 第 4 步:部署到 CloudBase 云托管 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。更多部署方式请参考 [部署服务](/run/quick-start/introduce)。 --- # 云托管/快速开始/PHP 快速开始 > 当前文档链接: https://docs.cloudbase.net/run/quick-start/dockerize-php 代码示例: [https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudbaserun/php](https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudbaserun/php) 或者一键部署到云托管: [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/cloudbase-examples&repoBranch=master&serverName=example-php&port=80&buildDir=cloudbaserun/php) ## 第 1 步:编写基础应用 创建名为 `helloworld-php` 的新目录,并转到此目录中: ```sh mkdir helloworld-php cd helloworld-php ``` 创建名为 `index.php` 的文件,并将以下代码粘贴到其中: ```php 当前文档链接: https://docs.cloudbase.net/run/develop/developing-guide 本文介绍开发云托管服务时需要了解的一些事项。 ## 编程语言支持 您可以使用任何语言、任何框架编写应用,甚至直接使用公共的容器镜像。 快速入门文档中提供了以多种主流语言编写的示例,但暂不在示例范围内的语言也均可支持。 ## 代码要求 ### 监听 HTTP 请求 服务必须监听 HTTP 请求,您可以自行配置发送请求的端口,并在部署服务时正确填写监听端口。在云托管的容器实例内部,`PORT` 环境变量的值始终反映请求发送到的端口。您的代码应检查是否存在此 `PORT` 环境变量,如果存在,则应侦听该变量以最大限度地提高可移植性。 ### 无状态服务 服务必须是无状态服务,不能依赖永久性本地状态。这是为了能够进行水平的自动扩缩容。 无状态服务(stateless service)指的是对单次请求的处理,不依赖其他请求。处理一次请求所需的全部信息必须都包含在这个请求里,或者可以从数据库、文件存储等外部获取,实例本身不存储任何信息。 有状态服务(stateful service)则相反,它会在自身保存一些数据,先后的请求是有关联的。 ### 避免后台活动 服务处理完请求后,对应的容器实例无法再访问 CPU。因此,代码中请不要在请求处理程序范围之外启动后台线程或进程,并确保在传送响应之前完成所有异步操作。强行运行后台线程可能会导致异常。 如果您怀疑服务中可能存在并不明显的后台活动,可以在日志查找在 HTTP 请求条目后记录的任何内容。 ## 代码容器化 基于容器部署服务,您需要提供一个容器镜像,或者提供代码由云托管在线构建镜像。容器镜像是一种封装格式,其中包含您的代码、代码软件包、所需的全部二进制依赖项、要使用的操作系统以及运行服务所需的其他任何内容。因此,镜像一旦构建完成,以上所有信息就都被固化,云托管不会感知镜像具体内容,容器实例运行过程中镜像内容也不会变化。 如您需要修改任何内容,则需要重新构建镜像。 通常,您可以使用名为 Dockerfile 的文件来声明如何构建镜像。快速入门文档中提供的示例,也包含多种主流语言的 Dockerfile 参考。 :::tip 提示 Dockerfile 背景知识:查看 [Dockerfile 官方文档](https://docs.docker.com/engine/reference/builder)了解语法,并查看[编写 Dockerfile 的最佳做法](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)中的提示了解如何将这些语法整合在一起。 ::: Dockerfile 通常从基础镜像(例如 `FROM golang:1.11`)开始。您可以在 [Docker Hub](https://hub.docker.com/) 上找到由操作系统和语言作者维护的基础镜像。 ### 谨慎使用依赖项 如果您使用具有依赖项库的动态语言,例如导入 Node.js 模块,那么在冷启动期间加载这些模块会增加延迟时间。 您可以通过以下方式缩短启动延迟时间: - 最大限度地减少依赖项的数量和大小,以构建精简服务。 - 惰性加载不常用的代码(如果您所用的语言支持此方式)。 - 使用代码加载优化技术。 ### 控制镜像大小 镜像较大会造成以下影响: - 增加安全漏洞; - 降低镜像的构建速度,增加构建时长,甚至引发构建超时; - 降低服务的部署速度。 :::tip 提示 镜像的大小不会影响请求处理时间,但过大的镜像会消耗更多构建时长,拉长的部署时间也会消耗更多 CPU 和内存资源,从而产生更多费用。 ::: 如何优化镜像请参阅:[优化容器镜像](/run/develop/image-optimization) --- # 云托管/开发指南/语言框架/总览 > 当前文档链接: https://docs.cloudbase.net/run/develop/languages-frameworks/index ### JavaScript/TypeScript - [Express](./express) - [Nest.js](./nest) - [Next.js](./next) ### Python - [Django](./django) - [Flask](./flask) - [FastAPI](./fastapi) ### Go - [Gin](./gin) ### Java - [Spring Boot](./springboot) ### PHP - [Laravel](./laravel) --- # 云托管/开发指南/语言框架/Express > 当前文档链接: https://docs.cloudbase.net/run/develop/languages-frameworks/express [Express](https://expressjs.com/) 是一个轻量级、灵活的 Node.js Web 框架,以其简单易用和高度可扩展著称。它提供了简洁的 API 设计,支持中间件机制,能快速构建 RESTful API 或全栈应用。Express 拥有丰富的插件生态(如 `body-parser`、`cors`),可轻松集成数据库、身份验证等功能,同时保持高性能和低学习成本,是 Node.js 开发者的首选框架之一。 本指南介绍如何通过多种方式在[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run)上部署[示例 Express 应用程序](https://github.com/TencentCloudBase/tcbr-templates/tree/main/cloudrun-express): ## 创建一个 Express App Note: 如果你已经有了一个 Express 应用,可以跳过这个步骤。 创建一个新的 Express 应用,首先需要保证在你记得机器上安装 Node.js 8.2.0 及以上版本。 创建一个目录 `cloudrun-express`, 然后 `cd` 进入该目录。 执行如下命令在 `cloudrun-express` 目录: ```sh npx express-generator --view=pug ``` `cloudrun-express` 将在目录中使用 pug 作为视图引擎创建一个新的 Express 应用程序。 ### 在本地运行 Express App 运行 `npm install` 安装所有依赖项。 接下来,通过运行以下命令启动应用程序: ```sh npm start ``` 启动浏览器并导航至 `http://localhost:3000` 以查看该应用程序。 ### 添加新路由 我们来增加一个`/apis/users`的路由其返回结果如下: ```json { "total": 2, "items": [ { "id": 0, "name": "zhangsan" }, { "id": 1, "name": "lisi" } ] } ``` 首先在`cloudrun-express/routes`目录下新增一个`users.js`文件,内容如下: ```js var express = require("express"); var router = express.Router(); /* GET users listing. */ router.get("", function (req, res, next) { const data = { total: 1, items: [ { id: 0, name: "zhangsan", }, { id: 1, name: "lisi", }, ], }; res.send(JSON.stringify(data)); }); module.exports = router; ``` 然后在`cloudrun-express`目录下 app.js 中新增: ```js var usersRouter = require("./routes/users"); app.use("/api/users", usersRouter); ``` 最后执行`npm start`启动服务,访问`http://localhost:3000/apis/users`即可返回相应结果。 ## 配置 Dockerfile 在`cloudrun-express`目录下创建一个名称为`Dockerfile`的新文件,内容如下: ```dockerfile FROM alpine:3.13 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tencent.com/g' /etc/apk/repositories \ && apk add --update --no-cache nodejs npm # 容器默认时区为UTC,如需使用上海时间请启用以下时区设置命令 RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone # 使用 HTTPS 协议访问容器云调用证书安装 RUN apk add ca-certificates # # 指定工作目录 WORKDIR /app # 拷贝包管理文件 COPY package*.json /app/ # npm 源,选用国内镜像源以提高下载速度 RUN npm config set registry https://mirrors.cloud.tencent.com/npm/ # RUN npm config set registry https://registry.npm.taobao.org/ # npm 安装依赖 RUN npm install # 将当前目录(dockerfile所在目录)下所有文件都拷贝到工作目录下(.dockerignore中文件除外) COPY . /app # 执行启动命令 # 写多行独立的CMD命令是错误写法!只有最后一行CMD命令会被执行,之前的都会被忽略,导致业务报错。 # 请参考[Docker官方文档之CMD命令](https://docs.docker.com/engine/reference/builder/#cmd) CMD ["npm", "start"] ``` 通过上面更改,你的 express 程序将可以部署到腾讯云托管了! ## 部署到云托管 云托管提供了多种部署方式来部署你的应用: ### [控制台部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=package) 打开[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run), 点击`通过本地代码部署` -> 填写服务名称 -> 部署方式选择`上传代码包` -> 代码包类型选择`文件夹` -> 选择 cloudrun-express 目录进行上传 -> 端口填写 3000 -> 点击创建并等待创建完成即可。 ### 通过 cli 部署 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy --port 3000 ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。 除了手动部署外,你也可以一键安装上述应用: ### 一键从模版部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=template&templateId=6a580dc9681b1afa028e99e2458fce1d) ### 一键从 github 部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/tcbr-templates&repoBranch=main&serverName=example-express&port=3000&buildDir=cloudrun-express) --- # 云托管/开发指南/语言框架/Nest > 当前文档链接: https://docs.cloudbase.net/run/develop/languages-frameworks/nest [Nest](https://nestjs.com/) 是一个基于 TypeScript/Node.js 的企业级后端框架,采用模块化设计,融合了 OOP(面向对象)、FP(函数式)和微服务架构。它底层支持 Express/Fastify,提供依赖注入、装饰器路由、GraphQL 集成等特性,适合构建高效、可维护的服务器端应用,尤其契合全栈 TypeScript 开发。 本指南介绍如何通过多种方式在腾讯[云托管](https://tcb.cloud.tencent.com/dev#/platform-run)上部署[示例 Nest 应用程序](https://github.com/TencentCloudBase/tcbr-templates/tree/main/cloudrun-nestjs): ## 创建 Nest 应用 Note: 如果你在本地已经有 Nest 应用,客户忽略该步骤。 要创建 Nest 应用程序,你确保机器上安装 Node 和 NestJS。 在终端中运行以下命令来创建新的 Nest 应用: ```sh nest new cloudrun-nest ``` 目录中将为你创建一个新的 Nest 应用`Nest`。 ### 在本地运行 Nest 应用 通过运行以下命令在本地启动应用程序: ```sh npm run start ``` 启动浏览器访问`http://localhost:3000`查看返回结果。 ## 配置 Dockerfile 在 Nest 应用程序的跟目录中创建一个 `Dockerfile` 文件, 内容如下: ```dockerfile FROM alpine:latest # 安装依赖包,如需其他依赖包,请到alpine依赖包管理(https://pkgs.alpinelinux.org/packages?name=php8*imagick*&branch=v3.13)查找。 # 选用国内镜像源以提高下载速度 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tencent.com/g' /etc/apk/repositories \ && apk add --update --no-cache nodejs npm # 容器默认时区为UTC,如需使用上海时间请启用以下时区设置命令 RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone # 使用 HTTPS 协议访问容器云调用证书安装 RUN apk add ca-certificates # # 指定工作目录 WORKDIR /app # 拷贝包管理文件 COPY package*.json /app/ # npm 源,选用国内镜像源以提高下载速度 RUN npm config set registry https://mirrors.cloud.tencent.com/npm/ # RUN npm config set registry https://registry.npm.taobao.org/ # npm 安装依赖 RUN npm install # 将当前目录(dockerfile所在目录)下所有文件都拷贝到工作目录下(.dockerignore中文件除外) COPY . /app # 执行启动命令 # 写多行独立的CMD命令是错误写法!只有最后一行CMD命令会被执行,之前的都会被忽略,导致业务报错。 # 请参考[Docker官方文档之CMD命令](https://docs.docker.com/engine/reference/builder/#cmd) CMD ["npm", "start"] ``` 通过上面更改,你的 nestjs 程序将可以部署到腾讯云托管了! ## 部署到云托管 云托管提供了多种部署方式来部署你的应用: ### [控制台部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=package) 打开[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run), 点击`通过本地代码部署` -> 填写服务名称 -> 部署方式选择`上传代码包` -> 代码包类型选择`文件夹` -> 选择 cloudrun-nest 目录进行上传 -> 端口填写 3000 -> 点击创建并等待创建完成即可。 ### 通过 cli 部署 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy --port 3000 ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。 除了手动部署外,你也可以一键安装上述应用: ### 一键从模版部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=template&templateId=dcffa824681b1b3c0293ad2261cd674a) ### 一键从 github 部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/tcbr-templates&repoBranch=main&serverName=example-nestjs&port=3000&buildDir=cloudrun-nestjs) --- # 云托管/开发指南/语言框架/Django > 当前文档链接: https://docs.cloudbase.net/run/develop/languages-frameworks/django [Django](https://www.djangoproject.com/) 是一个功能强大的 Python Web 框架,遵循 "Batteries-included" 理念,提供开箱即用的全栈解决方案。它以高效开发和安全稳定著称,内置 ORM、Admin 后台、用户认证等模块,大幅减少重复代码。Django 采用清晰的 MVC(MTV)架构,支持高扩展性,适合从快速原型到企业级应用开发,其自动化的管理界面和详尽的文档进一步提升了开发效率。 本指南介绍如何通过多种方式在腾讯[云托管](https://tcb.cloud.tencent.com/dev#/platform-run)上部署[示例 Django 应用程序](https://github.com/TencentCloudBase/tcbr-templates/tree/main/cloudrun-django): ## 创建一个 Django 应用 Note: 如果你已经存在一个 Django 应用,你可以跳过该步骤。 要创建新的 Django 应用程序,请确保你的机器上安装了[Python](https://www.python.org/downloads/)和 Django。 按照以下步骤在目录中设置项目。 创建虚拟环境 ```sh python -m venv env ``` 激活虚拟环境 ```sh source env/bin/activate ``` 安装 Django ```sh python -m pip install django ``` 一切设置完成后,在终端运行以下命令来配置新的 Django 项目: ```sh django-admin startproject cloudrun-django ``` 此命令将创建一个名为`cloudrun-django`的新项目。 接下来,`cd` 进入目录并运行`python manage.py runserver`以启动项目。 打开浏览器并查看`http://127.0.0.1:8000`, 您将看到 Django 欢迎页面。 ### 配置依赖项 创建 `requirements.txt` 文件: 要跟踪部署的所有依赖项,请创建一个`requirements.txt`文件: ```sh pip freeze > requirements.txt ``` Note: 只有在虚拟环境中运行上述命令才是安全的,否则它将生成系统上所有安装的 python 包。可能导致云托管上无法启动该应用程序。 ## 配置 Dockerfile 在 Django 应用程序的跟目录中创建一个 `Dockerfile` 文件, 内容如下: ```dockerfile FROM alpine:3.21.3 # 容器默认时区为UTC,如需使用上海时间请启用以下时区设置命令 RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone # 选用国内镜像源以提高下载速度 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tencent.com/g' /etc/apk/repositories \ && apk add --update --no-cache python3 py3-pip gcc python3-dev linux-headers musl-dev \ && rm -rf /var/cache/apk/* # 使用 HTTPS 协议访问容器云调用证书安装 RUN apk add ca-certificates # 拷贝当前项目到/app目录下(.dockerignore中文件除外) COPY . /app # 设定当前的工作目录 WORKDIR /app # 安装依赖到指定的/install文件夹 # 选用国内镜像源以提高下载速度 RUN pip config set global.index-url http://mirrors.cloud.tencent.com/pypi/simple \ && pip config set global.trusted-host mirrors.cloud.tencent.com \ && pip install --upgrade pip --break-system-packages \ # pip install scipy 等数学包失败,可使用 apk add py3-scipy 进行, 参考安装 https://pkgs.alpinelinux.org/packages?name=py3-scipy&branch=v3.13 && pip install --user -r requirements.txt --break-system-packages # 执行启动命令 # 写多行独立的CMD命令是错误写法!只有最后一行CMD命令会被执行,之前的都会被忽略,导致业务报错。 # 请参考[Docker官方文档之CMD命令](https://docs.docker.com/engine/reference/builder/#cmd) CMD ["python3", "manage.py", "runserver","0.0.0.0:8080"] ``` 通过上面更改,你的 Django 程序将可以部署到腾讯云托管了! ## 部署到云托管 云托管提供了多种部署方式来部署你的应用: ### [控制台部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=package) 打开[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run), 点击`通过本地代码部署` -> 填写服务名称 -> 部署方式选择`上传代码包` -> 代码包类型选择`文件夹` -> 选择 cloudrun-django 目录进行上传 -> 端口填写 8080 -> 点击创建并等待创建完成即可。 ### 通过 cli 部署 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy --port 8080 ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。 除了手动部署外,你也可以一键安装上述应用: ### 一键从模版部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=template&templateId=a6ec3048681b19c70291abb521d307bc) ### 一键从 github 部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/tcbr-templates&repoBranch=main&serverName=example-django&port=8080&buildDir=cloudrun-django) --- # 云托管/开发指南/语言框架/Flask > 当前文档链接: https://docs.cloudbase.net/run/develop/languages-frameworks/flask [Flask](https://flask.palletsprojects.com/en/stable/) Flask 是一个轻量级、灵活的 Python Web 框架,以简洁和可扩展性为核心设计理念。它不强制依赖特定库或架构,仅提供核心功能(如路由、模板渲染),开发者可自由选配数据库、表单验证等组件(如 SQLAlchemy、WTForms)。这种“微框架”特性使其学习成本极低,同时能通过扩展轻松构建复杂应用,特别适合快速开发小型项目或作为微服务基础。 本指南介绍如何通过多种方式在腾讯[云托管](https://tcb.cloud.tencent.com/dev#/platform-run)上部署 [Flask 应用程序](https://github.com/TencentCloudBase/tcbr-templates/tree/main/cloudrun-flask): ## 创建一个 Flask 应用 Note: 如果你已经有 Flask 应用程序,则可以跳过该步骤。 要创建新的 Flask 应用程序,需要机器上安装[Python](https://www.python.org/downloads/)和 Flask。 按照以下步骤在目录中设置项目。 创建一个`cloudrun-flask`目录, `cd`进入该目录。 创建虚拟环境 ```sh python -m venv env ``` 激活虚拟环境 ```sh source env/bin/activate ``` 安装 Flask ```sh python -m pip install flask ``` 在`cloudrun-flask`目录中创建一个新文件 `manage.py`, 内容如下: ```py import os from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello world!' ``` 1、`from flask import Flask` - 此行从 Flask 框架导入 Flask 类,用于创建和管理 Web 应用程序。 2、`app = Flask(__name__)` - 此行创建 Flask 类的实例并将其分配给 app 变量。 - 该**name**参数帮助 Flask 识别应用程序的位置。它对于确定资源路径和错误报告很有用。 3、`@app.route('/')` - 该`@app.route('/')`装饰器为应用设置了 URL 路由。当(/)访问根 URL 时,Flask 将执行紧接着该装饰器下方的函数。 4、`def hello()` - 该 hello 函数返回一条纯文本消息“Hello world!”,当访问应用程序的根 URL 时,该消息将显示在浏览器中。 在`cloudrun-flask`目录下执行`python3 manage.py runserver 0.0.0.0:8080` 启动服务, 打开浏览器并查看`http://127.0.0.1:8080`查看返回结果。 ### 配置依赖项 创建 `requirements.txt` 文件: 要跟踪部署的所有依赖项,请创建一个`requirements.txt`文件: ```sh pip freeze > requirements.txt ``` Note: 只有在虚拟环境中运行上述命令才是安全的,否则它将生成系统上所有安装的 python 包。可能导致云托管上无法启动该应用程序。 通过上面更改,你的 Flask 程序将可以部署到腾讯云托管了! ## 配置 Dockerfile 在`cloudrun-flask`目录中创建一个 `Dockerfile` 文件,内容如下: ```dockerfile FROM alpine # 容器默认时区为UTC,如需使用上海时间请启用以下时区设置命令 RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone # 选用国内镜像源以提高下载速度 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tencent.com/g' /etc/apk/repositories \ && apk add --update --no-cache python3 py3-pip gcc python3-dev linux-headers musl-dev \ && rm -rf /var/cache/apk/* # 使用 HTTPS 协议访问容器云调用证书安装 RUN apk add ca-certificates # 拷贝当前项目到/app目录下(.dockerignore中文件除外) COPY . /app # 设定当前的工作目录 WORKDIR /app # 安装依赖到指定的/install文件夹 # 选用国内镜像源以提高下载速度 RUN pip config set global.index-url http://mirrors.cloud.tencent.com/pypi/simple \ && pip config set global.trusted-host mirrors.cloud.tencent.com \ && pip install --upgrade pip --break-system-packages \ # pip install scipy 等数学包失败,可使用 apk add py3-scipy 进行, 参考安装 https://pkgs.alpinelinux.org/packages?name=py3-scipy&branch=v3.13 && pip install --user -r requirements.txt --break-system-packages # 执行启动命令 # 写多行独立的CMD命令是错误写法!只有最后一行CMD命令会被执行,之前的都会被忽略,导致业务报错。 # 请参考[Docker官方文档之CMD命令](https://docs.docker.com/engine/reference/builder/#cmd) CMD ["python3", "manage.py", "runserver","0.0.0.0:8080"] ``` 通过上面更改,你的 flask 程序将可以部署到腾讯云托管了! ## 部署到云托管 云托管提供了多种部署方式来部署你的应用: ### [控制台部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=package) 打开[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run), 点击`通过本地代码部署` -> 填写服务名称 -> 部署方式选择`上传代码包` -> 代码包类型选择`文件夹` -> 选择 cloudrun-flask 目录进行上传 -> 端口填写 8080 -> 点击创建并等待创建完成即可。 ### 通过 cli 部署 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy --port 8080 ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。 除了手动部署外,你也可以一键安装上述应用: ### 一键从模版部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=template&templateId=6a580dc9681b1a2c028e8ce5429f939d) ### 一键从 github 部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/tcbr-templates&repoBranch=main&serverName=example-flask&port=8080&buildDir=cloudrun-flask) --- # 云托管/开发指南/语言框架/FastAPI > 当前文档链接: https://docs.cloudbase.net/run/develop/languages-frameworks/fastapi [FastAPI](https://fastapi.tiangolo.com/) 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 并基于标准的 Python 类型提示。具有以下特性: - 快速:可与 NodeJS 和 Go 并肩的极高性能(归功于 Starlette 和 Pydantic)。最快的 Python web 框架之一。 - 高效编码:提高功能开发速度约 200% 至 300%。 - 更少 bug:减少约 40% 的人为(开发者)导致错误。 - 智能:极佳的编辑器支持。处处皆可自动补全,减少调试时间。 - 简单:设计的易于使用和学习,阅读文档的时间更短。 本文档介绍使用多种方式在云托管(容器型)部署[示例 FastAPI 应用](ttps://github.com/TencentCloudBase/tcbr-templates/tree/main/cloudrun-fastapi): ## 创建一个 FastAPI 项目 NOTE: 如果已经存在 FastAPI 项目,可以跳过该阶段。 1. 新建一个 fastapi-app 目录 2. 在 fastapi-app 目录中,新建一个 app.py 文件,内容如下: ```python from fastapi import FastAPI app = FastAPI() @app.get("/") async def root(): return {"greeting": "Hello, World!", "message": "Welcome to FastAPI!"} ``` 这个 app.py 文件定义了一个 FastAPI 应用,它包含一个根路径("/")和一个 GET 请求处理函数(root)。当用户访问根路径时,应用会返回一个包含问候语和欢迎信息的 JSON 响应。 3. 在 fastapi-app 目录中,新建一个 requirements.txt 文件,内容如下: ``` fastapi==0.100.0 hypercorn==0.14.4 ``` 这个 requirements.txt 文件定义了 FastAPI 应用所需的依赖项。其中 [hypercorn](https://github.com/pgjones/hypercorn) 是 FastAPI 运行时需要的 ASGI 服务器。 4. 安装依赖启动服务 使用 pip 安装依赖: ```bash pip install -r requirements.txt ``` 推荐使用虚拟环境安装依赖: ```bash python -m venv venv source venv/bin/activate pip install -r requirements.txt ``` 安装完成后,使用 hypercorn 启动服务: ```bash hypercorn main:app --bind 0.0.0.0:80 ``` 访问`http://127.0.0.1:80`即可返回相应结果。 在开发阶段,还可以使用 `fastapi-cli` 启动服务: 首先安装 fastapi-cli: ```bash pip install fastapi-cli ``` 然后启动服务: ```bash fastapi dev main.py ``` ## 配置 Dockerfile 在 fastapi-app 目录中,新建一个 Dockerfile 文件,内容如下: ```dockerfile FROM python:3-alpine # 设定当前的工作目录 WORKDIR /app # 拷贝当前项目到容器中 COPY . . # 安装依赖 RUN pip install --no-cache-dir -r requirements.txt # 启动服务 CMD ["hypercorn", "main:app", "--bind", "0.0.0.0:80"] ``` 通过上面更改,你的 express 程序将可以部署到腾讯云托管了! ## 部署到云托管 云托管提供了多种部署方式来部署你的应用: ### [控制台部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=package) 打开[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run), 点击`通过本地代码部署` -> 填写服务名称 -> 部署方式选择`上传代码包` -> 代码包类型选择`文件夹` -> 选择 cloudrun-fastapi 目录进行上传 -> 端口填写 80 -> 点击创建并等待创建完成即可。 ### 通过 cli 部署 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。 除了手动部署外,你也可以一键安装上述应用: ### 一键从模版部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=template&templateId=82bba809682f142500d7a00f116bd852) ### 一键从 github 部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/tcbr-templates&repoBranch=main&serverName=example-fastapi&port=80&buildDir=cloudrun-fastapi) --- # 云托管/开发指南/语言框架/Gin > 当前文档链接: https://docs.cloudbase.net/run/develop/languages-frameworks/gin [Gin](https://gin-gonic.com/) 是一个高性能的 Go Web 框架,以其简洁、高效和易用性著称。它基于`httprouter`实现快速路由匹配,支持中间件机制,能轻松构建 RESTful API 或微服务。 本指南介绍如何通过多种方式在腾讯[云托管](https://tcb.cloud.tencent.com/dev#/platform-run)上部署 [Gin 应用程序](https://github.com/TencentCloudBase/tcbr-templates/tree/main/cloudrun-flask): ## 创建一个 Gin 应用 Note: 如果你已经有了一个 Gin 应用,且确保 Dockerfile 文件和 main.go 文件都在根目录下,可以跳过这个步骤。 创建一个新的 Gin 应用,首先需要确保机器上安装 Go 服务程序。 创建一个目录``cloudrun-gin`, 然后`cd`进入该目录。 执行如下命令在`cloudrun-gin`目录: ```sh go mod init cloudrun-gin go get -u github.com/gin-gonic/gin ``` 在`cloudrun-gin`目录下创建 main.go 文件,内容如下: ```go func main() { router := gin.Default() router.GET("/json", func(c *gin.Context) { data := map[string]interface{}{ "id": 0, "name": "zhangsan", } c.JSON(http.StatusOK, data) }) // 监听并在 0.0.0.0:8080 上启动服务 router.Run(":8080") } ``` 执行 `go run main.go` 启动服务,访问`http://localhost:8080/json`可查看访问结果 ## 配置 Dockerfile 1、在`cloudrun-gin`目录下创建一个`Dockerfile` 文件,内容如下(根据真实情况修改 FROM 行 golang 版本): ```dockerfile FROM golang:1.22.3-alpine as builder # 指定构建过程中的工作目录 WORKDIR /app # 将当前目录(dockerfile所在目录)下所有文件都拷贝到工作目录下(.dockerignore中文件除外) COPY . /app/ # 执行代码编译命令。操作系统参数为linux,编译后的二进制产物命名为main,并存放在当前目录下。 RUN GOOS=linux go build -o main . # 选用运行时所用基础镜像(GO语言选择原则:尽量体积小、包含基础linux内容的基础镜像) FROM alpine:latest # 容器默认时区为UTC,我们使用以下时区设置命令启用上海时区 # RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone # 使用 HTTPS 协议访问容器云调用证书安装 RUN apk add ca-certificates # 指定运行时的工作目录 WORKDIR /app # 将构建产物/app/main拷贝到运行时的工作目录中 COPY --from=builder /app/main /app/ # 执行启动命令 # 写多行独立的CMD命令是错误写法!只有最后一行CMD命令会被执行,之前的都会被忽略,导致业务报错。 # 请参考[Docker官方文档之CMD命令](https://docs.docker.com/engine/reference/builder/#cmd) CMD ["/app/main"] ``` 通过上面更改,你的 gin 程序将可以部署到腾讯云托管了! ## 部署到云托管 云托管提供了多种部署方式来部署你的应用: ### [控制台部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=package) 打开[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run), 点击`通过本地代码部署` -> 填写服务名称 -> 部署方式选择`上传代码包` -> 代码包类型选择`文件夹` -> 选择 cloudrun-gin 目录进行上传 -> 端口填写 8080 -> 点击创建并等待创建完成即可。 ### 通过 cli 部署 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy --port 8080 ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。 除了手动部署外,你也可以一键安装上述应用: ### 一键从模版部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=template&templateId=6a580dc9681b0da3028d8b8710becbaf) ### 一键从 github 部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/tcbr-templates&repoBranch=main&serverName=example-gin&port=8080&buildDir=cloudrun-gin) --- # 云托管/开发指南/语言框架/Spring Boot > 当前文档链接: https://docs.cloudbase.net/run/develop/languages-frameworks/springboot [Spring Boot](https://spring.io/projects/spring-boot) 是一个开箱即用的 Java 框架,基于 Spring 生态,简化企业级应用开发。它内置 Web 服务器、自动配置和起步依赖(Starter),无需复杂 XML 配置即可快速构建独立、可部署的微服务或单体应用,大幅提升开发效率,适合现代云原生和分布式系统。 本指南介绍如何通过多种方式在腾讯[云托管](https://tcb.cloud.tencent.com/dev#/platform-run)上部署 [Spring Boot 应用程序](https://github.com/TencentCloudBase/tcbr-templates/tree/main/cloudrun-nestjs): ## 创建 Spring Boot 应用 Note: 如果本次已经有 Spring Boot 应用程序则可以跳过该步骤。 要创建 Spring Boot 应用程序,请确保你的机器上安装了[JDK](https://www.oracle.com/java/technologies/downloads/)。 前往[start.spring.io](https://start.spring.io/)初始化新的 Spring Boot 应用。选择以下选项来自定并生成你的应用。 - Project: Maven - 语言: Java - Spring Boot: 3.3.4 - 项目元数据: - Group: com.tencent - Artifact: cloudrun - Name: cloudrun - Description: Demo project for Spring Boot - Package name: com.tencent.cloudrun - Packaging: Jar - Java: 17 - 依赖项: - 点击"添加依赖项"按钮并搜索 Spring Boot。选择它。 单击"CREATE"按钮,下载压缩文件并解压到你机器的文件夹中。 ## 配置 Dockerfile 在 Spring Boot 应用程序的跟目录中创建一个 `Dockerfile` 文件, 文件内容如下: Note: maven 版本和 jre 版本需要根据实际情况进行修改。`cloudrun-1.0-SNAPSHOT.jar`名称根据实际情况修改。根目录下需要`settings.xml`文件 ```dockerfile FROM maven:3.6.0-jdk-17-slim as build # 指定构建过程中的工作目录 WORKDIR /app # 将src目录下所有文件,拷贝到工作目录中src目录下(.gitignore/.dockerignore中文件除外) COPY src /app/src # 将pom.xml文件,拷贝到工作目录下 COPY settings.xml pom.xml /app/ # 执行代码编译命令 # 自定义settings.xml, 选用国内镜像源以提高下载速度 RUN mvn -s /app/settings.xml -f /app/pom.xml clean package # 选择运行时基础镜像 FROM alpine:3.13 # 安装依赖包,如需其他依赖包,请到alpine依赖包管理(https://pkgs.alpinelinux.org/packages?name=php8*imagick*&branch=v3.13)查找。 # 选用国内镜像源以提高下载速度 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tencent.com/g' /etc/apk/repositories \ && apk add --update --no-cache openjdk17-jre-base \ && rm -f /var/cache/apk/* # 容器默认时区为UTC,如需使用上海时间请启用以下时区设置命令 # RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone # 使用 HTTPS 协议访问容器云调用证书安装 RUN apk add ca-certificates # 指定运行时的工作目录 WORKDIR /app # 将构建产物jar包拷贝到运行时目录中 COPY --from=build /app/target/*.jar . # 暴露端口 # 此处端口必须与「服务设置」-「流水线」以及「手动上传代码包」部署时填写的端口一致,否则会部署失败。 EXPOSE 80 # 执行启动命令. # 写多行独立的CMD命令是错误写法!只有最后一行CMD命令会被执行,之前的都会被忽略,导致业务报错。 # 请参考[Docker官方文档之CMD命令](https://docs.docker.com/engine/reference/builder/#cmd) CMD ["java", "-jar", "/app/cloudrun-1.0-SNAPSHOT.jar"] ``` 通过上面更改,你的 springboot 程序将可以部署到腾讯云托管了! ## 部署到云托管 云托管提供了多种部署方式来部署你的应用: ### [控制台部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=package) 打开[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run), 点击`通过本地代码部署` -> 填写服务名称 -> 部署方式选择`上传代码包` -> 代码包类型选择`文件夹` -> 选择 cloudrun-springboot 目录进行上传 -> 端口填写 80 -> 点击创建并等待创建完成即可。 ### 通过 cli 部署 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。 除了手动部署外,你也可以一键安装上述应用: ### 一键从模版部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=template&templateId=e23fc3b2681b1a8b028c85d423b7d6c5) ### 一键从 github 部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/tcbr-templates&repoBranch=main&serverName=example-springboot&port=80&buildDir=cloudrun-springboot) --- # 云托管/开发指南/语言框架/Laravel > 当前文档链接: https://docs.cloudbase.net/run/develop/languages-frameworks/laravel Laravel 是一个 Web 应用框架, 有着表现力强、语法优雅的特点。Laravel 致力于提供出色的开发体验,同时提供强大的特性,例如完全的依赖注入,富有表现力的数据库抽象层,队列和计划任务,单元和集成测试等等。 本指南介绍如何通过多种方式在腾讯[云托管](https://tcb.cloud.tencent.com/dev#/platform-run)上部署 [Laravel 应用程序](https://github.com/TencentCloudBase/tcbr-templates/tree/main/cloudrun-laravel): ## 创建一个 Laravel 应用 Note: 如果你已经有一个 Laravel 应用,且确保 Dockerfile 文件在根目录下,可跳过这个步骤。 创建一个 Laravel 应用,请确保本地已安装 PHP 和 [Composer](https://getcomposer.org/)。 安装好 PHP 和 Composer 后,可以通过 Composer 的 `create-project` 命令创建一个新的 Laravel 项目: ``` composer create-project laravel/laravel cloudrun-laravel ``` ### 启动 php 服务 执行 `php-fpm` 启动 php cgi 服务, 服务访问地址为 `127.0.0.1:9000`。 ### 配置 Nginx `default.conf` ``` server { listen 80; index index.php index.html; server_name localhost; root /var/www/html/public; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location ~ /\.ht { deny all; } } ``` 将 default.conf 放到 nginc `conf.d` 目录下,执行 `nginx -s reload`, 启动 nginx 服务,即可通过 `http://127.0.0.1` 访问 php 服务。 ### 从 Dockerfile 部署 1、在 `cloudrun-laravel` 项目下创建 nginx 目录,在 nginx 目录下分别添加 `default.conf` 和 `supervisord.conf` 文件,内容如下: #### default.conf ``` server { listen 80; index index.php index.html; server_name localhost; root /var/www/html/public; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location ~ /\.ht { deny all; } } ``` #### supervisord.conf ``` [supervisord] nodaemon=true [program:php-fpm] command=/usr/local/sbin/php-fpm [program:nginx] command=/usr/sbin/nginx -g "daemon off;" ``` 2、在 `cloudrun-laravel` 项目下创建 Dockerfile, 内容如下: ```dockerfile # 1. 构建阶段,安装依赖,composer install FROM php:8.2-fpm-alpine AS build # 安装系统依赖和 PHP 扩展 RUN apk add --no-cache \ bash \ git \ unzip \ libzip-dev \ oniguruma-dev \ curl \ && docker-php-ext-install pdo_mysql mbstring zip bcmath # 安装 composer COPY --from=composer:2 /usr/bin/composer /usr/bin/composer WORKDIR /var/www/html # 复制项目代码 COPY . . RUN composer install --no-dev --optimize-autoloader --no-interaction # 生成优化的自动加载文件 RUN composer dump-autoload -o # 2. 运行阶段,PHP-FPM 镜像 FROM php:8.2-fpm-alpine # 安装 PHP 扩展 RUN apk add --no-cache libzip oniguruma-dev libpng libjpeg-turbo libwebp freetype \ && apk add --no-cache --virtual .build-deps $PHPIZE_DEPS libzip-dev oniguruma-dev \ && docker-php-ext-configure zip \ && docker-php-ext-install pdo_mysql mbstring zip bcmath gd \ && apk del .build-deps WORKDIR /var/www/html # 复制 build 阶段的代码和依赖 COPY --from=build /var/www/html /var/www/html # 设置权限(根据需要调整) RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache # 3. 安装并配置 supervisord 以同时启动 php-fpm 和 nginx RUN apk add --no-cache supervisor nginx # 复制 nginx 配置 COPY nginx/default.conf /etc/nginx/conf.d/default.conf # 复制 supervisord 配置 COPY nginx/supervisord.conf /etc/supervisord.conf # 暴露端口 EXPOSE 80 # 启动 supervisord,管理 php-fpm 和 nginx CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] ``` 通过上面更改,你的 laravel 程序将可以部署到腾讯云托管了! ## 部署到云托管 云托管提供了多种部署方式来部署你的应用: ### [控制台部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=package) 打开[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run), 点击`通过本地代码部署` -> 填写服务名称 -> 部署方式选择`上传代码包` -> 代码包类型选择`文件夹` -> 选择 cloudrun-laravel 目录进行上传 -> 端口填写 8080 -> 点击创建并等待创建完成即可。 ### 通过 cli 部署 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy ``` 输入环境和服务名称后,CLI 会自动打包应用像并部署到云托管。 除了手动部署外,你也可以一键安装上述应用: ### 一键从 github 部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/tcbr-templates&repoBranch=main&serverName=example-laravel&port=80&buildDir=cloudrun-laravel) --- # 云托管/开发指南/语言框架/Next > 当前文档链接: https://docs.cloudbase.net/run/develop/languages-frameworks/next [Next.js](https://nextjs.org/) 是一个基于 React 的现代前端开发框架,由 Vercel 团队开发和维护,提供了开箱即用的功能,如服务器端渲染(SSR)、静态站点生成(SSG)、路由、API 路由等,帮助开发者快速构建高性能的 Web 应用。 本指南介绍如何通过多种方式在[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run)上部署[示例 Next.js 应用程序](https://github.com/TencentCloudBase/tcbr-templates/tree/main/cloudrun-nextjs): ## 创建 Next.js Note: 如果已有 Next.js 应用,可以跳过此步骤。 创建 Next.js 应用,需要安装 Node.js 18.18 及以上版本。 在 Terminal 运行如下命令,使用官方 cli 创建 Next.js 应用。 > 建议在常用的文件夹中运行命令,以避免创建完成后找不到项目文件夹 ```sh npx create-next-app@latest ``` ## 本地运行 Next.js 使用 VS Code 等 IDE 打开 cli 创建的项目,运行 `npm install` 安装所有依赖项。 运行以下命令启动应用程序: ```sh npm run dev ``` 启动浏览器并导航至 `http://localhost:3000` 以查看该应用程序。 ## 配置 Dockerfile 使用云托管部署项目需要添加 docker 配置文件。 在项目根目录下创建一个名称为 `Dockerfile` 的新文件,内容如下: ```dockerfile # 使用 Node.js 镜像作为构建环境 FROM node:22-alpine AS builder # 设置工作目录 WORKDIR /app # 复制包管理文件以利用 Docker 缓存层 COPY package.json package-lock.json* ./ # 安装依赖(包括 devDependencies,因为需要构建) RUN npm install # 复制项目文件 COPY . . # 构建应用 RUN npm run build # 使用更小的基础镜像来运行应用 FROM node:22-alpine AS runner # 设置工作目录 WORKDIR /app # 从构建阶段复制构建输出和依赖,减小镜像体积 COPY --from=builder /app/.next ./.next COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./package.json COPY --from=builder /app/public ./public # 环境变量 ENV NODE_ENV=production # 使用非 root 用户端口 EXPOSE 3000 # 启动命令 CMD ["npm", "start"] ``` 通过上述更改,你的 Next.js 程序即可部署到腾讯云托管了! ## 部署到云托管 云托管提供了多种部署方式来部署你的应用: ### [控制台部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=package) 打开[腾讯云托管](https://tcb.cloud.tencent.com/dev#/platform-run), 点击 `通过本地代码部署` -> 填写服务名称 -> 部署方式选择 `上传代码包` -> 代码包类型选择 `文件夹` -> 选择项目目录进行上传 -> 端口填写 3000 -> 点击创建并等待创建完成即可。 ### 通过 cli 部署 如果您已经安装了 [CloudBase CLI](/cli-v1/install),可以在项目目录下使用以下命令将应用部署到 CloudBase 云托管: ``` tcb cloudrun deploy --port 3000 ``` 输入环境和服务名称后,CLI 会自动打包应用并部署到云托管。 除了手动部署外,你也可以一键安装上述应用: ### 一键从模版部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=template&templateId=6c670e5d6870c72e0507fcb560b1ec48) ### 一键从 GitHub 部署 [![](https://main.qcloudimg.com/raw/67f5a389f1ac6f3b4d04c7256438e44f.svg)](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/TencentCloudBase/tcbr-templates&repoBranch=main&serverName=example-nextjs&port=3000&buildDir=cloudrun-nextjs) --- # 云托管/开发指南/调用云托管服务/通过 HTTP 访问 > 当前文档链接: https://docs.cloudbase.net/run/develop/access/client 创建云托管服务之后, 云托管会为您提供该服务的 HTTPS 访问域名。您可以通过该域名,来访问您部署的服务。 所有的云托管服务都会提供一个默认域名,不过正式环境场景下,我们强烈建议您通过 [HTTP 网关](/service/introduce)配置[自定义自定义域名](../../deploy/networking/custom-domains),通过 HTTP 网关访问服务时,可以开启服务鉴权、自定义域名等能力。 ## 创建服务 在云托管上创建服务需要满足以下条件: - 通过公共互联网访问 - 供公众使用的网址 云托管默认为用户提供公共互联网访问,且默认域名不会有任何的鉴权操作,如需关闭公网域名访问,请在部署服务之后,在`服务设置` -> `网络访问` 中关闭公网开关。 ## 服务域名 ### 公网域名 云托管会为每个服务分配一个公网域名,您可以通过该域名访问您的云托管服务。您也可以在 `服务设置` -> `网络访问` 中关闭公网开关。 我们假设创建的服务默认域名为: `https://demo.ap-shanghai.run.tcloudbase.com` ### 内网域名 云托管也会为每个服务分配一个内网域名,该内网域名默认是关闭状态。您可以在 `服务设置` -> `网络访问` 中开启公网开关。 内网域名有以下场景: - 服务之间通过 HTTP 相互访问 - 复制直接通过 gRPC 相互访问 - 服务间通过其它 4 层协议相互访问等等 ### 通过 CURL 访问 ```sh curl --location --request GET 'https://demo.ap-shanghai.run.tcloudbase.com' ``` ### 通过 PHP-CURL 访问 ```php 'https://demo.ap-shanghai.run.tcloudbase.com', CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 0, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'GET', )); $response = curl_exec($curl); curl_close($curl); echo $response; ``` ### 通过 fetch 访问 ```js fetch("https://demo.ap-shanghai.run.tcloudbase.com", { method: "GET", redirect: "follow", }) .then((response) => response.text()) .then((result) => console.log(result)) .catch((error) => console.log("error", error)); ``` ### 通过浏览器 JS-XHR 访问 ```js const xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.addEventListener("readystatechange", function () { if (this.readyState === 4) { console.log(this.responseText); } }); xhr.open("GET", "https://demo.ap-shanghai.run.tcloudbase.com"); xhr.send(); ``` ### 通过 QQ 小程序访问 ```js qq.request({ url: "https://demo.ap-shanghai.run.tcloudbase.com", success(res) { console.log(res.data); }, }); ``` ### 通过 UNI-APP 访问 ```js uni.request({ url: "https://demo.ap-shanghai.run.tcloudbase.com", success: function (res) { console.log(res.data); }, }); ``` --- # 云托管/开发指南/调用云托管服务/微信小程序 > 当前文档链接: https://docs.cloudbase.net/run/develop/access/mini 本文介绍如何在小程序中通过 `wx.cloud.callContainer` 访问云托管服务,并说明默认访问范围、跨环境(环境共享)使用方法及常见问题排查。 ## 适用范围与术语 - 云开发环境(env):TCB 环境,承载数据库、存储、云托管等资源。 - 云托管服务:部署在某个云开发环境下的容器化服务。 - 默认访问范围:小程序仅能访问与自己已关联的云开发环境中的云托管服务。 - 环境共享:云开发能力。开启后,同主体下其他小程序可通过 `resourceAppid/resourceEnv` 访问该环境中的云托管服务。 ## 前置条件 - 小程序与目标云开发环境已完成关联(见[关联操作](/quick-start/create-env#%E9%99%84%E5%A6%82%E4%BD%95%E5%85%B3%E8%81%94%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F))。 - 目标环境内已部署云托管服务,并确认服务名称。 - 在小程序侧已引入并可使用 `wx.cloud` 能力。 ## 基础用法(访问本小程序已关联的环境) 在 `app.js` 的 `onLaunch` 中初始化一次: ```js App({ async onLaunch() { // 使用 callContainer 前需先初始化 wx.cloud.init({ env: "prod-01", // 与小程序已关联的云开发环境 ID, 可以留空 }); }, }); ``` 在任意页面发起调用: ```js const res = await wx.cloud.callContainer({ config: { env: "prod-01", // 与小程序已关联的云开发环境 ID }, path: "/", // 业务自定义路径,根目录为 / method: "GET", // 依业务选择 header: { "X-WX-SERVICE": "hello-world", // 云托管服务名称 // 其他 header }, // dataType: 'text', // 默认为 JSON;如需自行解析可设为 'text' }); console.log(res); ``` ## 跨环境访问(通过“云开发环境共享”) :::info 注意 `callContainer` 默认不支持跨环境。仅在目标环境对当前小程序开启了[云开发环境共享](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/guide/resource-sharing/guidance.html)后才可访问。 ::: - 适用前提:小程序同主体;在云开发控制台为资源方小程序开启目标环境的“环境共享”。 - 消费方小程序代码示例: ```js // app.js App({ onLaunch: async function () { const c1 = new wx.cloud.Cloud({ resourceAppid: "wx-01", // 资源方 AppID resourceEnv: "prod-01", // 资源方云开发环境 ID }); await c1.init(); const result = await c1.callContainer({ path: "/", method: "GET", header: { "X-WX-SERVICE": "hello-world", // 资源方环境内的服务名 }, }); console.log(result); }, }); ``` ## 与 `wx.request` 的区别与优势 1. 内网通信,不耗费公网流量。 2. 仅授权小程序/公众号可访问,天然抵御 DDoS。 3. 就近接入与链路加速,无需后端多地域部署。 4. 无需在小程序后台配置「服务器域名」。 5. 后端可直接获取用户信息(如 openid),仅适用于微信云托管(腾讯云侧云托管暂无)。 6. 如果仅小程序/公众号调用,建议在服务设置中关闭公网访问(关闭后请使用 `callContainer` 而非 `wx.request`)。 ## 参考链接 - [微信小程序-访问云托管服务](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloudrun/src/development/call/mini.html) - [callContainer 技术原理](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloudrun/src/practice/call.html) - [云开发环境共享](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/guide/resource-sharing/introduce.html) --- # 云托管/开发指南/调用云托管服务/通过 SDK/API 访问 > 当前文档链接: https://docs.cloudbase.net/run/develop/access/sdk-api 一些仅把云托管当做 API server 的应用场景,可以通过开放 SDK/API 访问云托管服务。类似微信小程序内置 callContainer 方法,通过开放 SDK/API 也不需要开启公网访问即可访问云托管服务。通过开放 SDK/API 访问云托管服务,可以根据 access_token 对服务访问进行精细话的[权限控制](/run/deploy/authenticating/end-users)。 如果是 web 应用,需要先进行[安全域名配置](/faq/allowed_domain) 来支持 CORS 跨域。 ## JS-SDK 访问 ```js import cloudbase from "@cloudbase/js-sdk"; //初始化SDK实例 const app = cloudbase.init({ env: "xxxx-yyy", }); app .callContainer({ name: "helloworld", method: "POST", path: "/abc", header: { "Content-Type": "application/json; charset=utf-8", }, data: { key1: "test value 1", key2: "test value 2", }, }) .then((res) => { console.log(res); }); ``` 更多信息请参考 [JS-SDK 文档](/api-reference/webv2/cloudrun)。 ## Node SDK 访问 如在云函数中访问云托管服务: ```js import tcb from '@cloudbase/node-sdk' exports.main = async (event, context) => { const { httpContext } = context const { url, httpMethod } = httpContext console.log(`[${httpMethod}][${url}]`) const tcbapp = tcb.init({ context }) const result = await tcbapp.callContainer({ name: 'helloworld', method: 'POST', path: '/abc', data: { key1: 'test value 1', key2: 'test value 2' }, { timeout: 5000 } }) console.log(result) } ``` 更多信息请参考 [Node SDK 文档](/api-reference/server/node-sdk/cloudrun)。 ## HTTP API 访问 通过[云托管 HTTP API](/http-api/cloudrun/云托管) 访问云托管服务,您需要先获取访问令牌(Token),然后使用该令牌进行 API 调用。 ```sh curl -L 'https://your-envId.api.tcloudbasegateway.com/v1/cloudrun/:name' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ -d '{}' ``` 针对可能存在的合规封禁风险,HTTP API 对请求和响应均有所封装: - 所有的请求都需要携带 `Authorization: Bearer ` 形式的访问令牌; - 对于所有的 `GET` 请求返回的响应,响应头添加 `Content-Disposition: attachment`; - 对于非文本类型(文本类型包括响应头 `Content-Type` 中值是`application/json`、`application/x-www-form-urlencoded`、`text/plain` 类型的返回内容)的响应,响应头添加 `Content-Disposition: attachment`; - Server-sent Event 和 WebSocket 请求不受到上述限制。 --- # 云托管/开发指南/调用云托管服务/内网访问 > 当前文档链接: https://docs.cloudbase.net/run/develop/access/internal 云托管支持服务间[内网访问](/run/deploy/networking/private),允许您在云托管服务之间进行高效的通信。内网调用可以提高服务间通信的速度和安全性,避免了公共互联网的延迟和潜在风险。 ## 使用 gRPC 连接到其它服务 本页面向希望使用 gRPC 将云托管服务与其它服务连接起来(例如在内部服务直接提供高性能通信)的开发者介绍特定于云托管服务的详细信息。您将可以将[所有 gRPC 类型](https://grpc.io/docs/what-is-grpc/core-concepts/#rpc-life-cycle)(流式传输或一元)与云托管结合使用。 可能使用的场景包括: - 内部微服务之间的相互通信 - 在 gRPC 服务器中使用流式传输 gRPC 来构建响应更快的应用和 API。 如需将您的服务与 gRPC 集成,请执行以下操作: - `服务设置` -> `网络访问` 中开启内网开关,并通过内网域名进行相互访问。 - 如果使用流式传输 gRPC,将服务配置为使用 HTTP/2。 HTTP/2 是 gRPC 流式传输的传输方法。 - 在 proto 文件中定义请求消息和响应,并对其进行编译。 - 创建一个客户端,用于发送请求并处理来自 gRPC 服务器的响应。 - 构建和部署服务。 ### 监听云托管服务中的 gRPC 请求 云托管中运行的 gRPC 服务器的唯一特殊要求是监听 PORT 环境变量指定的端口,如以下代码所示: ```go func main() { log.Printf("grpc-ping: starting server...") port := os.Getenv("PORT") if port == "" { port = "8080" log.Printf("Defaulting to port %s", port) } listener, err := net.Listen("tcp", ":"+port) if err != nil { log.Fatalf("net.Listen: %v", err) } grpcServer := grpc.NewServer() pb.RegisterPingServiceServer(grpcServer, &pingService{}) if err = grpcServer.Serve(listener); err != nil { log.Fatal(err) } } ``` ### 打开与服务的 gRPC 连接 要打开与服务的 gRPC 连接以便发送 gRPC 消息,需要打开服务的内网开关,获取到内网域名,同时通过 PORT 环境变量只能的端口进行访问, 如下代码所示: ```go import ( "crypto/tls" "crypto/x509" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) // NewConn creates a new gRPC connection. // host should be of the form domain:port, e.g., example.com:443 func NewConn(host string, insecure bool) (*grpc.ClientConn, error) { var opts []grpc.DialOption if host != "" { opts = append(opts, grpc.WithAuthority(host)) } if insecure { opts = append(opts, grpc.WithInsecure()) } else { // Note: On the Windows platform, use of x509.SystemCertPool() requires // Go version 1.18 or higher. systemRoots, err := x509.SystemCertPool() if err != nil { return nil, err } cred := credentials.NewTLS(&tls.Config{ RootCAs: systemRoots, }) opts = append(opts, grpc.WithTransportCredentials(cred)) } return grpc.Dial(host, opts...) } ``` ### 发送请求 以下示例展示了如何使用按之前所述方法配置的 gRPC 连接来发送服务请求: ```go import ( "context" "time" pb "github.com/GoogleCloudPlatform/golang-samples/run/grpc-ping/pkg/api/v1" "google.golang.org/grpc" ) // pingRequest sends a new gRPC ping request to the server configured in the connection. func pingRequest(conn *grpc.ClientConn, p *pb.Request) (*pb.Response, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() client := pb.NewPingServiceClient(conn) return client.Send(ctx, p) } ``` --- # 云托管/开发指南/调用云托管服务/使用 Websocket > 当前文档链接: https://docs.cloudbase.net/run/develop/access/websocket [WebSocket](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets_API) 是一种网络通信协议,它提供了在单个 TCP 连接上进行全双工通信的能力。与传统的 HTTP 请求-响应模式不同,WebSocket 允许服务器主动向客户端推送数据,实现了真正的双向通信。 主要特点: - 全双工通信:客户端和服务器可以同时发送和接收数据 - 低延迟:建立连接后,数据传输的开销很小 - 实时性:服务器可以主动推送数据,无需客户端轮询 - 持久连接:一次握手后保持连接状态,避免重复建立连接 WebSocket 协议以 `ws://` 或 `wss://`(安全连接)开头,使用标准的 HTTP 握手过程建立连接,之后升级为 WebSocket 协议进行通信。 本文介绍如何使用云托管(容器型和函数型)实现 Websocket 收发消息,并在客户端访问这些服务。 ## 使用函数型云托管 在本地创建一个目录 `websocket-demo`,在目录中新建一个 `index.js` 文件,并编写以下代码: ```js exports.main = function (event, context) { console.log({ event, context }); if (context.ws) { const ws = context.ws; ws.on("close", (msg) => { console.log("close: ", msg); ws.send("bye!"); }); ws.on("message", (msg) => { console.log("message: ", msg); ws.send(`echo: ${msg?.data}`); }); } }; exports.main.handleUpgrade = async function (upgradeContext) { console.log(upgradeContext, "upgradeContext"); if (upgradeContext.httpContext.url === "/reject-upgrade") { return { allowWebSocket: false, statusCode: 403, body: JSON.stringify({ code: "code", message: "message" }), contentType: "appliaction/json; charset=utf-8", }; } return { allowWebSocket: true }; }; ``` 这段代码实现了以下功能: - main 函数处理 WebSocket 连接后的消息交互: - 监听 close 事件,在连接关闭时发送告别消息 - 监听 message 事件,收到消息时回复当前时间和消息内容 - handleUpgrade 函数控制 WebSocket 连接的建立: - 当访问 /reject-upgrade 时拒绝连接 - 其他路径允许建立 WebSocket 连接 ### 部署调试 推荐使用 [云开发 CLI](/cli-v1/install) 进行部署。 安装 CLI 并登录后,进入项目根目录,执行以下命令: ```bash # 部署函数型云托管实例 tcb cloudrunfuntion deploy ``` 实例部署完成以后,前往 [云开发控制台](https://tcb.cloud.tencent.com/dev)。 在左侧导航栏选择【云托管】, 在【服务列表】页面可以找到刚刚部署的实例,点击进入【服务详情】页面,在详情页可以找到默认域名。 将默认域名开头的 `https://` 替换为 `ws://` 或者 `wss://` ,在 Postman 等工具中进行 WebSocket 连接测试。 ## 使用容器型云托管 你可以使用代码示例,[示例代码仓库地址](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/cloudbaserun/websocket-demo)。 克隆代码仓库后,进入项目根目录,目录结构如下: ```bash ├── Dockerfile ├── app │   └── server.js └── package.json ``` 其中 `app/server.js` 是使用 `express` 和 `express-ws` 实现的服务,云托管会根据 `Dockerfile` 来构建容器。 关于容器型云托管的更多信息,可以参考 [服务开发说明](/run/develop/developing-guide)。 ### 部署调试 推荐使用 [云开发 CLI](/cli-v1/install) 进行部署。 安装 CLI 并登录后,进入项目根目录,执行以下命令: ```bash # 部署容器型云托管实例 tcb cloudrun deploy ``` 实例部署完成以后,前往 [云开发控制台](https://tcb.cloud.tencent.com/dev)。 在左侧导航栏选择【云托管】, 在【服务列表】页面可以找到刚刚部署的实例,点击进入【服务详情】页面,在详情页可以找到默认域名,我们假设创建的服务默认域名为:https://demo.ap-shanghai.run.tcloudbase.com,将默认域名开头的 `https://` 替换为 `ws://` 或者 `wss://` 如:wss://demo.ap-shanghai.run.tcloudbase.com,在 Postman 等工具中进行 WebSocket 连接测试。 ## 访问 websocket 服务 ### 微信小程序 ```js wx.cloud.init({ env: "xxxx-yyy", // 替换为你的环境 ID }); const { socketTask } = await wx.cloud.connectContainer({ service: "websocket-demo", // 替换自己的服务名 path: "/", // 不填默认根目录 }); socketTask.onMessage(function (res) { console.log("【WEBSOCKET】", res.data); }); socketTask.onOpen(function (res) { console.log("【WEBSOCKET】", "链接成功!"); socketTask.send({ data: "这是小程序消息", }); }); socketTask.onClose(function (res) { console.log("【WEBSOCKET】链接关闭!"); }); ``` 更新信息可以查看[微信云托管文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloudrun/src/development/websocket/miniprogram.html)。 ### 浏览器 ```js const ws = new WebSocket("wss://demo.ap-shanghai.run.tcloudbase.com"); // 替换为你的服务地址 ws.onopen = function () { console.log("链接建立成功"); ws.send("微信云托管测试信息"); // 发送消息 }; ws.onmessage = function (evt) { console.log(evt.data); }; ws.onclose = function () { console.log("链接已经关闭"); }; ``` ### Node.js Node.js 中可以使用 [ws](https://www.npmjs.com/package/ws) 模块来实现 WebSocket 客户端。 ```js import WebSocket from "ws"; function run() { const ws = new WebSocket("wss://demo.ap-shanghai.run.tcloudbase.com"); // 替换为你的服务地址 ws.on("close", (code, reason) => { console.log("close:", code, `${reason}`); }); ws.on("error", (err) => { console.error("error: ", err); }); ws.on("upgrade", () => { console.log("upgrade"); }); ws.on("ping", () => { console.log("recv ping message"); }); ws.on("pong", () => { console.log("recv pong message"); setTimeout(() => { ws.ping(); }, 1000); }); ws.on("unexpected-response", (ws, req, res) => { // 非 upgrade 响应和 3xx 重定向响应认为是 unexpected-response console.log("recv unexpected-response message"); }); ws.on("message", (data) => { console.log("received: %s", data); }); ws.on("open", () => { ws.ping(); ws.send("string data"); ws.send(Buffer.from("buffer data")); }); } run(); ``` ### 注意事项 ``` 业务代码中使用WebSocket/SSE 出现超时的情况,这块都是用户业务代码中收到请求后长时间(60s)没有给出响应 , 如果WebSocket/SSE 连接建立后长时间(60s)没有数据传输,网关会主动关闭非活跃的连接,建议客户端或者服务端每隔10s发送一个心跳包来保持连接; ``` --- # 云托管/开发指南/资源集成/未命名文档 > 当前文档链接: https://docs.cloudbase.net/run/develop/resource-integration/mysql --- title: MySQL 数据库集成 description: 详细介绍在云托管中连接和操作 MySQL 数据库的多种方式,包括云开发 MySQL、公网连接和内网互联,提供完整的配置步骤和最佳实践 keywords: [MySQL, 数据库, 云托管, 公网连接, 内网互联, 腾讯云, 连接池, Express] --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import DocCardList from '@theme/DocCardList'; # MySQL 数据库集成 ## 概述 「云托管」提供了多种方式连接和操作 MySQL 数据库,满足不同场景的应用需求。根据您的数据库部署位置和网络环境,选择合适的连接方式: | 连接方式 | 使用场景 | 优势 | | -------------------- | --------------------------------- | ---------------------- | | 云开发 MySQL(推荐) | 使用云开发环境自带的 MySQL 数据库 | 配置简单,自动内网直连 | | 内网互联 | 连接腾讯云其他 MySQL 实例 | 高性能,低延迟,安全 | | 公网连接 | 连接任意可公网访问的 MySQL 实例 | 灵活性高,适用范围广 | > **注意**:本文示例基于 [Express 应用框架](../languages-frameworks/express) 构建,您可以根据自己的技术栈选择相应的数据库驱动和连接方式。 ## 快速开始 :::info 📌 前置说明 如果是连接小租户的 云开发 MySQL,需要在云托管服务中「**开启私有网络配置**」,否则无法通过内网访问数据库。配置方法请参考下方「步骤 3:配置网络连接」。 ::: ### 步骤 1:准备数据库 1. 登录 [云开发平台/MySQL 数据库](https://tcb.cloud.tencent.com/dev?#/db/mysql/table/default/) 2. 根据您的情况选择: - **已有数据库**:参考 [MySQL 迁移至自有账号](/database/configuration/db/tdsql/migrate-to-user-account) 文档,将数据库迁移到您的自有腾讯云账号,迁移后可支持 VPC 内网连接 - **首次使用**:系统将提示您初始化数据库,选择私有网络及子网后确认 3. 在 [数据库设置](https://tcb.cloud.tencent.com/dev?#/db/mysql/setting) 页面复制「内网连接地址」 :::tip 连接字符串格式 格式:`mysql://root:密码@内网地址:3306/tcb` ::: 1. 登录 [腾讯云 MySQL 控制台](https://console.cloud.tencent.com/cdb) 2. 确认 MySQL 实例与云托管在同一地域(推荐上海) 3. 记录实例的内网地址、端口、用户名、密码 1. 确保 MySQL 数据库已开启公网访问 2. 配置防火墙规则,允许云托管访问 3. 记录数据库的公网地址、端口、用户名、密码 :::warning 安全提醒 公网连接存在安全风险,生产环境建议使用内网连接 ::: ### 步骤 2:安装数据库驱动 在云托管项目中安装 `mysql2`: ```bash npm install mysql2 --save ``` ```bash yarn add mysql2 ``` ```bash pnpm add mysql2 ``` ### 步骤 3:配置网络连接 1. 进入 [云开发平台/云托管](https://tcb.cloud.tencent.com/dev?#/platform-run) 2. 选择您的云托管服务,进入「服务配置」页面 3. 在「网络配置」中开启「私有网络」 4. **选择云开发 MySQL 数据库所在的 VPC** 1. 进入 [云开发平台/云托管](https://tcb.cloud.tencent.com/dev?#/platform-run) 2. 选择您的云托管服务,进入「服务配置」页面 3. 在「网络配置」中开启「私有网络」 4. **选择目标 VPC**(MySQL 实例所在的 VPC) :::tip 查看 VPC 信息 在腾讯云 MySQL 控制台的实例详情页面可查看 VPC 和子网信息 ::: 无需配置网络,云托管默认可访问公网 ### 步骤 4:编写云托管代码 ```javascript const express = require('express'); const mysql = require('mysql2/promise'); const router = express.Router(); // 全局连接池 let pool; function getPool() { if (!pool) { pool = mysql.createPool({ host: process.env.DB_HOST, port: process.env.DB_PORT || 3306, user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD, database: process.env.DB_NAME || 'tcb', waitForConnections: true, connectionLimit: 10, queueLimit: 0, acquireTimeout: 60000, timeout: 60000, charset: 'utf8mb4' }); } return pool; } /* GET home page. */ router.get('/', async function(req, res, next) { try { const pool = getPool(); const connection = await pool.getConnection(); try { const [rows] = await connection.query('SELECT * FROM persons LIMIT 10'); res.json({ success: true, data: rows }); } finally { connection.release(); } } catch (error) { console.error('数据库操作失败:', error); res.status(500).json({ success: false, error: error.message }); } }); module.exports = router; ``` ### 步骤 5:配置环境变量 在云托管服务的「环境变量」中配置: | 环境变量 | 说明 | 示例值 | | ------------- | ---------- | ------------------------------- | | `DB_HOST` | 数据库地址 | `gz-xxxxx.mysql.tencentcdb.com` | | `DB_PORT` | 端口 | `3306` | | `DB_USER` | 用户名 | `root` | | `DB_PASSWORD` | 密码 | `your_password` | | `DB_NAME` | 数据库名称 | `tcb` | :::tip 使用连接字符串 也可以配置 `CONNECTION_URI` 环境变量,直接使用完整连接字符串 ::: ## 连接方式详解 ### 配置差异对比 三种连接方式的**核心差异**仅在于网络配置,代码实现完全相同: | 连接方式 | 网络配置 | 数据库地址 | 适用场景 | | ------------ | -------------------------- | ---------- | ------------------ | | 云开发 MySQL | 配置 VPC(数据库所在 VPC) | 内网地址 | 云开发项目首选 | | 内网互联 | 开启内网互联(目标 VPC) | 内网地址 | 已有腾讯云 MySQL | | 公网连接 | 无需配置 | 公网地址 | 第三方或自建数据库 | :::tip 配置建议 - **云开发 MySQL**:直接使用「快速开始」即可,最简单 - **腾讯云 MySQL**:仅需调整网络配置为「内网互联」,其他步骤相同 - **公网 MySQL**:无需网络配置,但需确保数据库已开启公网访问 ::: ## 数据库操作 ### 基础 CRUD 操作 在云托管中实现完整的增删改查(适用于所有连接方式): ```javascript const express = require('express'); const mysql = require('mysql2/promise'); const router = express.Router(); let pool; function getPool() { if (!pool) { pool = mysql.createPool({ host: process.env.DB_HOST, port: process.env.DB_PORT || 3306, user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD, database: process.env.DB_NAME || 'tcb', connectionLimit: 10, waitForConnections: true, charset: 'utf8mb4' }); } return pool; } // 查询列表 router.get('/users', async (req, res) => { try { const pool = getPool(); const connection = await pool.getConnection(); try { const [rows] = await connection.query('SELECT * FROM persons ORDER BY created_at DESC LIMIT 10'); res.json({ success: true, data: rows }); } finally { connection.release(); } } catch (error) { console.error('查询失败:', error); res.status(500).json({ success: false, error: '查询失败' }); } }); // 创建记录 router.post('/users', async (req, res) => { const { name, age, email } = req.body; if (!name || !age || !email) { return res.status(400).json({ error: '缺少必要参数' }); } try { const pool = getPool(); const connection = await pool.getConnection(); try { const [result] = await connection.query( 'INSERT INTO persons (name, age, email) VALUES (?, ?, ?)', [name, age, email] ); res.status(201).json({ success: true, data: { id: result.insertId, name, age, email } }); } finally { connection.release(); } } catch (error) { if (error.code === 'ER_DUP_ENTRY') { return res.status(409).json({ error: '邮箱已存在' }); } res.status(500).json({ error: '创建失败' }); } }); // 更新记录 router.put('/users/:id', async (req, res) => { const { id } = req.params; const { name, age, email } = req.body; try { const pool = getPool(); const connection = await pool.getConnection(); try { const [result] = await connection.query( 'UPDATE persons SET name = ?, age = ?, email = ? WHERE id = ?', [name, age, email, id] ); if (result.affectedRows === 0) { return res.status(404).json({ error: '用户不存在' }); } res.json({ success: true }); } finally { connection.release(); } } catch (error) { res.status(500).json({ error: '更新失败' }); } }); // 删除记录 router.delete('/users/:id', async (req, res) => { const { id } = req.params; try { const pool = getPool(); const connection = await pool.getConnection(); try { const [result] = await connection.query('DELETE FROM persons WHERE id = ?', [id]); if (result.affectedRows === 0) { return res.status(404).json({ error: '用户不存在' }); } res.json({ success: true }); } finally { connection.release(); } } catch (error) { res.status(500).json({ error: '删除失败' }); } }); module.exports = router; ``` ### 测试 API ```bash # 获取用户列表 curl https://your-domain.com/users # 创建用户 curl -X POST https://your-domain.com/users \ -H "Content-Type: application/json" \ -d '{"name":"张三","age":25,"email":"test@example.com"}' # 更新用户 curl -X PUT https://your-domain.com/users/1 \ -H "Content-Type: application/json" \ -d '{"name":"李四","age":30,"email":"lisi@example.com"}' # 删除用户 curl -X DELETE https://your-domain.com/users/1 ``` ```javascript // 获取用户列表 async function getUsers() { const response = await fetch('https://your-domain.com/users'); const result = await response.json(); console.log(result); } // 创建用户 async function createUser() { const response = await fetch('https://your-domain.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: '新用户', age: 25, email: 'newuser@example.com' }) }); const result = await response.json(); console.log(result); } ``` ## 最佳实践 ### 连接池配置优化 ```javascript const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, port: process.env.DB_PORT ? Number(process.env.DB_PORT) : 3306, // 连接池配置 connectionLimit: 10, // 最大连接数 queueLimit: 0, // 队列限制,0 表示无限制 acquireTimeout: 60000, // 获取连接超时时间 timeout: 60000, // 查询超时时间 // 重连配置 reconnect: true, // 自动重连 // 字符集配置 charset: 'utf8mb4', // 支持 emoji 和特殊字符 // SSL 配置(生产环境推荐) ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, // 时区配置 timezone: '+08:00' }); ``` ### 错误处理和重试机制 ```javascript // 带重试的数据库操作 async function executeWithRetry(operation, maxRetries = 3) { let lastError; for (let i = 0; i < maxRetries; i++) { try { return await operation(); } catch (error) { lastError = error; // 判断是否为可重试的错误 if (isRetryableError(error) && i < maxRetries - 1) { const delay = Math.pow(2, i) * 1000; // 指数退避 await new Promise(resolve => setTimeout(resolve, delay)); continue; } throw error; } } throw lastError; } function isRetryableError(error) { const retryableCodes = [ 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ER_LOCK_WAIT_TIMEOUT', 'PROTOCOL_CONNECTION_LOST' ]; return retryableCodes.includes(error.code); } // 使用示例 router.get('/users', async (req, res) => { try { const result = await executeWithRetry(async () => { const connection = await pool.getConnection(); try { const [rows] = await connection.query('SELECT * FROM persons'); return rows; } finally { connection.release(); } }); res.json({ success: true, data: result }); } catch (error) { console.error('查询失败:', error); res.status(500).json({ success: false, error: '查询失败' }); } }); ``` ### 事务处理 ```javascript // 事务处理示例 async function transferMoney(fromUserId, toUserId, amount) { const connection = await pool.getConnection(); try { await connection.beginTransaction(); // 检查发送方余额 const [fromUser] = await connection.query( 'SELECT balance FROM users WHERE id = ? FOR UPDATE', [fromUserId] ); if (fromUser[0].balance < amount) { throw new Error('余额不足'); } // 扣除发送方余额 await connection.query( 'UPDATE users SET balance = balance - ? WHERE id = ?', [amount, fromUserId] ); // 增加接收方余额 await connection.query( 'UPDATE users SET balance = balance + ? WHERE id = ?', [amount, toUserId] ); // 记录转账日志 await connection.query( 'INSERT INTO transfer_logs (from_user_id, to_user_id, amount, created_at) VALUES (?, ?, ?, NOW())', [fromUserId, toUserId, amount] ); await connection.commit(); return { success: true, message: '转账成功' }; } catch (error) { await connection.rollback(); throw error; } finally { connection.release(); } } ``` ### 安全性 - 使用环境变量存储敏感信息,避免硬编码 - 为数据库用户设置最小权限 - 定期更新数据库密码 - 使用参数化查询防止 SQL 注入 ### 性能优化 - 为频繁查询的字段创建索引 - 优化查询语句,避免全表扫描 - 使用适当的数据类型和表结构 - 考虑使用读写分离提高性能 ## 常见问题 ### 连接问题
连接超时怎么办? **可能原因:** 1. 网络延迟过高 2. 数据库服务器负载过高 3. 防火墙阻止连接 **解决方案:** ```javascript // 增加超时时间 const pool = mysql.createPool({ // ... 其他配置 acquireTimeout: 120000, // 增加到 2 分钟 timeout: 120000, // 查询超时 2 分钟 connectTimeout: 60000 // 连接超时 1 分钟 }); ```
连接数过多怎么处理? **解决方案:** 1. 优化连接池配置 2. 及时释放连接 3. 使用连接监控 ```javascript // 监控连接池状态 setInterval(() => { console.log('连接池状态:', { 总连接数: pool._allConnections.length, 空闲连接数: pool._freeConnections.length, 使用中连接数: pool._acquiringConnections.length }); }, 30000); ```
### 性能问题
查询速度慢怎么优化? **优化策略:** 1. 添加适当的索引 2. 优化 SQL 查询 3. 使用连接池 4. 实施查询缓存 ```sql -- 添加索引示例 CREATE INDEX idx_persons_email ON persons(email); CREATE INDEX idx_persons_age ON persons(age); CREATE INDEX idx_persons_created_at ON persons(created_at); ```
### 安全问题
如何防止 SQL 注入? **防护措施:** 1. 始终使用参数化查询 2. 验证输入数据 3. 使用最小权限原则 ```javascript // 正确的参数化查询 const [rows] = await connection.query( 'SELECT * FROM persons WHERE name = ? AND age > ?', [userName, minAge] ); // 错误的字符串拼接(容易受到 SQL 注入攻击) // const query = `SELECT * FROM persons WHERE name = '${userName}'`; ```
:::tip 性能建议 - 在生产环境中启用连接池以提高性能 - 对于频繁查询的字段添加索引 - 使用事务确保数据一致性 - 定期监控数据库性能和慢查询日志 ::: :::warning 安全提醒 - 避免在代码中硬编码数据库密码 - 使用参数化查询防止 SQL 注入 - 定期更新数据库版本和安全补丁 - 配置适当的防火墙规则 ::: --- # 云托管/开发指南/资源集成/未命名文档 > 当前文档链接: https://docs.cloudbase.net/run/develop/resource-integration/tencentcloud --- title: 内网调用腾讯云其它数据库资源 description: 详细介绍如何通过内网互联功能在云托管中安全访问腾讯云账号下的各种数据库资源,包括 MySQL、Redis、Kafka 等服务的配置和使用方法 keywords: [内网互联, 腾讯云, 数据库, MySQL, Redis, Kafka, VPC, 云托管, 安全访问] --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import DocCardList from '@theme/DocCardList'; # 内网调用腾讯云其它数据库资源 ## 概述 如果您希望您的云托管可以安全地访问您的腾讯云账号下的其它资源,如 MySQL、Redis、Kafka,甚至其它 CVM 上部署的服务等,您可以使用云托管的「内网互联」功能。开启内网互联功能之后,即可在云托管中通过内网 IP 访问该 VPC 下的相关资源。 ### 内网互联的优势 - **安全性高**:数据传输不经过公网,降低安全风险 - **性能优异**:内网延迟低,带宽大,访问速度快 - **成本节约**:避免公网流量费用 - **稳定可靠**:内网环境更加稳定,减少网络波动影响 ## 配置内网互联 ### 前提条件 1. 您的腾讯云账号下已有 VPC 网络 2. 目标资源(如数据库实例)已部署在该 VPC 中 3. 云函数和目标资源在同一地域 ### 配置步骤 #### 1. 开启内网互联 在云托管控制台中配置内网互联: 1. 登录 [云开发控制制台] 2. 选择对应的云托管服务,进入**服务配置**页面,点击**编辑** 3. 在**私有网络设置**部分 4. 选择目标 VPC 和子网 5. 保存配置 #### 2. 配置安全组 确保安全组规则允许云函数访问目标资源: ```bash # 示例:允许云托管访问 MySQL(端口 3306) 入站规则: - 协议:TCP - 端口:3306 - 来源:云托管所在子网的 CIDR(如 10.0.1.0/24) # 示例:允许云托管访问 Redis(端口 6379) 入站规则: - 协议:TCP - 端口:6379 - 来源:云托管所在子网的 CIDR ``` #### 3. 获取内网地址 在对应的云服务控制台获取资源的内网访问地址: | 服务类型 | 控制台位置 | 内网地址示例 | | -------- | -------------------------- | ----------------- | | MySQL | 数据库 MySQL > 实例详情 | `10.0.1.100:3306` | | Redis | 数据库 Redis > 实例详情 | `10.0.1.101:6379` | | Kafka | 消息队列 CKafka > 实例详情 | `10.0.1.102:9092` | | CVM | 云服务器 CVM > 实例详情 | `10.0.1.103:80` | ## 访问数据库服务 ### MySQL 数据库 ```javascript const mysql = require("mysql2/promise"); // 使用内网地址连接 MySQL const pool = mysql.createPool({ host: "10.0.1.100", // MySQL 内网地址 port: 3306, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, connectionLimit: 10, acquireTimeout: 60000, timeout: 60000, }); exports.main = async (event, context) => { try { const connection = await pool.getConnection(); try { // 执行查询 const [rows] = await connection.query("SELECT * FROM users LIMIT 10"); return { statusCode: 200, body: { success: true, data: rows, message: "查询成功", }, }; } finally { connection.release(); } } catch (error) { console.error("MySQL 连接失败:", error); return { statusCode: 500, body: { success: false, error: error.message, }, }; } }; ``` ```python import pymysql import json import os def main_handler(event, context): try: # 使用内网地址连接 MySQL connection = pymysql.connect( host='10.0.1.100', # MySQL 内网地址 port=3306, user=os.environ['DB_USER'], password=os.environ['DB_PASSWORD'], database=os.environ['DB_NAME'], charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor ) with connection: with connection.cursor() as cursor: # 执行查询 cursor.execute("SELECT * FROM users LIMIT 10") result = cursor.fetchall() return { 'statusCode': 200, 'body': json.dumps({ 'success': True, 'data': result, 'message': '查询成功' }, ensure_ascii=False) } except Exception as e: print(f'MySQL 连接失败: {str(e)}') return { 'statusCode': 500, 'body': json.dumps({ 'success': False, 'error': str(e) }, ensure_ascii=False) } ``` ### Redis 缓存 ```javascript const redis = require("redis"); // 创建 Redis 客户端(使用内网地址) const client = redis.createClient({ host: "10.0.1.101", // Redis 内网地址 port: 6379, password: process.env.REDIS_PASSWORD, db: 0, retry_strategy: (options) => { if (options.error && options.error.code === "ECONNREFUSED") { return new Error("Redis 服务器拒绝连接"); } if (options.total_retry_time > 1000 * 60 * 60) { return new Error("重试时间超过 1 小时"); } if (options.attempt > 10) { return undefined; } return Math.min(options.attempt * 100, 3000); }, }); exports.main = async (event, context) => { try { // 连接 Redis await client.connect(); const { action, key, value } = event; let result; switch (action) { case "get": result = await client.get(key); break; case "set": await client.set(key, value, "EX", 3600); // 设置 1 小时过期 result = "OK"; break; case "del": result = await client.del(key); break; case "exists": result = await client.exists(key); break; default: throw new Error("不支持的操作"); } return { statusCode: 200, body: { success: true, data: result, message: "操作成功", }, }; } catch (error) { console.error("Redis 操作失败:", error); return { statusCode: 500, body: { success: false, error: error.message, }, }; } finally { await client.quit(); } }; ``` ```python import redis import json import os def main_handler(event, context): try: # 使用内网地址连接 Redis r = redis.Redis( host='10.0.1.101', # Redis 内网地址 port=6379, password=os.environ.get('REDIS_PASSWORD'), db=0, decode_responses=True, socket_timeout=5, socket_connect_timeout=5 ) action = event.get('action') key = event.get('key') value = event.get('value') if action == 'get': result = r.get(key) elif action == 'set': r.setex(key, 3600, value) # 设置 1 小时过期 result = 'OK' elif action == 'del': result = r.delete(key) elif action == 'exists': result = r.exists(key) else: raise ValueError('不支持的操作') return { 'statusCode': 200, 'body': json.dumps({ 'success': True, 'data': result, 'message': '操作成功' }, ensure_ascii=False) } except Exception as e: print(f'Redis 操作失败: {str(e)}') return { 'statusCode': 500, 'body': json.dumps({ 'success': False, 'error': str(e) }, ensure_ascii=False) } ``` ### Kafka 消息队列 ```javascript const { Kafka } = require("kafkajs"); // 创建 Kafka 客户端(使用内网地址) const kafka = Kafka({ clientId: "scf-kafka-client", brokers: ["10.0.1.102:9092"], // Kafka 内网地址 sasl: { mechanism: "plain", username: process.env.KAFKA_USERNAME, password: process.env.KAFKA_PASSWORD, }, }); exports.main = async (event, context) => { const { action, topic, message, groupId } = event; try { if (action === "produce") { // 生产消息 const producer = kafka.producer(); await producer.connect(); await producer.send({ topic: topic, messages: [ { key: Date.now().toString(), value: JSON.stringify(message), timestamp: Date.now(), }, ], }); await producer.disconnect(); return { statusCode: 200, body: { success: true, message: "消息发送成功", }, }; } else if (action === "consume") { // 消费消息 const consumer = kafka.consumer({ groupId: groupId || "scf-group" }); await consumer.connect(); await consumer.subscribe({ topic: topic }); const messages = []; await consumer.run({ eachMessage: async ({ topic, partition, message }) => { messages.push({ topic, partition, offset: message.offset, key: message.key?.toString(), value: message.value?.toString(), timestamp: message.timestamp, }); // 限制消息数量,避免超时 if (messages.length >= 10) { await consumer.stop(); } }, }); // 等待一段时间收集消息 await new Promise((resolve) => setTimeout(resolve, 5000)); await consumer.disconnect(); return { statusCode: 200, body: { success: true, data: messages, message: "消息消费成功", }, }; } } catch (error) { console.error("Kafka 操作失败:", error); return { statusCode: 500, body: { success: false, error: error.message, }, }; } }; ``` ```python from kafka import KafkaProducer, KafkaConsumer import json import os from datetime import datetime def main_handler(event, context): action = event.get('action') topic = event.get('topic') try: if action == 'produce': # 生产消息 producer = KafkaProducer( bootstrap_servers=['10.0.1.102:9092'], # Kafka 内网地址 security_protocol='SASL_PLAINTEXT', sasl_mechanism='PLAIN', sasl_plain_username=os.environ['KAFKA_USERNAME'], sasl_plain_password=os.environ['KAFKA_PASSWORD'], value_serializer=lambda v: json.dumps(v).encode('utf-8') ) message = event.get('message', {}) message['timestamp'] = datetime.now().isoformat() future = producer.send(topic, message) producer.flush() producer.close() return { 'statusCode': 200, 'body': json.dumps({ 'success': True, 'message': '消息发送成功' }, ensure_ascii=False) } elif action == 'consume': # 消费消息 consumer = KafkaConsumer( topic, bootstrap_servers=['10.0.1.102:9092'], security_protocol='SASL_PLAINTEXT', sasl_mechanism='PLAIN', sasl_plain_username=os.environ['KAFKA_USERNAME'], sasl_plain_password=os.environ['KAFKA_PASSWORD'], group_id=event.get('groupId', 'scf-group'), value_deserializer=lambda m: json.loads(m.decode('utf-8')), consumer_timeout_ms=5000 # 5 秒超时 ) messages = [] for message in consumer: messages.append({ 'topic': message.topic, 'partition': message.partition, 'offset': message.offset, 'key': message.key.decode('utf-8') if message.key else None, 'value': message.value, 'timestamp': message.timestamp }) # 限制消息数量 if len(messages) >= 10: break consumer.close() return { 'statusCode': 200, 'body': json.dumps({ 'success': True, 'data': messages, 'message': '消息消费成功' }, ensure_ascii=False) } except Exception as e: print(f'Kafka 操作失败: {str(e)}') return { 'statusCode': 500, 'body': json.dumps({ 'success': False, 'error': str(e) }, ensure_ascii=False) } ``` ## 访问其他服务 ### CVM 服务器 ```javascript const axios = require("axios"); exports.main = async (event, context) => { try { // 访问 CVM 上的 HTTP 服务(使用内网地址) const response = await axios({ method: "GET", url: "http://10.0.1.103:8080/api/data", // CVM 内网地址 timeout: 10000, headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.API_TOKEN}`, }, }); return { statusCode: 200, body: { success: true, data: response.data, message: "CVM 服务调用成功", }, }; } catch (error) { console.error("CVM 服务调用失败:", error); return { statusCode: 500, body: { success: false, error: error.message, }, }; } }; ``` ## 最佳实践 ### 连接池管理 ```javascript // 全局连接池,避免每次调用都创建新连接 let mysqlPool; let redisClient; function getMysqlPool() { if (!mysqlPool) { mysqlPool = mysql.createPool({ host: process.env.MYSQL_HOST, port: 3306, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE, connectionLimit: 5, // 云函数环境建议较小的连接数 acquireTimeout: 60000, timeout: 60000, reconnect: true, }); } return mysqlPool; } function getRedisClient() { if (!redisClient) { redisClient = redis.createClient({ host: process.env.REDIS_HOST, port: 6379, password: process.env.REDIS_PASSWORD, retry_strategy: (options) => { if (options.attempt > 3) return undefined; return Math.min(options.attempt * 100, 3000); }, }); } return redisClient; } ``` ### 错误处理和重试 ```javascript // 带重试机制的数据库操作 async function executeWithRetry(operation, maxRetries = 3) { let lastError; for (let i = 0; i < maxRetries; i++) { try { return await operation(); } catch (error) { lastError = error; // 判断是否为可重试的错误 if (isRetryableError(error) && i < maxRetries - 1) { const delay = Math.pow(2, i) * 1000; // 指数退避 await new Promise((resolve) => setTimeout(resolve, delay)); continue; } throw error; } } throw lastError; } function isRetryableError(error) { const retryableCodes = [ "ECONNRESET", "ETIMEDOUT", "ENOTFOUND", "ECONNREFUSED", ]; return ( retryableCodes.includes(error.code) || error.message.includes("Connection lost") ); } ``` ### 安全配置 ```javascript // 环境变量配置示例 const config = { mysql: { host: process.env.MYSQL_HOST, port: parseInt(process.env.MYSQL_PORT) || 3306, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE, }, redis: { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT) || 6379, password: process.env.REDIS_PASSWORD, }, kafka: { brokers: process.env.KAFKA_BROKERS?.split(",") || [], username: process.env.KAFKA_USERNAME, password: process.env.KAFKA_PASSWORD, }, }; // 配置验证 function validateConfig() { const required = [ "MYSQL_HOST", "MYSQL_USER", "MYSQL_PASSWORD", "MYSQL_DATABASE", "REDIS_HOST", "REDIS_PASSWORD", "KAFKA_BROKERS", "KAFKA_USERNAME", "KAFKA_PASSWORD", ]; const missing = required.filter((key) => !process.env[key]); if (missing.length > 0) { throw new Error(`缺少必要的环境变量: ${missing.join(", ")}`); } } ``` ### 性能监控 ```javascript // 性能监控装饰器 function withMonitoring(operation, operationName) { return async function (...args) { const startTime = Date.now(); const requestId = Math.random().toString(36).substr(2, 9); console.log(`[${requestId}] ${operationName} 开始`); try { const result = await operation.apply(this, args); const duration = Date.now() - startTime; console.log(`[${requestId}] ${operationName} 完成: ${duration}ms`); // 记录慢操作 if (duration > 5000) { console.warn( `[${requestId}] 慢操作检测: ${operationName} ${duration}ms`, ); } return result; } catch (error) { const duration = Date.now() - startTime; console.error( `[${requestId}] ${operationName} 失败: ${duration}ms`, error.message, ); throw error; } }; } // 使用示例 const monitoredMysqlQuery = withMonitoring(async (sql, params) => { const pool = getMysqlPool(); const connection = await pool.getConnection(); try { return await connection.query(sql, params); } finally { connection.release(); } }, "MySQL查询"); ``` ## 故障排查 ### 常见问题
无法连接到内网资源 **可能原因:** 1. 内网互联未正确配置 2. 安全组规则阻止访问 3. 目标服务未启动或地址错误 **排查步骤:** 1. 检查云函数的 VPC 配置是否正确 2. 验证安全组规则是否允许相应端口 3. 确认目标资源的内网地址和端口 4. 在云函数中使用 ping 或 telnet 测试连通性 ```javascript // 连通性测试代码 const net = require("net"); function testConnection(host, port, timeout = 5000) { return new Promise((resolve, reject) => { const socket = new net.Socket(); socket.setTimeout(timeout); socket.on("connect", () => { socket.destroy(); resolve(true); }); socket.on("timeout", () => { socket.destroy(); reject(new Error("连接超时")); }); socket.on("error", (error) => { reject(error); }); socket.connect(port, host); }); } ```
连接频繁断开 **解决方案:** 1. 配置连接池和重连机制 2. 适当设置超时时间 3. 实现健康检查 ```javascript // 健康检查示例 async function healthCheck() { try { await testConnection(process.env.MYSQL_HOST, 3306); await testConnection(process.env.REDIS_HOST, 6379); return { status: "healthy" }; } catch (error) { return { status: "unhealthy", error: error.message }; } } ```
### 监控和日志 ```javascript // 统一日志记录 function logger(level, message, extra = {}) { const logEntry = { timestamp: new Date().toISOString(), level, message, requestId: context.requestId, ...extra, }; console.log(JSON.stringify(logEntry)); } // 使用示例 exports.main = async (event, context) => { logger("INFO", "函数开始执行", { event }); try { // 业务逻辑 const result = await processRequest(event); logger("INFO", "函数执行成功", { result }); return result; } catch (error) { logger("ERROR", "函数执行失败", { error: error.message, stack: error.stack, }); throw error; } }; ``` :::tip 提示 - 内网互联功能仅支持同一地域的资源访问 - 建议使用连接池来提高性能和资源利用率 - 重要操作务必实现重试机制和错误处理 - 定期监控连接状态和性能指标 ::: :::warning 注意 - 确保安全组规则配置正确,避免安全风险 - 内网地址可能会发生变化,建议使用域名或配置中心 - 注意云函数的执行时间限制,避免长时间连接 - 生产环境中务必配置适当的超时时间和重试策略 ::: --- # 云托管/开发指南/优化容器镜像 > 当前文档链接: https://docs.cloudbase.net/run/develop/image-optimization CloudBase 云托管支持托管任意镜像,但我们建议您**尽可能地优化您的镜像**,以获得更快的启动速度、更快的构建速度、更小的容器体积、更优的服务性能。 以下是一些推荐做法: ## 选择更小的基础镜像 同样一种语言的运行时容器,由于选择的基础容器不同,体积也会有很大的差异,**对于大多数业务,我们推荐您使用更小的基础镜像**。 以 Node.js 为例,[官方镜像仓库](https://hub.docker.com/_/node) 提供了以下不同体积的基础镜像: ``` REPOSITORY TAG IMAGE ID CREATED SIZE node 15 ca36fba5ad66 2 days ago 941MB node 15-slim 922b09b21278 2 days ago 165MB node 15-buster 36754275e286 2 days ago 910MB node 15-buster-slim eda2c7e487ff 2 days ago 179MB node 15-alpine 1e8b781248bb 2 days ago 115MB ``` 可以注意到 `15-slim`、`15-buster-slim`、`15-alpine` 的体积明显较小,这是由于它们使用了经过裁剪的底层操作系统镜像。使用这些裁剪、优化后的镜像通常不会影响您服务的运行。 ## 减少镜像层数 Dockerfile 中的每一行指令,都会生成一个层,镜像的层数会直接影响镜像的体积大小。 我们推荐您**将多个指令合并串联,以减少层的数量**。例如,您可以将以下的指令合并为同一个: ```docker RUN cd my-app RUN make RUN make install RUN rm -rf /tmp ``` 合并后: ```docker RUN cd my-app && \ make && \ make install && \ rm -rf /tmp ``` ## 充分利用层的缓存 Docker 会对镜像的每一层单独进行缓存,如果层的内容没有变化,那么会直接使用之前的缓存,以提高构建、上传镜像的速度。为了能尽量复用这一机制,我们推荐您**将镜像变动不大的层独立出来**。 例如,您的应用可能存在诸多依赖,通常来说,这些依赖在开发过程中变动较小,所以依赖的拷贝最好独立成为一层指令: ```docker COPY ./deps deps RUN make && make install ``` 以上的做法将 COPY 语句独立出来,每次构建、推送镜像时,只要依赖的文件内容没有变化,那么都可以复用之前的缓存,以提高构建、推送速度。 ## 清理不必要的文件 您可以使用 [.dockerignore](https://docs.docker.com/engine/reference/builder/#dockerignore-file) 在容器构建时忽略文件,以减少非必要文件的导入,同时也可以提高安全性,避免将一些本地敏感文件打包到镜像中。 ## 多阶段构建 以 Java 服务为例,实际运行的过程中只需要最后编译生成的 jar 文件即可,而构建期使用的依赖、扩展包以及源代码文件都是不必要的。 此时我们可以使用多阶段构建的方式,**分离构建环境和运行环境**,从而精简运行时容器的体积,例如以下的 Dockerfile: ```docker # 使用官方 maven/Java 8 镜像作为构建环境 # https://hub.docker.com/_/maven FROM maven:3.6-jdk-11 as builder # 将代码复制到容器内 WORKDIR /app COPY pom.xml . COPY src ./src # 构建项目 RUN mvn package -DskipTests # 使用 AdoptOpenJDK 作为基础镜像 # https://hub.docker.com/r/adoptopenjdk/openjdk8 # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds FROM adoptopenjdk/openjdk11:alpine-slim # 将 jar 放入容器内 COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar # 启动服务 CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/helloworld.jar"] ``` --- # 云托管/服务指南/服务部署/部署方式 > 当前文档链接: https://docs.cloudbase.net/run/deploy/deploy/introduce 云托管为开发者提供多种灵活的部署方式,满足不同类型项目的上线需求。 ## 部署方式一览 - [容器镜像部署 🐳](./deploying-image),直接拉取自定义或官方镜像,适合有独立构建流程或特殊环境需求的项目。 - [本地代码部署 💻](./deploying-source-code),上传本地代码,平台自动构建镜像,适合快速迭代和小型项目。 - [Git 仓库部署 🔗](./deploying-git),从 git 仓库部署,支持绑定 GitHub、GitLab 等私有仓库,支持自动化触发部署,适合团队协作和 CI/CD。也支持直接填写公开仓库地址,无需授权,适合开源项目和快速体验。 - [CLI 部署 🛠️](/cli-v1/cloudrun/deploy),使用命令行工具一键上传、构建和部署,适合自动化脚本和高级用户。 不同的部署方式可根据项目需求灵活选择,云托管平台为每种方式都提供了详细的文档和操作指引,助力您的应用高效上线。 ## 通用部署参数说明 无论采用哪种部署方式,均可配置以下常用参数: - **端口**:指定服务监听的端口,平台会自动将该端口暴露为外部访问入口。例如:`8080` 或 `3000`。 - **环境变量**:可自定义环境变量(如 `NODE_ENV=production`、`API_KEY=your_key` 等),用于配置应用运行环境。支持多组键值对。 - **ENTRYPOINT**:自定义容器启动命令(覆盖镜像默认 ENTRYPOINT )。例如:`["npm", "start"]`。 - **CMD**:自定义容器启动参数(覆盖镜像默认 cmd)。例如:`["--port", "3000"]`。 - **目标目录**:指定 Dockerfile 所在的目录路径(如 `./app`),默认为项目根目录。适用于多阶段构建或子目录部署的场景。 - **Dockerfile 名称**:自定义 Dockerfile 文件名(如 `Dockerfile.prod`),默认为 `Dockerfile`。适用于多环境配置的场景。 - **资源限制**:可配置 CPU 和内存资源限制(如 `1 CPU` 和 `2GB 内存`),确保服务稳定运行。 - **网络配置**:支持配置服务间网络互通、外部访问策略等。 如需详细参数说明和最佳实践,请参考各部署方式详情页或平台官方文档。 --- # 云托管/服务指南/服务部署/部署容器镜像 > 当前文档链接: https://docs.cloudbase.net/run/deploy/deploy/deploying-image 本页面介绍如何将容器镜像部署到新的云托管服务或现有的云托管服务的新版本。 ## 使用限制 1. **镜像限制** - 云托管是基于X86架构运行的,所以暂不支持运行在ARM类型镜像,支持AMD类型的镜像。 ## 支持的容器镜像仓库 您可以直接使用存储在以下容器镜像仓库中的镜像: 1. **腾讯云容器镜像服务 (Tencent Cloud Container Registry, TCR)** - 提供稳定、高效的镜像托管服务。 - 支持私有和公有镜像仓库。 - 访问地址:[腾讯云容器镜像服务](https://console.cloud.tencent.com/tcr/)。 2. **Docker Hub** - 全球最大的公共容器镜像仓库。 - 提供丰富的开源镜像资源。 - 访问地址:[Docker Hub](https://hub.docker.com/)。 您也可以使用其他厂商提供的容器镜像服务。我们建议您优先使用腾讯云容器镜像服务,以获得更好的本地化支持。 ## 创建镜像仓库 进入 [腾讯云容器镜像服务](https://console.cloud.tencent.com/tcr/),如果是首次使用,可以创建一个个人实例,该实例是免费的,不收取任何费用。 进入 [命名空间](https://console.cloud.tencent.com/tcr/namespace) 页面,新建命名空间,比如 `my-project`。 进入[镜像仓库](https://console.cloud.tencent.com/tcr/repository) 页面,新建镜像仓库,填写服务名称,比如 `gintest`, 类型选择为`公有`, 命名空间选择刚刚创建的 `my-project` 命名空间,点击创建完成。 点击刚刚创建的镜像名称,在`操作`一栏中点击快捷指令,按照要求完成镜像上传即可。 ## 获取镜像下载地址 ### 从腾讯云容器镜像服务 (TCR) 获取镜像地址 1. **登录腾讯云控制台** - 访问 [腾讯云容器镜像服务](https://console.cloud.tencent.com/tcr/) 并登录您的账号。 2. **进入目标镜像仓库** - 在控制台中选择目标镜像仓库,点击进入详情页。 3. **查找镜像** - 在镜像列表中,找到所需的镜像及其版本标签。 4. **复制拉取命令** - 点击镜像名称,进入详情页后,复制显示的拉取命令(例如 `docker pull ccr.ccs.tencentyun.com/namespace/repository:tag`)。 ### 从 Docker Hub 获取镜像地址 1. **访问 Docker Hub** - 打开 [Docker Hub](https://hub.docker.com/) 并搜索所需的镜像。 2. **选择镜像版本** - 在镜像详情页中,切换到 `Tags` 选项卡,选择所需的版本标签。 3. **复制拉取命令** - 页面会显示拉取命令(例如 `docker pull repository:tag`),直接复制即可。 ## 示例:获取 nginx 镜像 ### 从腾讯云容器镜像服务 (TCR) 获取 nginx 镜像 1. **登录腾讯云控制台** - 访问 [腾讯云容器镜像服务](https://console.cloud.tencent.com/tcr/) 并登录您的账号。 2. **搜索 nginx 镜像** - 在镜像仓库中搜索 `nginx`,选择官方或自定义的 nginx 镜像。 3. **复制拉取命令** - 点击镜像名称,进入详情页后,复制显示的拉取命令(例如 `docker pull ccr.ccs.tencentyun.com/namespace/nginx:latest`)。 4. **拉取镜像** - 在本地终端运行复制的命令,完成镜像拉取。 ### 从 Docker Hub 获取 nginx 镜像 1. **访问 Docker Hub** - 打开 [Docker Hub](https://hub.docker.com/) 并搜索 `nginx`。 2. **选择官方镜像** - 在搜索结果中选择 `nginx` 官方镜像。 3. **复制拉取命令** - 页面会显示拉取命令(例如 `docker pull nginx:latest`),直接复制即可。 4. **拉取镜像** - 在本地终端运行复制的命令,完成镜像拉取。 ## 部署新服务 准备: - 容器像下载地址: 上一步获取到的镜像下载地址中 `docker pull` 之后片段为下载地址, 即(`ccr.ccs.tencentyun.com/namespace/repository:tag`, `repository:tag` 部分), 首次部署到服务时会创建第一个版本。请注意,版本是不可变的。 要部署容器进行请进行如下操作: - 1、进入[云托管从容器部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=image) - 2、镜像地址中填写需要指定的`容器镜像下载地址`,填写服务名称和端口 - 3、点击创建,等待部署完成即可。 - 4、部署完成之后,在概述页,获取默认访问域名进行访问测试。 ## 常见镜像部署开源项目示例 | 项目(GitHub) | 简介 | 一键部署链接 | | :------------------------------------------------------------- | :----------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Nextcloud](https://github.com/nextcloud/all-in-one) | 开源的自托管云存储与协作平台,支持文件同步、共享、在线协作办公等。 | [一键部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=image&image=nextcloud&serverName=nextcloud) | | [n8n](https://github.com/n8n-io/n8n) | 开源自动化与集成平台,支持可视化拖拽构建工作流,集成 500+ 应用。 | [一键部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=image&image=n8nio/n8n&serverName=n8n&port=5678) | | [code-server](https://github.com/coder/code-server) | 在浏览器中运行 VS Code 的开源项目,实现云端开发环境。 | [一键部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=image&image=codercom/code-server&serverName=code-server&port=8080) | | [Stirling-pdf](https://github.com/Stirling-Tools/Stirling-PDF) | 本地部署的 PDF 工具,支持合并、拆分、转换、OCR 等多种 PDF 操作。 | [一键部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=image&image=stirlingtools/stirling-pdf&serverName=stirling-pdf&port=8080) | | [Excalidraw](https://github.com/excalidraw/excalidraw) | 开源虚拟白板,支持手绘风格图表协作,适合头脑风暴与原型设计。 | [一键部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=image&image=excalidraw/excalidraw&serverName=excalidraw) | | [Crawl4ai](https://github.com/unclecode/crawl4ai) | 开源高性能异步网页爬虫,专为 AI 数据采集和 LLM 集成优化。 | [一键部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=image&image=unclecode/crawl4ai&serverName=crawl4ai&port=11235) | --- # 云托管/服务指南/服务部署/从源代码部署 > 当前文档链接: https://docs.cloudbase.net/run/deploy/deploy/deploying-source-code 本页面介绍如何通过源代码部署到云托管服务。 部署要求: * 源代码中必须包含 Dockerfile 文件 各个语言及框架 Dockerfile 编写请参考: * [Python 快速开始](../../quick-start/dockerize-python) * [Node.js 快速开始](../../quick-start/dockerize-node) * [Go 快速开始](../../quick-start/dockerize-go) * [Java 快速开始](../../quick-start/dockerize-java) * [.NET 快速开始](../../quick-start/dockerize-dotnet) * [PHP 快速开始](../../quick-start/dockerize-php) * [创建一个 Express 应用](../../develop/languages-frameworks/express) * [创建一个 Nest 应用](../../develop/languages-frameworks/nest) * [创建一个 Gin 应用](../../develop/languages-frameworks/gin) * [创建一个 Django 应用](../../develop/languages-frameworks/django) * [创建一个 Flask 应用](../../develop/languages-frameworks/flask) * [创建一个 Spring Boot 应用](../../develop/languages-frameworks/springboot) * [创建一个 FastAPI 应用](../../develop/languages-frameworks/fastapi) * [创建一个 Laravel 应用](../../develop/languages-frameworks/laravel) ## 部署 如需从源代码进行部署,请执行以下操作: 进入[云托管通过本地代码部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=package) 进入配置页, 填写如下配置: * 代码包类型,选择文件夹 * 代码包选择源代码目录进行上传 * 端口配置服务真实端口 * Dockerfile 目录填写 Dockerfile 真实所在目录,如果在根目录下,可不填写 * 填写真实 Dockerfile 名称 配置填写完成之后,点击部署按钮等待部署完成。 部署完成之后,点击切换到```概览``` 页,使用默认域名完成测试验证。 --- # 云托管/服务指南/服务部署/通过 Git 仓库部署 > 当前文档链接: https://docs.cloudbase.net/run/deploy/deploy/deploying-git 云托管支持通过绑定 GitHub、GitLab 等个人或私有 Git 仓库进行应用部署,也支持直接通过公开 Git 仓库地址进行部署。两者适用场景和操作流程略有不同: ## 公开仓库部署 - 直接填写公开 Git 仓库地址(无需授权),如 GitHub/Gitee 等开源项目。 - 适合开源项目、无需私密性和权限控制的场景。 - 操作方式与私有仓库类似,但无需账号授权,适合快速体验和分享。 ### 操作流程 1. 在云托管控制台选择“通过公开 Git 仓库地址部署”。 2. 填写公开仓库的地址和分支,配置访问端口等参数。 3. 保存并启动部署。 --- ## 私有仓库部署 - 需授权绑定 GitHub、GitLab 等账号,支持私有和个人仓库。 - 支持分支选择、构建参数配置、自动化触发部署(如 push、PR 合并等)。 - 适合团队协作、持续集成(CI/CD)、自动化部署等场景。 - 可结合 Webhook、CI 工具实现更复杂的自动化流程。 ### 操作流程 1. 在云托管控制台选择“通过 Git 仓库部署”。 2. 绑定您的 GitHub、GitLab 等代码仓库账号。 3. 选择需要部署的私有或个人仓库及分支,配置构建参数。 4. 配置自动化触发规则(如 push、PR 合并等)。 5. 保存并启动部署。 ### 启用自动部署 私有仓库的最大优势是支持自动部署功能: 1. 在服务创建或更新页面启用"自动部署"选项 2. 系统会自动在 Git 平台配置 Webhook 3. 此后,当指定分支有新的代码提交时: - Webhook 会自动触发云托管的部署流程 - 系统拉取最新代码并构建新版本 - 自动发布新版本到生产环境 配置完成后,后续每次代码变更(如 push、PR 合并)都会自动触发新的构建和部署,极大提升开发效率。 ### 持续部署最佳实践 ### 分支策略 - **开发分支**:用于日常开发工作,不触发自动部署 - **测试分支**:可配置自动部署到测试环境 - **主分支**:配置自动部署到生产环境,通常是 `master` 或 `main` ### 流程建议 1. 在开发分支进行功能开发和单元测试 2. 通过 Pull Request/Merge Request 将代码合并到测试分支 3. 自动部署到测试环境并进行集成测试 4. 测试通过后,将代码合并到主分支 5. 自动部署到生产环境 ### 部署安全 - 启用自动部署前,确保 `Dockerfile` 已经过充分测试 - 配置适当的构建超时时间,避免异常构建占用资源 - 定期检查部署日志,确保部署过程正常 - 保留多个历史版本,以便在新版本出现问题时快速回滚 ### 故障处理 - 如果自动部署后服务异常,可在云托管控制台快速回退到之前的稳定版本 - 检查构建日志和应用日志,定位问题原因 - 修复问题后,重新提交代码触发新的部署 > **注意**:首次使用自动部署功能时,建议先在非关键业务上测试,熟悉整个流程后再应用到核心服务。 --- ## 开源项目示例 | 项目(GitHub) | 简介 | 一键部署链接 | | :------------------------------------------------------- | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [AI Gateway](https://github.com/Portkey-AI/gateway) | Portkey AI Gateway,开源 AI 网关,支持多模型路由与安全管控。 | [一键部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/Portkey-AI/gateway&repoBranch=main&serverName=ai-gateway&port=8787) | | [jsoncrack](https://github.com/AykutSarac/jsoncrack.com) | 可视化 JSON 结构的开源工具,支持交互式编辑与分享。 | [一键部署](https://tcb.cloud.tencent.com/dev#/platform-run/service/create?type=git&repoUrl=https://github.com/AykutSarac/jsoncrack.com&repoBranch=main&serverName=jsoncrack&port=8080) | 如需详细操作指引,请参考云托管平台相关文档或联系技术支持。 --- # 云托管/服务指南/服务部署/通过 Cli 仓库部署 > 当前文档链接: https://docs.cloudbase.net/run/deploy/deploy/deploying-cli | 功能 | 简介 | |:------------------------------------------------------------|:-----------------------------------------------------------------| | [初始化项目](https://docs.cloudbase.net/cli-v1/cloudrun/init) | 使用初始化命令可以创建云托管服务代码项目。 | | [本地运行](https://docs.cloudbase.net/cli-v1/cloudrun/run) | 使用本地运行命令可以在开发环境中运行和调试函数型云托管服务(不支持容器型服务)。 | | [代码部署](https://docs.cloudbase.net/cli-v1/cloudrun/deploy) | 使用部署命令可以将本地项目代码发布到云托管环境。 | | [服务列表](https://docs.cloudbase.net/cli-v1/cloudrun/list) | 使用列表命令可以查看云托管环境中的所有服务及其基本信息。。 | | [下载代码](https://docs.cloudbase.net/cli-v1/cloudrun/download) | 使用下载命令可以将已部署的云托管服务代码下载到本地。。 | | [删除服务](https://docs.cloudbase.net/cli-v1/cloudrun/run) | 使用删除命令可以移除云托管服务及其关联资源。。 | 如需详细操作指引,请参考云托管平台相关文档或联系技术支持。 --- # 云托管/服务指南/服务网络/配置自定义域名 > 当前文档链接: https://docs.cloudbase.net/run/deploy/networking/custom-domains 本页介绍如何为服务配置公网自定义访问域名。 默认情况下,云托管会为每个云托管服务分配一个默认域名,但是该域名仅可用户开发和测试使用,且该域名不具有任何的鉴权策略,如果服务本身没有实现任何的鉴权策略,则有时候将服务暴露到公网是相对危险的,所以对于需要暴露到公网的服务,我们建议配置自定义域名,同时开启鉴权访问。 ## 准备 * 域名及 SSL 证书, 您也可以在[腾讯云SSL证书](https://console.cloud.tencent.com/ssl)进行购买, 购买单域名证书即可。 * 或者在[腾讯云SSL证书](https://console.cloud.tencent.com/ssl)上传您已有的证书。 ## 配置域名 * 进入[云开发控制台](https://tcb.cloud.tencent.com/dev#/env/http-access) * 选择 ```HTTP 网关``` * 在```自定义域名```部分选择```添加域名``` * 填写准备好的域名及证书,点击确认完成域名配置 ## 域名关联资源 * 进入[云开发控制台](https://tcb.cloud.tencent.com/dev#/env/http-access) * 选择 ```HTTP 网关``` * 在 ```域名关联资源``` 部分点击```新建```按钮 * 填写配置信息: * 关联资源: 云托管 * 资源实例: 资源实例选择需要访问的云托管服务 * 域名: 域名选择刚添加的自定义域名 * 触发路径: 默认为 /, 可根据实际情况填写需要暴露的路径 * 鉴权开关: 默认为关闭状态,如果需要使用[云开发鉴权](../../../authentication-v2/auth/introduce) * 路基透传: 默认为关闭状态,我们设置为开启状态 * 填写完配置信息,点击确认创建等待创建完成 * 创建完成之后,即可通过自定义域名访问云托管服务。 --- # 云托管/服务指南/服务网络/公网访问 > 当前文档链接: https://docs.cloudbase.net/run/deploy/networking/public 本页介绍如何使用公网访问。 默认情况下,云托管服务公网访问时开通状态。公网访问会提供一个默认域名,该默认域名不会伴随服务的版本变化而变化。我们强烈建议您配置自己的[自定义域名](./custom-domains), 以免存在被封的风险。 默认情况下,云托管服务不会提供任何的鉴权操作,如需鉴权操作,请按照以下进行: * 服务本身自己实现鉴权操作 * 绑定自定义域名,在自定义域名侧开启鉴权操作 使用公网访问有如下场景: * 通过 web 网页访问云托管服务(通过小程序,微信公众号h5 ```callContainer``` 方法访问,不需要开通公网访问) * 使用 curl 命令测试服务请求状态 ## 开通/关闭公网访问 您可以在创建服务时,在服务创建页面选择开启/关闭公网访问。 您也可以在服务创建之后,在 ```服务详情``` -> ```服务设置``` 中选择开启/关闭公网访问。 --- # 云托管/服务指南/服务网络/内网访问 > 当前文档链接: https://docs.cloudbase.net/run/deploy/networking/private 本页介绍如何使用内网访问。 默认情况下,云托管服务的内网访问是关闭状态。开通内网访问主要有以下场景: - 服务之间的相互访问 - 服务之间通过非 HTTP 协议进行相互访问 具体可参考[使用 gRPC 连接到其它服务](/run/develop/access/internal) ## 开通内网访问 您可以在创建服务时,在服务创建页面选择开启内网访问。 您也可以在服务创建之后,在 `服务详情` -> `服务设置` 中选择开启/关闭内网访问。 --- # 云托管/服务指南/服务网络/内网互联 > 当前文档链接: https://docs.cloudbase.net/run/deploy/networking/internal-link 本页面介绍云托管服务如何访问您的腾讯云账号的其它资源(如: cvm, mysql, redis 等)。 :::tip - 当前内网互联功能仅 ```标准版及以上套餐支持```,个人版暂不支持改功能。 - 个人版如需使用该功能,请前往[套餐管理](https://tcb.cloud.tencent.com/dev?#/env/package-usage/basic)升级您的套餐。 ::: 默认情况下,云托管服务和您的腾讯云账号下的其它资源(如: cvm, mysql, redis 等)内网网络无法互通。如果需要需要访问其它资源,需要为服务开启内网互联功能。 ## 使用场景 通过开启内网互联功能,可以满足以下典型场景下的内网访问需求: - 通过内网访问 Lighthouse 服务。 - 通过内网,跨账号访问对端账号 VPC 内的服务。本账号与对端账号的 VPC 通过 [CCN](https://cloud.tencent.com/document/product/877) 或[对等连接](https://cloud.tencent.com/document/product/553) 打通。 ::::tip 以上两个场景均要求在「服务详情 → 服务设置 → 网络设置」中联动操作: 1. 先开启「**私有网络**」并绑定 VPC; 2. 再关闭「**公网访问**」开关。 两者是联动关系:未先开启「私有网络」时,无法关闭「公网访问」;关闭「公网访问」后,平台默认公网出口关闭,所有出向流量(包括对端 VPC 资源)将走您的 VPC 内网。 > 「关闭公网访问」控制的是服务实例的**出向**流量路径,**不会影响**通过公网域名从外部访问该服务。 :::: ### 开通内网互联功能 开通前提: * 您需要[腾讯云私有网络](https://console.cloud.tencent.com/vpc/vpc?rid=4)创建 VPC 网段。 * 区域需选择上海区域。 * VPC 网段子网掩码尽量使用``` >= 22```。 * 子网子网掩码尽量 ```<= 28```,该子网将被云托管服务占用,请尽量不要在该子网中创建其它资源,以免云托管服务被影响。 开通: * 1、登录[腾讯云托管](https://tcb.cloud.tencent.com/dev?#/platform-run)。 * 2、选择你要开启网络互联功能的服务,点击```服务详情```。 * 3、点击```服务设置```。 * 4、在```访问配置``` 点击编辑功能。 * 5、打开```私有网络```开关。 * 6、选择你新建的 VPC, 及其子网。 * 7、点击保存,等待服务更新完成。 * 8、服务更新完成之后,云托管服务即可访问 VPC 下的资源了。在创建 mysql, redis 等资源时,选择该 VPC 即可。 ## 使用注意事项 * 确保 VPC 网络中具有可分配的 IP 地址,否则可能会由于无法分配 IP,导致云托管服务实例启动失败。 * 如果需要接入 VPC 的服务访问外网,需要确保 VPC 中具备 NAT 网关,且路由配置正确。更多内容可见[NAT 网关说明](https://cloud.tencent.com/document/product/552/83056)。 --- # 云托管/服务指南/服务网络/公网出访 > 当前文档链接: https://docs.cloudbase.net/run/deploy/networking/egress 本页介绍云托管服务实例如何访问公网。 ## 功能介绍 云托管服务实例的**公网出向**流量默认通过云托管平台默认出口访问公网(例如拉取外部依赖、调用第三方 API 等)。在「服务详情 → 服务设置 → 网络设置」中,可以对这一行为进行开关控制: - **公网访问**(开):服务实例的公网出向流量走平台默认出口。 - **公网访问**(关):公网出向流量不再走平台默认出口,需配合 [VPC 及 NAT 网关](https://cloud.tencent.com/document/product/552/83056) 实现公网出站。 「**公网访问**」与「**私有网络**」两个选项**联动**:关闭「公网访问」前,必须先开启「私有网络」并绑定 VPC,否则无法关闭。 > 「关闭公网」控制的是服务实例的**出向**流量路径,**不会影响**通过[公网域名](./public) 从外部访问该服务。如需关闭外部访问入口,请使用[自定义域名](./custom-domains) 或 [公网访问](./public) 中的入口控制能力。 ## 开通/关闭公网访问 您可以在创建服务时,在服务创建页面选择开启/关闭公网访问。 您也可以在服务创建之后,在 `服务详情` -> `服务设置` -> `网络设置` 中操作「**公网访问**」与「**私有网络**」开关。 :::tip 关闭「公网访问」前,请确保「**私有网络**」开关已打开且已绑定 VPC,否则保存时无法关闭。 ::: ## 使用场景 ### 保持公网访问开启 * 服务需要拉取外部依赖包或资源文件。 * 服务需要调用第三方公网 API(如支付、地图、短信等)。 * 服务需要访问公网上的数据库或其他云服务。 ### 关闭公网访问 当业务对网络出站有更严格的合规或可控性要求时,可关闭「公网访问」,让出向流量走 VPC 内网: * 通过内网访问 [Lighthouse](./internal-link) 服务。 * 通过内网,跨账号访问对端账号 VPC 内的服务([CCN](https://cloud.tencent.com/document/product/877) 或[对等连接](https://cloud.tencent.com/document/product/553) 打通)。 ## 注意事项 * 关闭「公网访问」后,服务实例的出向流量将走 VPC 网络,请确保 VPC 中已正确配置 [NAT 网关](https://cloud.tencent.com/document/product/552/83056) 及路由规则,否则服务将无法访问公网。 * 「公网访问」与「私有网络」必须联动配置:关闭「公网访问」必须先开启「私有网络」。 * 「关闭公网」与[公网访问](./public) 是两个不同概念:前者控制**服务实例出向**流量,后者控制**外部入向**访问入口。 * 如需使用固定公网出口 IP,请参考[固定公网出口 IP](./staticip)。 --- # 云托管/服务指南/服务网络/固定公网出口 IP > 当前文档链接: https://docs.cloudbase.net/run/deploy/networking/staticip 在部分需要 IP 白名单验证的业务场景,需要云托管服务能够绑定固定公网出口 IP,例如微信支付、企业微信、公网访问数据库等。 ## 功能介绍 在启用固定公网 IP 功能后,云托管服务在请求外网时,会从一个固定公网 IP 请求到公网上。此时可以在需要绑定 IP 白名单的业务场景中,将云托管服务的公网 IP 添加到 IP 白名单中以便放通请求。 ## 配置方法 在云托管服务的“环境设置”中,编辑并启用“固定公网 IP”能力。 :::tip - 当前固定公网出口 IP 功能仅 ```标准版及以上套餐支持```,个人版暂不支持改功能。 - 个人版如需使用该功能,请前往[套餐管理](https://tcb.cloud.tencent.com/dev?#/env/package-usage/basic)升级您的套餐。 ::: ## 注意事项 - 如果服务同时启用了 VPC 私有网络,此时公网请求将被默认路由到私有网络中,此时需要在 VPC 私有网络中配置 NAT 网关,并使用 NAT 网关上的出口 IP 作为固定出口 IP。更多可见[公网网关](https://cloud.tencent.com/document/product/215/20078) 或 [NAT 网关](https://cloud.tencent.com/document/product/215/4975) 说明。 --- # 云托管/服务指南/服务管理/查看或删除版本 > 当前文档链接: https://docs.cloudbase.net/run/deploy/managing/revisions 当您向服务执行部署或更改服务配置时,系统会创建一个不可变的修订版本。 以下注意事项适用于修订版本: * 您不需要手动删除修订版本,但可以根据需要这样做。 * 未提供的修订版本不会使用任何资源,也不会产生费用。 * 统会自动分配修订版本号。 ## 查看版本列表 您可以在 ```服务详情``` -> ```部署记录``` 中查看服务的版本记录,如需了解版本的详细信息,可点击查看版本详情。 ## 删除版本 删除修订版本的一个常见使用场景是,您想要确保特定修订版本不会被意外使用。 在以下情况下,您无法删除修订版本: * 它能够接收流量, * 它是服务的唯一修订版本, * 它是该服务的最新修订版本。 当您删除某个修订版本后,该修订版本使用的容器映像不会自动从镜像仓库中删除。 您无法撤消修订版本删除操作。 您可以在 ```服务详情``` -> ```部署记录``` 中选择对应的版本进行删除。 ## 版本回退 要回滚到先前的版本,您可以在 ```服务详情``` -> ```部署记录``` 中选择相应的版本进行回退操作。 --- # 云托管/服务指南/服务配置/资源/CPU 及内存配置 > 当前文档链接: https://docs.cloudbase.net/run/deploy/configuring/resources/cpu-limits 本文档介绍如何指定每个云托管实例所使用的 CPU 和内存量。默认情况下,系统会将云托管容器实例配置为 1 核 CPU、2 GiB 内存。您可以按照本页中的说明增加或减少此值。 ## 设置和更新 CPU 设置 默认情况下,系统会将每个实例配置为 1 核 CPU, 2 GiB 内存。您可以将此值更改为本页面中所述的其他值。 ### CPU 和 内存 以下是 CPU 和内存的配置要求: | CPU(核数) | 内存(GiB) | | ---- | ---- | | 0.25 | 0.5 | | 0.5 | 1 | | 1 | 2 | | 2 | 4 | | 4 | 8 | | 8 | 16 | | 16 | 32 | 或者如果您想使用的 CPU 少于 1, 则可以选择 0.25 或 0.5 核。大于 1 的值为整数值。CPU 及内存配置符合以下要求: * CPU 和 内存的比例关系为 1:2, 即 1 个 CPU 需要 2GiB 内存 * 最小 CPU 设置为 0.25 * 最大 CPU 设置为 16 ## 配置 CPU 限制 任何配置更改都会导致新版本的创建。后续的发布新版本也将采用此配置。 你可以在创建服务后,在 ```服务设置``` 中 中修改 CPU 及内存配置。 ## 查看 CPU 配置 你可以在创建服务后,在 ```服务设置``` 中 查看当前的 CPU 及内存配置。 --- # 云托管/服务指南/服务配置/环境/容器端口和入口点 > 当前文档链接: https://docs.cloudbase.net/run/deploy/configuring/environment/containers 本页面介绍如何为云托管服务配置容器端口、入口点命令和参数。 当腾讯云托管启动容器时,它会运行映像的默认入口点命令和默认命令参数。 ## 配置容器端口 任何配置更改都会创建新的版本。后续版本也将自动采用该配置。 您可以在创建服务时,在```容器配置``` -> ```端口``` 指定您的服务端口。注意,该端口必须和您的服务实际访问端口保持一致,否则会导致云托管服务启动失败。 您也可以在服务创建后,在 ```服务配置``` 中修改您的服务端口。 --- # 云托管/服务指南/服务配置/环境/环境变量 > 当前文档链接: https://docs.cloudbase.net/run/deploy/configuring/environment/envs 本页介绍了如何为云托管服务配置环境变量。 ## 设置环境变量 您可以为新服务和现有服务设置环境变量。环境变量会绑定到特定的服务版本中,并且对云托管中的其他服务不可见。 您可以在创建新服务或新建服务后在 ```服务设置``` 中修改环境变量。 ## 在容器中设置默认环境变量 您可以使用 Dockerfile 中的 ENV 语句设置环境变量的默认值: ``` ENV KEY1=VALUE1,KEY2=VALUE2 ``` ## 优先顺序 如果您在容器中设置默认环境变量,并在云托管服务上设置具有相同名称的环境变量,则该服务中设置的值优先。 --- # 云托管/服务指南/服务配置/环境/会话亲和性 > 当前文档链接: https://docs.cloudbase.net/run/deploy/configuring/environment/session-affinity 本页面介绍如何为云托管服务配置如何开启会话亲和性。 通过启用会话亲和性,云托管会尽力将携带特定请求头的请求路由到同一云托管实例上,会话亲和性需要客户端和服务端配合实现。 ## 配置会话亲和性 您也可以在服务创建后,在 ```服务配置``` -> ```访问配置```中 来操作开启和关闭,开启会话亲和性,云托管会尽力将携带特定请求头的请求路由到同一云托管实例上,会话亲和性需要客户端和服务端配合实现。 。 ## 通用客户端 对于一般的客户端,在第一次调用后云托管服务后,客户端需要保存云托管返回的 X-Cloudbase-Session-Id 响应头,并在接下来的请求头中携带相同值的 X-Cloudbase-Session-Id 请求头以路由到相同的云托管示例。默认情况下,X-Cloudbase-Session-Id 的有效期为 4 小时,活跃的 X-Cloudbase-Session-Id 将保持有效,不会被自动清除。 调用示意图如下: ![](https://qcloudimg.tencent-cloud.cn/raw/fbda67b392b85a79360d7bc3846c1a5d/affinity-0.png) ### MCP 客户端 云托管会话亲和性内置了对 Model Context Protocol 协议所声明的 MCP Session 的支持,对于符合协议的 MCP 客户端,云托管会自动处理 Mcp-Session-Id 的会话亲和性保持,无需额外携带请求头。 调用示意图如下: ![](https://qcloudimg.tencent-cloud.cn/raw/356eee08357a259f35fa990ae6d3a95b/affinity-1.png) --- # 云托管/服务指南/服务配置/存储/临时存储 > 当前文档链接: https://docs.cloudbase.net/run/deploy/configuring/storage/local ## 概述 云托管服务在接收请求后,会启动实例并在实例内运行相关代码逻辑。每个实例内都配备了临时存储空间,可用于存储请求处理过程中产生的中间数据。 临时存储的主要特点: - **易于使用**:通过标准文件系统接口访问,无需额外配置 - **临时性**:数据仅在实例生命周期内有效 - **实例隔离**:不同实例间的临时存储相互独立 ## 使用场景 临时存储适用于以下场景: - **文件上传处理**:临时存储上传文件,进行处理后转存至持久化存储 - **请求级缓存**:存储当前请求处理过程中的中间结果 - **日志记录**:记录业务日志,便于问题排查 - **临时计算数据**:存储计算过程中的中间结果 ## 使用限制与注意事项 :::warning 重要提醒 - **数据非持久化**:实例回收或销毁后,临时存储中的所有数据将被清除 - **内存占用**:临时存储实际占用实例的内存空间,过度使用可能导致内存不足 - **性能影响**:大量或频繁的文件操作可能影响应用性能 ::: ### 最佳实践 1. **合理使用空间**:仅存储必要的临时数据,及时清理不再需要的文件 2. **控制文件大小**:避免存储过大的文件,防止出现OOM(内存溢出)错误 3. **定期转移**:对需要长期保存的数据,及时转移至持久化存储 4. **监控内存使用**:关注实例内存使用情况,避免因临时存储导致的内存压力 ## 代码示例 ```javascript // Node.js 示例:使用临时存储处理上传文件 const fs = require('fs'); const path = require('path'); // 将上传的文件保存到临时存储 app.post('/upload', (req, res) => { const tempFilePath = path.join('/tmp', `upload-${Date.now()}.dat`); // 保存到临时存储 fs.writeFileSync(tempFilePath, req.body.fileData); // 处理文件... // 处理完成后删除临时文件 fs.unlinkSync(tempFilePath); res.send('文件处理完成'); }); ``` ## 持久化存储方案 对于需要持久保存的数据,建议使用以下存储方案: - [COS 对象存储挂载](/run/deploy/configuring/storage/cos):适用于需要长期保存的文件数据 - 数据库:适用于结构化数据的持久化存储 --- # 云托管/服务指南/服务配置/存储/挂载对象存储 > 当前文档链接: https://docs.cloudbase.net/run/deploy/configuring/storage/cos ## 概述 云托管服务需要持久化存储时,可以通过挂载云开发对象存储,或腾讯云对象存储来实现。COS 提供了高可用、高可靠、低成本的数据存储服务,将其挂载到云托管实例后,应用可以像使用本地文件系统一样访问和管理存储在 COS 中的数据。 ### 主要优势 - **数据持久化**:与临时存储不同,COS 中的数据在实例销毁后仍然保留 - **高可靠性**:COS 提供 99.999999999% 的数据可靠性 - **多实例共享**:所有实例可访问相同的存储内容,便于数据共享 - **按需付费**:根据实际存储用量和请求次数计费,经济实惠 ## 使用场景 COS 对象存储挂载特别适用于以下场景: - **静态资源存储**:存储网站图片、视频、文档等静态资源 - **用户上传内容**:保存用户上传的文件,如头像、附件等 - **数据备份归档**:存储应用日志、数据备份等需长期保存的信息 - **跨实例数据共享**:在多个实例间共享配置文件、模板等数据 - **大文件处理**:处理超出临时存储容量限制的大型文件 ## 配置步骤 ### 准备工作 1. **获取访问密钥**:前往[腾讯云访问管理控制台](https://console.cloud.tencent.com/cam/capi),创建或获取 SecretID 和 SecretKey > 可以为对象存储创建单独的子用户,并仅授予必要的 COS 访问权限,遵循最小权限原则 ### 详细步骤 1. **配置访问密钥** - 进入[云开发平台控制台](https://tcb.cloud.tencent.com/dev) - 选择「资源连接」>「连接管理」,或在云托管服务的「存储挂载」>「新增连接密钥」 - 添加腾讯云 API 密钥,填入获取的 SecretID 和 SecretKey 2. **启用存储挂载** - 在云托管服务详情页面,选择「存储挂载」 - 点击「启用存储挂载」,选择存储类型为「对象存储」 3. **选择存储桶** - 选择「云开发对象存储」:系统自动填充存储桶信息 - 选择「腾讯云对象存储」:需手动填写存储桶名称 4. **配置挂载路径** - **对象存储挂载路径**:指定要挂载的 COS 目录,默认为根目录「/」 - **实例挂载路径**:指定 COS 在实例中的挂载位置,默认为「/mnt」 5. **确认并保存配置** :::tip 挂载路径限制 实例中以下目录不能被挂载:`/`, `/proc`, `/sys`, `/dev`, `/var/run`, `/app/cert` ::: ## 代码示例 ### Node.js 示例 ```javascript const fs = require('fs'); const path = require('path'); // 假设 COS 挂载在 /mnt/cos 目录 const cosPath = '/mnt/cos'; // 写入文件 app.post('/upload', (req, res) => { const fileName = `file-${Date.now()}.txt`; const filePath = path.join(cosPath, fileName); fs.writeFileSync(filePath, req.body.content); res.send(`文件已保存至: ${fileName}`); }); // 读取文件 app.get('/files/:fileName', (req, res) => { const filePath = path.join(cosPath, req.params.fileName); if (fs.existsSync(filePath)) { const content = fs.readFileSync(filePath, 'utf8'); res.send(content); } else { res.status(404).send('文件不存在'); } }); ``` ### Python 示例 ```python import os from flask import Flask, request, jsonify app = Flask(__name__) # 假设 COS 挂载在 /mnt/cos 目录 cos_path = '/mnt/cos' @app.route('/upload', methods=['POST']) def upload_file(): file_name = f"file-{int(time.time())}.txt" file_path = os.path.join(cos_path, file_name) with open(file_path, 'w') as f: f.write(request.json.get('content', '')) return jsonify({"message": f"文件已保存至: {file_name}"}) @app.route('/files/', methods=['GET']) def get_file(file_name): file_path = os.path.join(cos_path, file_name) if os.path.exists(file_path): with open(file_path, 'r') as f: content = f.read() return content else: return jsonify({"error": "文件不存在"}), 404 ``` ## 注意事项及最佳实践 ### 性能考量 - **缓存策略**:频繁访问的文件考虑先缓存到内存或临时存储 - **批量操作**:尽量批量读写文件,减少 I/O 操作次数 - **文件大小**:对于大文件操作,考虑分片处理 ### 多实例协作 - **路径隔离**:不同实例操作时,建议使用唯一标识(如实例ID)创建子目录 - **文件锁**:读写共享文件时,实现适当的锁机制避免冲突 - **原子操作**:使用原子重命名等技术确保文件操作的完整性 ```javascript // 安全的文件更新示例(Node.js) const fs = require('fs').promises; const path = require('path'); async function safelyUpdateFile(filePath, newContent) { const tempPath = `${filePath}.tmp`; // 先写入临时文件 await fs.writeFile(tempPath, newContent); // 原子性地重命名替换原文件 await fs.rename(tempPath, filePath); } ``` ### 安全性 - **权限控制**:定期检查 COS 存储桶的访问权限设置 - **敏感数据**:避免在 COS 中存储未加密的敏感信息 - **公开访问**:使用云开发对象存储时,注意公有读权限可能导致文件被外部访问 ### 成本优化 - **生命周期管理**:为不常用数据配置生命周期规则,自动转换存储类型或删除 - **按需挂载**:只在需要时挂载 COS,不需要时可以卸载以减少资源占用 ## 相关资源 - [COS 对象存储产品文档](https://cloud.tencent.com/document/product/436) - [访问密钥管理](https://cloud.tencent.com/document/product/598/40488) --- # 云托管/服务指南/服务配置/扩缩容/运行模式与扩缩容 > 当前文档链接: https://docs.cloudbase.net/run/deploy/configuring/autoscaling/about-instance-autoscaling 本文档介绍云托管的运行模式及扩缩容机制,帮助您选择最适合业务场景的配置方案。 ## 运行模式概览 云托管提供以下几种运行模式,满足不同的业务需求: - [始终自动扩缩容](#始终自动扩缩容):根据负载自动调整实例数量,最大程度节约资源 - [持续运行](#持续运行):保持固定数量的实例,适合流量稳定的场景 - [白天持续运行,夜间自动扩缩容](#白天持续运行夜间自动扩缩容):工作时间固定实例,非工作时间自动扩缩 - [自定义](#自定义):灵活配置自动扩缩容和定时扩缩容策略 - [手工启停实例](#手工启停实例):完全手动控制实例数量 ## 运行模式详解 ### 始终自动扩缩容 在此模式下,云托管服务会根据实际负载情况自动调整实例数量: - **扩缩容范围**:实例数量可在 0-10 之间自动调整 - **触发指标**:可选择 CPU 使用率、内存使用率或两者同时作为扩缩容触发条件 - **优势**:最大程度节约资源,自动应对流量波动,降低运营成本 此模式特别适合流量波动较大或不可预测的业务场景。 ### 持续运行 持续运行模式下,服务会保持固定数量的实例持续运行,不会自动扩缩容。 **适用场景**: - 服务流量相对稳定,无明显波峰波谷 - 需要保证服务始终处于热启动状态,避免冷启动延迟 - 对服务可用性要求极高的核心业务 在此模式下,您需要根据业务峰值预估所需实例数量,确保服务稳定运行。 ### 白天持续运行,夜间自动扩缩容 此模式结合了持续运行和自动扩缩容的优势: - **白天时段**(8:00-24:00):保持指定数量的实例持续运行 - **夜间时段**(0:00-8:00):启用自动扩缩容,实例可缩至 0 适合工作时间流量较高、非工作时间流量显著降低的业务场景,如企业内部系统、办公应用等。 ### 自定义 自定义模式提供最灵活的配置选项,包括: - **自动扩缩容**: - 配置实例的最大及最小数量 - 选择 CPU 或内存使用率作为触发条件 - 设置触发阈值和检测周期 - **定时扩容**: - 指定时间段内保持最小实例数 - 定时扩容的实例数需大于自动扩缩容的最小实例数 - 在定时期间仍可自动扩容,但不超过最大实例限制 此模式适合有明确流量模式且需要精细控制资源的场景,如电商促销、定期任务处理等。 ### 手工启停实例 手工启停模式下,实例完全由人工控制,不会自动扩缩: - 实例不会因无请求而自动缩容 - 实例不会因负载增加而自动扩容 - 所有实例变更都需通过控制台或 API 手动操作 **操作方式**: - **启动实例**: - 通过控制台版本列表中的"启动实例"操作 - 通过 API 调用启动实例 - 控制台的每次操作启动一个实例,需多次操作启动多个实例 - **停止实例**: - 通过控制台实例列表中的"停止实例"操作 - 通过 API 调用停止实例 - 可选择指定实例进行停止 此模式适合需要完全控制实例生命周期的场景,如开发测试环境或特殊业务需求。 :::warning 实例资源使用 手工启停实例的情况下,如果实例启动后遗忘了停止实例,可能会产生不必要的资源浪费,会产生较多的计费开销。 ::: ## 扩缩容机制详解 ### 实例扩容过程 实例扩容分为两个阶段: 1. **从 0 到 1 扩容(冷启动)**: - 触发条件:服务完全缩容后收到新请求 - 过程:启动一个新实例,实例就绪后开始处理请求 - 耗时因素:平台资源状态、镜像大小、业务代码启动时间等 - 用户体验:首次请求可能有一定延迟 2. **从 1 到多实例扩容**: - 触发条件:已有实例的平均 CPU/内存使用率达到或超过配置的扩容阈值 - 过程:启动新实例,新实例就绪后加入负载均衡 - 持续监测:如检测周期后仍超过阈值,将继续扩容 - 限制:实例数量不超过配置的最大值 ### 实例缩容机制 - **触发条件**:实例无访问、无流量,闲置 10 分钟 - **缩容行为**:回收并销毁闲置实例 - **最小实例**:当配置了最小实例为 0 时,可能缩容至无实例状态 - **后续请求**:全部缩容后的新请求将触发冷启动过程 ### 实例销毁说明 - 实例在缩容后将被完全销毁 - 实例内的临时文件、内存数据等将一并清除 - **重要提示**:请确保您的应用是无状态的,所有持久化数据应存储在外部存储服务中 ## 选择合适的运行模式 | 运行模式 | 适用场景 | 成本效益 | 响应速度 | |---------|---------|---------|---------| | 始终自动扩缩容 | 流量波动大、不可预测 | 最优 | 可能有冷启动延迟 | | 持续运行 | 流量稳定、高可用要求 | 固定较高 | 最佳(无冷启动) | | 白天持续运行,夜间自动扩缩容 | 工作时间集中型应用 | 较优 | 工作时间最佳,非工作时间可能有延迟 | | 自定义 | 有明确流量模式、需精细控制 | 可优化 | 可根据配置优化 | | 手工启停实例 | 开发测试、特殊业务需求 | 完全可控 | 取决于人工操作及时性 | --- # 云托管/服务指南/服务鉴权/允许公开访问 > 当前文档链接: https://docs.cloudbase.net/run/deploy/authenticating/public 默认情况下,云托管服务为公开访问(即不需要任何的权限验证),任何用户都可以通过访问域名来访问服务。 如果您希望只有部分用户可以访问云托管服务,可以参考[对用户开启身份认证](./end-users)。 --- # 云托管/服务指南/服务鉴权/服务到服务 > 当前文档链接: https://docs.cloudbase.net/run/deploy/authenticating/service-to-service 服务到服务之间如果采用内网访问则不需要任何的权限验证操作。 --- # 云托管/服务指南/服务鉴权/对用户进行身份校验 > 当前文档链接: https://docs.cloudbase.net/run/deploy/authenticating/end-users 默认云托管不提供鉴权服务,如果客户没有实现鉴权功能,则在某些情况下存在安全问题。如果您的服务要处理来自客户的请求,则最佳实践是只让允许的用户进行访问。 您可以通过[自定义域名](../networking/custom-domains)功能,并根据要求配置相关 path, 同时为 path 开启```鉴权```功能,来设置云托管服务的访问权限。 ## 用户创建 * 1、登录腾讯云托管 * 2、在导航菜单```扩展能力``` 中选择 [云后台](https://tcb.cloud.tencent.com/dev?envId=lowcode-9gms1m53798f7294#/cloud-admin), 点击前往云后台 * 3、在云后台中选择 ```用户管理``` 添加用户, 设置用户高名称和密码 ## 用户权限配置 云托管服务默认只能以下几种角色客户访问: * 默认内部用户 * 默认外部用户 * 自定义策略访问 注意: 其中在 ```用户创建``` 步骤创建的客户为 ```默认内部用户```。 如果不想某些用户访问,可以给客户解绑 ```默认内部用户``` 和 ```默认外部用户``` 角色 ### 自定义策略 * 1、登录腾讯云托管 * 2、在导航菜单```扩展能力``` 中选择 [云后台](https://tcb.cloud.tencent.com/dev?envId=lowcode-9gms1m53798f7294#/cloud-admin), 点击前往云后台 * 3、在云后台中选择 ```权限控制```, 在权限控制页面选择 ```策略管理```, 如果没有该选项,可以刷新页面。 * 4、点击```新增自定义策略```,填写表单: * 策略标识: 英文填写 * 策略名称: 中英文填写 * 策略内容: 我们以允许 ```/api``` 访问为例, 如下在 action 配置 ```/api``` 路径即可。 ``` { "statement": [ { "action": "cloudrun:/api", "resource": "*", "effect": "allow" } ], "version": "1.0" } ``` * 5、配置完自定义策略之后,将改策略关联到需要访问该资源的角色上,然后将该角色中关联需要访问的用户即可。 ## 获取用户 Token 参考[用户名密码登录](https://docs.cloudbase.net/http-api/auth/auth-sign-in) OpenAPI 获取用户登录 token。 ## 通过自定义域名访问云托管 我们以设置路由为 ```/api``` 开头的服务为例。 参考[自定义域名](../networking/custom-domains)功能,在域名关联资源时,```鉴权开关```选择打开, 路径透穿根据实际情况填写,比如我们 api 路径为 ```/api```, 我们不开启路径透传的情况下,我们使用 ```/api/users``` 访问,后端服务收到的 url 为 ```/users```, 反之开启情况下收到的为 ```/api/users```。 我们配置完成之后,通过以下方式访问: ``` curl -H "Content-Type: application/json" -H "Authorization: Bearer <获取到的Token>" https://<自定义域名>/api/users ``` 即可获取到访问结果。 ## 通过其他登录方式获取 Token 可以参考[用户登录设置](https://docs.cloudbase.net/http-api/auth/%E7%99%BB%E5%BD%95%E8%AE%A4%E8%AF%81%E6%8E%A5%E5%8F%A3)。 ## 获取用户信息 我们会将客户的 token 透穿给后端,后端可以通过获取请求 Header 中 ```Authorization``` 字段获取到请求 Token, 注意去除 Bearer 字段。 然后通过 jwt 解码 token, 解码后 ```user_id``` 字段为用户的唯一标识。解析后 payload 内容如下: ``` { "iss": "", "sub": "22332323", "aud": "", "exp": 1750073415, "iat": 1750066215, "at_hash": "", "scope": "", "project_id": "", "provider_type": "username", "meta": { "wxOpenId": "", "wxUnionId": "" }, "user_id": "1934543672625225729", "user_type": "internal" } ``` 如需查询客户详细信息,请参考 [用户详情](https://docs.cloudbase.net/lowcode/manage/auth#%E7%94%A8%E6%88%B7%E8%AF%A6%E6%83%85)。 --- # 云托管/运维指南/日志 > 当前文档链接: https://docs.cloudbase.net/run/maintain/log 本文介绍如何在开发平台上配置及查询日志。 ## 日志采集 云托管服务默认采集容器中的标准输出内容,但采集内容可以配置。 通过修改服务设置中的日志采集路径,可以采集多种内容: * stdout : 仅采集标准输出 * stdout,stderr :采集标准输出和错误输出 * stdout,/path/to/file :采集标准输出和指定路径的日志文件 ## 日志检索 1. 进入[开发平台](https://tcb.cloud.tencent.com/dev),按需要切换到指定的环境。 2. 进入`计算` - `云托管` 服务。 3. 选择您需要查看的服务,单击服务名称进入服务详情页面。 4. 单击「日志」切换到日志选项卡,即可查看当前服务、服务下所有版本的日志数据。 ## 日志说明 ### 服务访问日志、访问产生的业务日志 服务访问日志包含了请求服务时的请求日志,以及请求时带来的业务日志。 服务请求日志使用 accesslog 的 logType 进行标识,内容包含了请求路径、请求方法、请求开始时间、响应时间、耗时、响应状态等信息 跟随请求日志的有相关业务日志,即在业务代码中打印的内容;通常如果是在请求处理中增加的日志打印,将会出现在业务日志中。 通过点击服务请求日志操作中的业务日志,即可以查看到当前请求对应的业务日志。 ### 标准输出日志 未跟随服务访问日志打印的,例如服务启动时、或在后台运行时产生的日志,均可以再标准输出日志中查询到。 切换到标准输出日志分类,即可查询到具体日志内容。 ### 服务版本的日志区分 服务版本实际运行的实例均会带有版本标识,类似 001、002、003。 在日志中同样可以根据版本标识进行筛选,查询具体版本的日志。 --- # 云托管/运维指南/监控 > 当前文档链接: https://docs.cloudbase.net/run/maintain/monitoring 本文介绍如何查看服务相关的监控数据。 ## 操作步骤 1. 进入云托管后,通过服务列表,选择需要查询的服务。 2. 在服务页面可以查询到基础监控概览。可以通过`更多详情`,进入运维管理的监控页面。通过直接选择环境的运维管理,也可以切换到监控界面查询云托管监控信息。 3. 在运维管理的运维仪表盘中,监控项目选择云托管,展开选择具体需要查询的服务。 4. 可以在下拉选择列表中,选择具体服务维度的监控,也可以进一步选择具体服务的版本,查询到版本监控。 ## 监控字段说明 - 在服务/版本维度监控选项卡中,顶部时间筛选器对页面内所有监控数据生效。例如:在顶部选择时间"7 天",则统计卡片、统计曲线,均展示过去 7 天的数据。 - 监控曲线图中的“粒度”指每个监控数据对应的单位时间,会随着所选时间区间变化,时间跨度长则粒度粗。**曲线图上所有数值需配合粒度解读**。 ### 服务监控 - **调用次数**:单位时间内服务收到的请求次数总和(包括通过服务域名、HTTP 网关产生的请求)。 - **响应时间(毫秒)**:单位时间内服务收到的所有请求的响应时间取平均值。 - **QPS**:单位时间内服务平均每秒处理的请求数。 - **错误响应**:单位时间内请求服务失败,返回 404、500 的 HTTP 错误的次数。 - **CPU 用量**:单位时间内服务所有版本 CPU 资源消耗之和,单位为(核 x 小时)。 - **内存用量**:单位时间内服务所有版本内存资源消耗之和,单位为(GiB x 小时)。 - **实例个数**:服务所有版本的实时实例个数之和,在单位时间内取平均值。 - **异常实例个数**:服务所有版本的实时实例状态不正常个数之和,在单位时间内取平均值。非正常运行的实例均会标记为异常实例,其中也会包括启动中的实例。 ### 版本监控 - **调用次数**:单位时间内版本收到的请求次数总和(包括通过服务域名、HTTP 网关产生的请求)。 - **响应时间(毫秒)**:单位时间内版本收到的所有请求的响应时间取平均值。 - **QPS**:单位时间内版本平均每秒处理的请求数。 - **HTTP 错误**:单位时间内请求版本失败返回 HTTP 错误的次数。 - **CPU 用量**:单位时间内版本 CPU 资源消耗,单位为(核 x 小时)。 - **内存用量**:单位时间内版本内存资源消耗之和,单位为(GiB x 小时)。 - **CPU 使用率**:版本所有实例 CPU 使用率平均值,在单位时间内再取平均值。可作为设置扩缩容条件的参考值。 - **内存使用率**:版本所有实例内存使用率平均值,在单位之间内再取平均值。 - **实例个数**:版本的实时实例个数,在单位时间内取平均值。 - **异常实例个数**:版本的实时实例状态不正常个数,在单位时间内取平均值。非正常运行的实例均会标记为异常实例,其中也会包括启动中的实例。 --- # 云托管/运维指南/使用 Webshell 登录实例 > 当前文档链接: https://docs.cloudbase.net/run/maintain/webshell 本文为您介绍云托管的 Webshell 功能,以及如何使用 Webshell 完成基本的运维需求。 ## 操作背景 容器是一个暂态的、供服务运行的环境,您在使用云托管时只需关注自己的服务,不需要涉及对容器的直接操作,包括创建、配置、更新、重启、销毁等等。但为了方便进行线上问题定位、排查,特别是调试关于代码本身的问题,云托管在控制台提供了简易版 Webshell,供您查看并调试自己的容器。 :::caution 注意 通过 Webshell 直接操作容器可能会带来风险。云托管只是为您提供了一个直达容器的途径,您在 Webshell 中做的一切操作,云托管都无法感知、无法管控。 - 若您在 Webshell 中的操作引起容器 OOM,可能会带来服务中断。 - 若您在 Webshell 中直接修改了容器配置,可能会导致与云托管中记录的容器配置不一致,引起后续操作混乱。 ::: ## 步骤 1:进入开发平台 进入[开发平台](https://tcb.cloud.tencent.com/dev),按需要切换到指定的环境。 进入`计算` - `云托管` 服务。 ## 步骤 2:进入服务详情页面 单击服务名称进入服务详情页面。 ## 步骤 3:进入实例详情页 单击服务基本信息中的实例详情查看,打开实例详情列表。 ## 步骤 4:进入 Webshell 页面 单击需要调试的容器对应的「Webshell」,进入 Webshell 管理页面。 :::tip 提示 根据版本的“副本个数”、“扩缩容条件”和当前版本流量情况,您的版本下可能有多个实例(容器)。同一个版本下所有的容器都是根据“版本配置”创建出来的,配置信息完全一致,因此绝大多数情况下您任意选择一个容器进入 Webshell 都可对当前版本进行调试和问题定位。但不排除某些特殊情况下,仅有个别容器状态异常。 ::: :::caution 注意 如果采用的是精简容器,容器内没有 `sh` 或 `bash` 工具,会导致启动 shell 失败。您可以更新包含了 shell 能力的基础镜像,或在 dockerfile 中增加 shell 安装。 ::: ## 步骤 5:调试操作 您可以开始对自己的服务进行调试。 --- # 云托管/函数型开发框架/介绍 > 当前文档链接: https://docs.cloudbase.net/cbrf/intro `云托管函数框架` 是云开发推出的支持在 `云托管` 上运行 `函数式代码` 的一种 函数托管模式,因此也可以称为 `云托管-函数模式`。 `云托管函数框架` 本质上是 `云托管` 服务的一种开发和运行模式,支持在 `云托管` 上开发和运行 `函数式代码`,而不需要像开发传统容器服务一样开发一个完整的服务。 `云托管函数框架` 可以让容器服务开发像开发云函数一样简单。 技术原理:`云托管函数框架` = `云托管` + `函数框架 (@cloudbase/functions-framework)` + `函数代码`。`函数框架` 通常也称为 `运行时(Runtime)`,可以加载云函数并运行。`云托管` 提供云端容器化运行环境。 --- Q:**`云托管函数框架` 解决什么问题?** 1. 相比 `云托管`,`云托管函数框架` 具备更简单的开发方式 2. 相比 `云函数`,`云托管函数框架` 可支持更多场景能力 `云托管函数框架` 在一定程度上结合了两者的优点,开发者可以根据自己的业务场景进行选择 --- Q:**如何选择是否需要 `函数代码` 部署到 `云托管`?** 1. 如果业务场景需要支持 `WebSocket 长连接`、`Server-Sent Events (SSE)`、`表单提交`、`文件上传下载`、`流式响应`、`单实例多函数`、`函数间共享内存数据` 等特性,需要使用 `云托管函数框架` 2. 如果业务场景对响应耗时更高要求,更好的用户体验,推荐使用 `云托管函数框架` 3. 如果需要在函数中连接 `Redis`、`MySQL` 等 `数据库`,推荐使用 `云托管函数框架` 4. 如果需要在函数中对接 `Kafka`、`Pulsar` 等 `消息队列`,需要使用 `云托管函数框架` 5. 如果业务场景中需要进行长耗时异步任务处理,推荐使用 `云托管函数框架` 6. 如果需要更好的 `日志查询能力`,推荐使用 `云托管函数框架` 7. 如果想获得更好的开发调试体验,推荐使用 `云托管函数框架` 8. 目前 `云托管函数框架` 还在不断迭代中,如某些功能特性在 `云托管函数框架` 中尚未支持,推荐使用 `云函数` 进行开发 > 函数框架 `@cloudbase/functions-framework` 提供了更好的能力支持 --- Q:**`云托管函数框架` 是否需要理了解云托管或容器** 1. 需要了解 `云托管`,例如需要决定选择 `实例规格`、`实例数量` 等,`云托管` 本身也比较简单,也很容易上手 2. 不需要理解 `容器`,开发者不需要了解容器的概念和原理以及如何使用容器等 3. 总体上来说,相比 `云函数` 并没有需要学习和了解很多新内容 --- Q:**现有云函数代码如何迁移到 `云托管`?** 1. 现有云函数代码进行简单的改造后可以运行到云托管环境中 2. 除了云函数代码本身进行改造外,客户端侧代码也需要进行简单的改造 > 因 `函数框架 (@cloudbase/functions-framework)` 与 `云函数` 运行时机制存在一定差异,因此可能需要进行一定的改造。 --- Q:**基于云托管函数框架编写的函数代码是否可以运行在其他环境?** 1. 可以,通过 `函数框架 (@cloudbase/functions-framework)` 即在任何支持 `node.js` 运行的环境中运行函数代码 2. 支持 本地运行,主要针对开发调试场景 3. 支持 部署在传统主机环境中,例如云服务器环境中 4. 支持 部署在容器环境中,例如 `Docker` 环境中 > 相比运行在云托管环境中,部署在其他环境中时,需要自行解决构建部署等问题。 --- 更多详细的差异对比见 [对比其他方案-对比云函数](../cbrf/vs?#对比云函数) 文档。 > 注意:云托管函数框架 和 云函数 在调用方式、调用链路、和运行时机制上是不同的,现有 云函数代码 在 云托管函数框架 上运行需要进行简单的改造。 ## 相关链接 - [快速开始](./quickstart.md) - [函数编写指南](./how-to-writing-functions-code.md) - [本地开发云函数](./how-to-develop-local.md) - [在线开发云函数](./how-to-develop-online.md) - [如何调试函数代码?](./how-to-debug-functions-code.md) - [如何调用TCB云开发的其他能力?](./how-to-invoke-tcb.md) - [开发函数型智能体](./develop-cbrf-agent.md) - [函数代码示例](./example.md) - [与其他方案对比](../cbrf/vs.md) - [CLI工具](./cli.md) - [@cloudbase/functions-framework](https://www.npmjs.com/package/@cloudbase/functions-framework) --- # 云托管/函数型开发框架/快速开始 > 当前文档链接: https://docs.cloudbase.net/cbrf/quickstart 本文将介绍如何使用云托管函数框架快速创建一个服务 ,并从小程序端发起调用。 ## 通过模板创建云托管函数 1. 在云托管界面,选择【通过模版部署】-》【更多模版】 ![](./images/quickstart-template-sample.png) 2. 在所有模版中,选择【函数型开发框架示例】 ![](./images/quickstart-template-all.png) 3. 填入【服务名称】后,点击屏幕最下方的【部署】 ![](./images/quickstart-template-build.png) 4. 部署成功后,会显示状态为`正常` ![](./images/quickstart-build-success.png) 5. 点击部署成功页面左上角的箭头,可以进入服务的详情页,完成创建过程 ![](./images/quickstart-overview.png) 同时在创建时,可以通过模板详情,了解后续如何调用当前函数。 当前模板内置了的可调用的函数,可以参考[模板项目](https://github.com/TencentCloudBase/func-v2-template/tree/main)。 ## 小程序端调用 可以通过如下方式,在小程序中调用函数: ```js // 调用必填环境id,不能为空 const c1 = new wx.cloud.Cloud({ resourceEnv: '环境id' //填入云开发环境 id }) await c1.init() const r1 = await c1.callContainer({ path: '/', // 默认函数对应的请求路径 header: { 'X-WX-SERVICE': 'func-template-arch', // 填入创建时的函数名称 func-template-arch }, // 其余参数同 wx.request method: 'GET', }) console.log(r1) //输出 Hello world! const r2 = await c1.callContainer({ path: '/echo', // echo 函数对应的请求路径 header: { 'X-WX-SERVICE': 'func-template-arch', // 填入创建时的函数名称 func-template-arch }, // 其余参数同 wx.request method: 'POST', data: { a: 1, b: 2 } }) console.log(r2) //输出具体请求及时间 ``` 可以在调试器中查看到请求结果如下类似: ![](./images/quickstart-call.png) --- # 云托管/函数型开发框架/函数编写指南 > 当前文档链接: https://docs.cloudbase.net/cbrf/how-to-writing-functions-code 本文介绍如何编写基于 `@cloudbase/functions-framework` 的函数代码,如何进行错误处理,以及函数结构、出入参等基本信息,并提供一系列的示例以供参考。 ## 快速开始 ### 第一步:编写函数 1. 在本地创建一个空的文件夹,作为项目的根目录。 2. 新建 `index.js` 文件,作为函数的入口文件。 3. 在`index.js` 文件中,填写以下内容: ```js exports.main = function (event, context) { const { httpContext } = context; const { url, httpMethod } = httpContext; return `[${httpMethod}][${url}] Hello world!`; }; ``` 以上即为一个简单的函数代码示例,函数的入口函数为 `main`,函数的输入参数为 `event` 和 `context`,函数的返回值为 `字符串`。 - 更多函数示例可参考: - https://docs.cloudbase.net/cbrf/example - https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudrunfunctions - 模板代码可参考: - JavaScript: https://github.com/TencentCloudBase/func-v2-template - TypeScript: https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudrunfunctions/ts-multiple-functions - 如何调试函数代码可参考: - https://docs.cloudbase.net/cbrf/how-to-debug-functions-code 当前代码为简单的函数示例,`云托管函数框架` 在框架层面支持 `单实例多函数及函数路由`,即多个函数跑在同一个函数实例上,具体可参考:[单实例多函数及函数路由](#单实例多函数及函数路由) ### 第二步:运行函数 函数代码编写完毕后,既可以将函数运行起来,之后便可以通过 HTTP 请求来调用函数。 运行函数的方式有两种: #### 1. 通过命令行工具本地运行,通常在开发调试阶段 首先需要全局安装 `tcb-ff` 命令行工具: ```sh npm install -g @cloudbase/cli ``` 在函数代码根路径下执行以下命令,即可本地运行函数: ```sh tcb cloudrun run ``` #### 2. 部署在云托管环境中 可在云开发平台创建 `云托管函数框架` 服务,部署函数代码。 将代码部署到 `云托管函数框架` 可跳转 [云开发平台](https://tcb.cloud.tencent.com/dev) ### 第三步:调用函数 #### 1. 调用本地运行的函数 本地可以通过 `curl` 命令或其他 HTTP 请求工具来调用本地运行的云函数。 函数框架加载并运行云函数后,默认会监听 `http(3000)` 端口,可以通过 `http` 调用触发云函数执行。 ```sh # 发送 GET 请求 curl -v -XGET http://localhost:3000/path/to/xxx # 发送 POST 请求 curl -v -XPOST 'http://127.0.0.1:3000/path/to/xxx \ -H 'Content-Type: application/json' \ --data-raw '{"name":"xxxxx"}' # 发送 PUT 请求 curl -v -XPUT 'http://127.0.0.1:3000/path/to/xxx \ -H 'Content-Type: application/json' \ --data-raw '{"name":"xxxxx"}' curl -v -XDELETE 'http://127.0.0.1:3000/path/to/xxx ``` 智能体函数请求示例: ```sh curl -XPOST 'http://127.0.0.1:3000/v1/aibot/bots/ibot-xxxx/send-message' \ -H 'Accept: text/event-stream' \ -H 'Content-Type: application/json' \ --data-raw '{"name":"xxxxx"}' ``` 如上请求示例,`@cloudbase/functions-framework` 支持 `GET` 、`POST`、`PUT`、`DELETE` 等请求方式,同时也支持指定路径。 #### 2. 调用云端环境中运行的函数 因为 `云端环境中运行的函数` 是基于云托管的,因此,可以通过调用云托管服务的方式来调用函数。 1. 通过 `服务域名` 进行调用,在 `云托管服务页面` 可以查询到域名信息。 2. 通过 `HTTP访问服务` 进行调用,在 `访问服务页面` 配置路由后关联到对应资源后,即可进行调用。 3. 通过 `开放API` 进行调用,可参考:https://docs.cloudbase.net/http-api/cloudrun/cloudrun-post。 4. 通过 `SDK` 进行调用 1. 服务端 `@cloudbase/node-sdk` 调用,可参考: 1. `callFunction`:https://docs.cloudbase.net/api-reference/webv3/functions 2. `callContainer`:https://docs.cloudbase.net/api-reference/server/node-sdk/cloudrun 2. 浏览器 `@cloudbase/js-sdk` 调用,可参考: 1. `callFunction`:https://docs.cloudbase.net/api-reference/webv3/functions 2. `callContainer`:https://docs.cloudbase.net/api-reference/webv3/cloudrun 3. 微信小程序可通过 `wx.cloud.callContainer` 方式进行调用,见:[Cloud.callContainer](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/container/Cloud.callContainer.html) ##### 2.1 微信小程序中通过 `callContainer` 调用示例 ```ts // 容器调用必填环境id,不能为空 const c1 = new wx.cloud.Cloud({ resourceEnv: "环境id", }); await c1.init(); const r = await c1.callContainer({ path: "/", // 填入业务自定义路径 header: { "X-WX-SERVICE": "xxx", // 填入服务名称 }, // 其余参数同 wx.request method: "POST", }); console.log(r); ``` 通过该方式调用,您可以省去配置自定义域名、证书等操作,快速调用函数,并获得微信私密链路的安全性提升。 更多信息可参考 `wx.cloud.callContainer` 相关文档: - [wx.cloud.callContainer 技术原理和使用指南](https://developers.weixin.qq.com/miniprogram/dev/wxcloudrun/src/practice/call.html) - [Cloud.callContainer](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/container/Cloud.callContainer.html) ##### 2.2 通过 `服务域名` 进行调用 在 `云托管服务页面` 可以查询到域名信息,以下示例为默认域名: ```sh curl -v -XGET https://{}.run.tcloudbase.com/ ``` ##### 2.3 通过 `HTTP访问服务` 进行调用 在 `访问服务页面` 配置路由后关联到对应资源后,即可进行调用,以下示例为默认域名: ```sh curl -v -XGET https://{}.app.tcloudbase.com/ ``` ## 了解函数 本部分内容主要介绍函数的 `基本结构`、`输入输出`、`错误处理`、`函数日志` 等内容。 云开发提供了 `入口main函数` 的 `TypeScript` 类型定义 `@cloudbase/functions-typings@v-1` 辅助代码编写,可参考:[函数类型定义](#使用-typescript-编写函数代码) ### 函数的输入参数 `event` 和 `context` 函数的基本结构如下: ```js exports.main = function (event, context) { // 函数逻辑 }; ``` 或 ```js exports.main = async function (event, context) { // 函数逻辑 }; ``` 该文件导出一个 `main` 函数,该函数即为函数的入口方法,当请求到达时,该方法会作为代码执行的起点。该方法接受两个固定的参数 `event` 和 `context`,分别代表函数的输入和上下文。 其中,`event` 为函数的触发事件,在当前的 HTTP 访问场景下,可以认为是 HTTP 请求体,例如 `POST` 请求的 `body`,`multipart/form-data` 请求的 `form-data`,`PUT` 请求所传输的二进制文件等。如果 HTTP 请求没有传递请求体,`event` 将会是一个空对象。 `context` 为函数执行的上下文信息,包含以下属性和方法: | Name | 类型 | 含义 | | --------------- | ------------------------- | ----------------------------------------------- | | eventID | `string` | 事件唯一标识,用于关联上下文 | | eventType | `string` | 事件类型,当前固定为 `http` | | timestamp | `number` | 请求时间戳 | | httpContext | `httpBasis` | HTTP 请求的相关信息 | | extendedContext | `Record` | 扩展的上下文信息,平台提供,包含 环境相关信息 | | sse() | `ISeverSentEvent` | 返回用于发送 `Server-Sent Event` 格式响应的对象 | `httpBasis` 定义如下: | 名称 | 类型 | 含义 | | ---------- | --------------------- | ------------------------------ | | url | `string` | 本次请求的完整 URL | | httpMethod | `string` | 本次请求的 HTTP 方法,如 `GET` | | headers | `IncomingHttpHeaders` | 本次请求的 HTTP 头部 | 其中,从 `extendedContext` 中可以获得以下属性: ```ts // import { TcbExtendedContext } from '@cloudbase/functions-typings' interface TcbExtendedContext { envId: string; // 环境 ID uin: string; // 请求的 UIN source: string; // 请求来源,如 wx serviceName: string; // 服务名称 serviceVersion: string; // 服务版本 authMethod?: string; // 认证方式 UNAUTHORIZED | CAM_TC3 | TCB_OAUTH2_B | TCB_OAUTH2_C | WX_SERVER_AUTH userType?: string; // 用户类型 NONE | B_SIDE_USER | C_SIDE_USER isAdministrator?: boolean; // C 端用户(C_SIDE_USER)是否为管理员 accessToken?: string; // 调用请求时的 AccessToken userId?: string; // 请求的用户 ID tmpSecret?: { // 临时凭证 secretId: string; // secretId secretKey: string; // secretKey token: string; // token }; wechatContext?: { // 微信上下文信息 callId: string; // 微信调用 ID source: string; // 请求来源,如 wx appId: string; // 访问小程序的 AppID openId: string; // 访问用户的 OpenID unionId: string; // 访问用户的 UnionID fromOpenId?: string; // 环境资源共享时的 fromOpenID fromUnionId?: string; // 环境资源共享时的 fromUnionID fromAppId?: string; // 环境资源共享时的 fromAppID }; } ``` 函数支持 `SSE(Server-Sent Event)` 格式的响应,通过调用 `sse()` 即可以切换到 `SSE` 模式并得到 `ISeverSentEvent` 实例。 ```ts interface ISeverSentEvent { setEncoder(encoder: TextEncoder): void; send(event: SseEvent): boolean; end(msg: SseEvent): void; } interface SseEvent> { data: T; comment?: string; event?: string; id?: string; retry?: number; } ``` 可以通过 `send()` 方法来发送 SSE 响应,例如: ```js const { sse } = context; sse.send({ data: "Hello world!", }); ``` 更多 `SSE` 使用示例可参考:[使用-server-sent-event-推送消息](./example#使用-server-sent-event-推送消息) 当函数执行完毕(即从入口函数返回)时,返回值将会被直接返回给客户端,不会进行额外的序列化处理。 ### 函数的输出(即返回值) 函数执行最后的返回值将作为最终返回值返回给客户端,此时,将使用 `默认的HTTP状态码`(如正常返回时`200`、未带有响应体时`204`、抛出异常时`500`)和 `默认的HTTP响应头` 返回给客户端。 函数支持 `同步`、`异步` 模式,支持 `普通类型` | `Promise<普通类型>` 的返回值。可以返回 `Buffer | Stream` 类型的返回值,实现下载文件的能力。 > 注意:函数返回后,仍可能会有异步操作在后台执行。 函数的返回值可以支持 `普通响应` 和 `集成响应` 两种形式。 函数框架通过返回值的格式来区分两种类型的响应,如果返回值格式为 `{statusCode: number, headers: { [key: string]: string | string[] }}` 即会被判定为 `集成响应`,其他均为 `普通响应`。 #### 普通响应 `普通响应` 直接将 `返回值` 完整的返回给客户端。 #### 集成响应 通过集成响应,可以自定义 HTTP 响应的结构,包括响应状态码、头部字段等,可以采用集成响应的返回结构。集成响应的定义如下: ```ts interface IntegrationResponse { statusCode: number; headers: { [key: string]: string | string[] }; body?: unknown; } ``` `statusCode` 为 HTTP 响应状态码,`headers` 为自定义的 HTTP 响应头部,`body` 为 HTTP 响应体。例如返回下面的集成响应: ```js exports.main = function (event, context) { return { statusCode: 200, headers: { "Content-Type": "text/html", }, body: { message: "

Hello world!

", }, }; }; ``` 将可以在浏览器中渲染出 HTML。 ### 错误处理 函数运行过程中可能或抛出异常,异常整体可以分为以下几类: 1. 不可恢复的错误:如启动时未加载成功代码、Out of Memory(OOM)、堆栈溢出,这类错误会导致函数无法启动或从运行中退出,此类错误是严重错误,会导致函数实例重启,应当尽量规避; 2. 请求处理过程中的错误:这类错误通常是可捕获的,函数需要对这些错误进行处理,记录相关日志并返回相关信息给客户端。如果用户代码未捕获相关异常,函数框架将会捕获该错误,记录相关日志并返回框架定义的错误信息给客户端; 3. 未被捕获的异常 `UncaughtException` 和 `UnhandledRejection`:通常发生在异步代码逻辑中,因为是未捕获异常,业务上是感知不到的,这类错误会被框架捕获,并打印相关信息到日志中。因为通常发生在异步逻辑中,该错误不会体现在函数返回值中; 4. 函数框架报错:函数框架内部可能发生一些错误,可能是函数框架代码存在 Bug,也可能是函数框架的限制,这类错误会被框架捕获,并打印相关信息到日志中; 5. 网络错误:在客户端往返函数实例的网络链路中也可能出现异常,导致一些错误。该类错误非云函数本身的错误,需结合具体部署平台进行排查。 ### 函数的模块化和依赖安装 如通常的 `Node.js` 代码一样,函数也可以通过 `Javascript` 模块化方式来组织代码。对于遵循 `CommonJS` 规范的模块,可以通过 `require` 来引入依赖。例如: ```js // index.js const { add } = require("./sum"); exports.main = function (event, context) { return add(1, 2); }; // sum.js exports.add = function (a, b) { return a + b; }; ``` 对使用 `ECMAScript` 模块化(需在 `package.json` 中声明模块化方式)的代码,可以通过 `import` 语句来引入依赖。例如: ```js // index.mjs import { add } from './sum.mjs' export function main(event, context) { return add(1, 2) } // sum.mjs export function add(a, b) { return a + b } // package.json { "name": "example", "version": "1.0.0", "main": "index.mjs", // 指定入口文件 "type": "module", // 声明模块化方式 "dependencies": { } } ``` 对于外部依赖安装,可在 `package.json` 中声明依赖,函数构建时会根据依赖声明自动安装依赖包。如果目录下存在 `package-lock.json` 等 lock 文件,可以起到锁定依赖版本的作用。 对于通过云托管部署的云函数,构建过程支持 `npm` 、`yarn`、`pnpm` 包依赖管理工具,会识别代码使用的包管理工具进行依赖安装。 ### 函数日志 在函数代码中打印的日志可以分为两类,一类是函数实例级别日志,即主入口函数之外的日志,这部分日志在函数实例启动时、模块被加载时被打印,跟某次具体的请求无关;另一类是请求级别日志,即某次请求触发、代码执行流程进入主入口函数内部之后才会打印的日志,这部分日志往往跟某次具体的请求相关联,一般可以通过请求的 `eventID` 搜索得到。 请求级别的日志从内容上划分,又可以分为以下几类: 1. 每次请求自动打印的结构化访问日志(access log),包括该次请求的基本信息,如处理请求的主机名、访问的 URL、请求方法、响应结果、请求处理耗时等; 2. 函数代码中主动打印的日志,即代码中使用 `console.log` 等方法打印的日志,这部分日志会跟 `eventID` 所关联,有助于排查某次请求的具体执行流程; 3. 函数框架捕获的异常日志,如 `UncaughtException` 和 `UnhandledRejection` ,这部分日志会自动跟 `eventID` 关联,并打印出异常的堆栈信息等便于排查问题。 日志的逻辑级别如下所示: ```text 函数日志 ├── 函数实例日志 ├── 请求级别日志 │ ├── 访问日志 │ ├── 函数代码中打印的日志 │ ├── 框架捕获的异常日志 ``` 日志内容记录位置 `{process.env.CWD}/logs/`,具体内容如下: - `{process.env.CWD}/logs/accesslog*.log`:访问日志,每次函数调用都会有一条日志 - `{process.env.CWD}/logs/usercodelog*.log`:函数代码中打印的日志,可通过 `RequestID` 关联到每一次请求 - `stdout`:标准输出日志,内容包括:函数实例日志、框架捕获的异常日志,以及函数框架打印的日志 例如下面的函数代码: ```js // 函数实例日志 console.log("func initialization started."); // 函数实例日志 setTimeout(() => { console.log("timeout log."); }, 1000); exports.main = function (event, context) { // 请求级别日志 console.log({ event, context }); setTimeout(() => { throwReject(); }, 1000); return "Hello world"; }; async function throwReject() { // 这里会触发未捕获的异常,因为没有捕获,会被框架捕获 // 该异常在 main 中 setTimeout 中抛出,因此不会体现在 main 的返回值中 // 该异常由 函数框架捕获并打印到日志中 Promise.reject(new Error("This is an error.")); } // 函数实例日志 console.log("func initialization finish."); ``` 在函数实例启动时,`App started.` 会被打印。请求到达并处理完毕后,客户端会收到 `Hello world` 响应,1s 后该请求抛出异常被框架捕获,这次请求的访问日志形如: ```json { "@timestamp": "2024-xx-xxTxx:xx:xx.xxxZ", // 日志打印的时间戳 "startAt": "2024-xx-xxTxx:xx:xx.xxxZ", // 请求开始处理的时间 "endAt": "2024-xx-xxTxx:xx:xx.xxxZ", // 请求处理结束的时间 "logType": "accesslog", // 日志类型 "hostname": "host", // 处理请求的主机名 "pid": 13506, // 进程 ID "version": "x.y.z", // 函数框架版本 "nodeEnv": "", // Node.js 相关的环境变量 "tcbEnv": "", // 云开发相关的环境变量 "eventId": "b0900934-79d7-4441-856f-dd46392a5f91", // 本次请求的 eventID "method": "GET", // 请求方法 "url": "http://127.0.0.1/", // 请求 URL "path": "/", // 请求路径 "status": 200, // 响应状态码 "httphost": "127.0.0.1", // 请求的主机名 "ua": "PostmanRuntime/7.39.0", // 请求的 User-Agent "connection": "keep-alive", "contentType": "application/json", // 请求的 Content-Type "clientIp": "::1", // 请求的客户端 IP "resSize": 12, // 响应体大小 "resContentType": "text/plain", // 响应的 Content-Type "timeCost": 11, // 本次请求的总耗时 "userTimeCost": 0 // 从函数主入口开始到从入口返回的耗时 } ``` 随后异常会被框架捕获,可通过 `eventID`(`b0900934-79d7-4441-856f-dd46392a5f91`) 关联,并打印出如下堆栈: ```text Unhandled Rejection for: Error: This is an error. at throwReject (/path/to/your/project/index.js:9:18) at Timeout._onTimeout (/path/to/your/project/index.js:4:22) ``` > 注意:日志对于问题排查是非常重要的,建议在函数代码中适当打印日志,以便排查问题。 > 但是打印大量日志将会带来其他问题,如日志消耗磁盘空间可能导致磁盘可用空间不足,日志采集组件采集解析文件消耗较多 CPU,日志上报到存储系统会消耗大量网络带宽,存储大量日志导致成本增加,日志写入和查询性能下降延迟高等诸多问题,因此建议适度打印日志。 ## 函数路由 函数框架在框架层面支持将一个 `大函数` 拆分成多个 `子函数`,并支持通过配置请求 `path` 将不同的请求路由到不同函数上。 基于函数路由能力,可以方便的将多个函数合并为一个大函数并部署在同一个云托管服务上,可以实现资源共享、较少资源消耗、节约成本。 要使用函数路由能力,需要添加命名为 `cloudbase-functions.json` 的配置文件,对不同函数进行声明,并进行路由配置。 `cloudbase-functions.json` 文件说明: ```ts export interface FunctionDefinition { name: string; // 函数名称 directory: string; // 函数目录,相对于函数 functionsRoot 的路径 source?: string; // 函数入口文件,默认为 index.js target?: string; // 入口函数名称,默认为 main triggerPath?: string; // 匹配的路径,前缀匹配,与 RouteDefinition 中的 path 作用相同,简化配置 } export interface RouteDefinition { functionName: string; // 目标函数名称,与 FunctionDefinition 中的 name 对应 path?: string; // 匹配的路径,前缀匹配,匹配后会调用 functionName 对应的函数 } export interface FunctionsConfig { functionsRoot: string; // 函数根目录,会到此目录下查找并加载函数 functions: FunctionDefinition[]; // 函数定义,描述函数信息,包括函数名称、目录、入口文件等 routes: RouteDefinition[]; // 路由定义,即 path -> functionName } ``` 函数路由规则说明: 1. 路由匹配为前缀匹配模式,如:请求路径 `/a`、`/a/b`、`/a/b/c` 均可匹配 `path=/a` 的路由规则 2. 路由匹配遵循最长匹配原则,即匹配到最长的路由规则,例如 `/a/b/c` 会优先匹配 `/a/b` 而不会匹配 `/a` 3. 路由规则中 `path` 末尾带 `/` 的和不带 `/` 的路由匹配效果相同,例如 `/aa` 和 `/aa/` 是等价的,但不会被认为是冲突的 4. 路由匹配以 `/` 分隔为路径单元,路径单元需精确匹配,如 `/aaa` 不会匹配 `/a -> functionA` 规则 5. 一个函数可以有多个路由,即可以通过多个不同路径规则触发同一个函数,例如 `/a1` 和 `/a2` 可配置都触发 `a` 函数 6. 同一个路由只能指向一个函数,例如:`/a` 只能指向一个函数,不能同时指向 `a` 和 `b` 函数 编辑器支持:使用 VSCode 开发时,可以在 `.vscode/settings.json` 中声明如下配置以获得 `cloudbase-functions.json` 的 schema 提示: ```json { "json.schemas": [ { "fileMatch": ["cloudbase-functions.json"], "url": "./node_modules/@cloudbase/functions-framework/functions-schema.json" } ] } ``` ### 示例 假设有如下目录结构: ```shell . ├── cloudbase-functions.json # 多函数配置文件 ├── index.js # 默认函数入口文件 ├── Dockerfile # 构建文件 ├── echo # 函数 echo │ └── echo.js # 函数 echo 入口文件 ├── sse # 处理 SSE 请求的函数 sse │ ├── index.mjs │ └── package.json ├── sum # 函数 sum │ ├── index.js │ ├── package.json │ └── sum.js └── ws # 处理 websocket 请求的函数 ws ├── client.mjs ├── index.mjs └── package.json ``` 在 `cloudbase-functions.json` 配置文件中,需要对 `默认函数(index.js)`、`echo`、`sse`、`sum`、`ws` 等函数进行声明和路由,配置文件如下: ```json { "functionsRoot": ".", // 函数根目录,指定为当前目录 "functions": [ // 声明各函数及其入口文件 { "name": "default", // 声明默认函数,即入口为 index.js 的函数 "directory": "." // 函数目录为当前目录 }, { "name": "echo", // 声明函数 echo "directory": "echo", // 声明 echo 函数所在目录为 echo (相对于函数根目录) "source": "echo.js", // 函数入口文件为 echo.js "triggerPath": "/echo" // 函数触发路径,即请求 path 匹配 /echo 时,路由至函数 echo }, { "name": "sum", // 声明函数 sum "directory": "sum" }, { "name": "ws", // 声明函数 ws "directory": "ws" }, { "name": "sse", // 声明函数 sse "directory": "sse" } ], "routes": [ // 声明路由规则,可通过 triggerPath 方式简化路由规则配置 { "functionName": "sum", // 路由至函数 sum,名称需要与上述定义的函数名称对应 "path": "/sum" // 满足请求 path 前缀匹配 /sum 条件的请求匹配该规则 }, // path 前缀匹配 /echo 的请求路由至函数 echo { "functionName": "echo", "path": "/echo" }, // path 前缀匹配 /ws 的请求路由至函数 ws { "functionName": "ws", "path": "/ws" }, // path 前缀匹配 /sse 的请求路由至函数 sse { "functionName": "sse", "path": "/sse" }, // 请求默认匹配到 default 函数 { "functionName": "default", "path": "/" } ] } ``` 使用该配置文件启动服务,根据路径的不同,请求会被路由至不同的函数: - path 前缀匹配 `/sum` 时,路由至函数 `sum` - path 前缀匹配 `/echo`时,路由至函数 `echo` - path 前缀匹配 `/ws` 时,路由至函数 `ws` - path 前缀匹配 `/sse` 时,路由至函数 `sse` - 未能匹配任一规则的请求,路由至函数 `default` ## 使用 `TypeScript` 编写函数代码 相比 `JavaScript` 通过 `TypeScript` 编写代码可以获得诸多好处,例如: - `更好的开发体验`:编辑器可以提供更好的代码补全、重构和导航功能。这使得开发过程更加高效 - `语法特性`:`TypeScript` 支持最新的 `ECMAScript` 特性,如装饰器、泛型、异步函数等 - `静态类型检查`:可以在编译时捕获类型错误,减少运行时错误,提高代码的可靠性 - `接口和类型别名`:`TypeScript` 支持接口和类型别名,允许开发者定义复杂的数据结构。这使得代码更加模块化和可重用 - `可读性和可维护性`:通过明确的类型定义,`TypeScript` 代码通常更易于理解和维护 - `更好的文档`:类型定义本身可以作为文档,帮助开发者理解函数和模块的用法,而不需要额外的文档 推荐使用 `TypeScript` 编写函数代码,尤其是相对复杂一点儿的项目中。 ### TS 类型定义 云函数入口的格式是在入口文件导出一个 `main` 函数,如: ```ts // index.ts export const main = function(event, context) {} // 等价于 export const main = function(event: any, context: any) any {} ``` 如上示例的函数声明中,函数入参 `event`, `context` 以及 `函数的返回值` 均为 `any` 类型。 参数和返回值类型不明确,导致没有良好的基于类型的代码补全能力,也没有类型检查的约束能力,不利于代码的维护和调试。 针对以上 `参数和返回值` 类型的不同特点: - `event` 是由业务逻辑定义的,需根据业务需要进行类型定义,并指定 - `context` 是云函数框架提供的,由函数框架定义 - `函数的返回值` 也是由业务逻辑定义的,需根据业务需要进行类型定义,并指定 云函数框架提供了云函数的入口函数类型定义,可以增强对参数和返回值类型的感知和约束能力。 可以通过安装 `@cloudbase/functions-typings` 包来获取云函数的入口函数类型定义: ```sh npm install @cloudbase/functions-typings@v-1 ``` 安装完成后,即可在函数代码中引入云函数的入口函数类型定义: ```ts import { TcbEventFunction } from "@cloudbase/functions-typings"; ``` 如上示例中导入的 `TcbEventFunction` 为云函数的入口函数类型定义,支持定义函数的 `event` 和 `context` 参数类型,以及函数的 `返回值` 类型。 该类型是一个泛型类型 `TcbEventFunction` 1. 通过 `EventT` 可定义 `event` 参数的类型 2. 通过 `ResultT` 可定义函数的 `返回值` 的类型 这两个类型具有业务逻辑决定,这里开放了灵活的定义方式。 #### 定义函数的 `event` 参数类型 可通过 `TcbEventFunction` 的第一个泛型参数 `EventT` 来指定函数 `event` 参数的类型。 如下为定义 `event` 参数类型一个示例: ```ts type jsonEvent = {a: number, b: string, c: boolean, d: {e: string}} export const main: TcbEventFunction = function(event, context) { event.a event.b } ``` 如果是通过 `FormData` 方式进行文件上传,函数框架会将文件上传时对应的字段放在 `event` 入参的对应字段中,类型为 `File` 类型,用于描述文件信息,例如文件名称、文件大小、文件路径等。 可通过如下示例的方式定义包含文件 `File` 的 `event` 参数类型: ```ts import { TcbEventFunction, File } from "@cloudbase/functions-typings"; type formEvent = { str: string; file: File }; export const main: TcbEventFunction = function (event, context) { event.str; event.file.filepath; }; ``` #### 定义函数的 `返回值` 类型 可通过 `TcbEventFunction` 的第二个泛型参数 `ResultT` 来指定函数的返回值类型。 函数返回值支持 [`普通响应`](#普通响应) 和 [`集成响应`](#集成响应) 两种类型。 普通响应直接指定具体类型即可,普通响应类型示例: ```ts import { TcbEventFunction } from "@cloudbase/functions-typings"; // 普通响应-有返回值,返回值类型为 string // ReturnT = string export const main: TcbEventFunction = function (event, context) { return "done."; }; ``` 集成响应可通过 `IntegrationResponse` 进行定义,通过 `BodyT` 可以定义响应体类型,如下示例: ```ts import { TcbEventFunction, IntegrationResponse, } from "@cloudbase/functions-typings"; // 集成响应,返回值 body 类型为 string // ReturnT = IntegrationResponse export const main: TcbEventFunction< void, IntegrationResponse > = function (event, context) { return { statusCode: 200, headers: {}, body: "", }; }; ``` 函数的返回值类型支持 `Promise` 类型,如 `ReturnT = Promise` 或 `ReturnT = Promise>` 如下风格的函数定义,也可以指定类型,但是会略麻烦,推荐使用如上描述的风格。 ```ts export function main(event, context) {} ``` ### 完整示例 ```ts import { TcbEventFunction, File, IntegrationResponse, } from "@cloudbase/functions-typings"; // GET no boyd export const main: TcbEventFunction = function (event, context) {}; // Content-Type: application/json type jsonEvent = { a: number; b: string }; export const main: TcbEventFunction = function (event, context) { event.a; event.b; }; // Content-Type: multipart/form-data type formEvent = { str: string; file: File }; export const main: TcbEventFunction = function (event, context) { event.str; event.file.filepath; }; // Content-Type: application/octet-stream export const main: TcbEventFunction = function (event, context) { event.byteLength; }; // 访问 Context 信息 export const main: TcbEventFunction = function (event, context) { context.extendedContext?.envId; context.extendedContext?.userId; }; // 普通响应-无返回值 export const main: TcbEventFunction = function (event, context) { return; }; // 普通响应-有返回值 export const main: TcbEventFunction = function (event, context) { return "done."; }; // 集成响应 export const main: TcbEventFunction< void, IntegrationResponse > = function (event, context) { return { statusCode: 200, headers: {}, body: "", }; }; // 异步函数 export const main: TcbEventFunction> = async function ( event, context ) { return new Promise((resolve) => { setImmediate(() => { resolve("done."); }); }); }; // SSE export const main: TcbEventFunction> = async function ( event, context ) { const sse = context.sse?.(); // 可选参数,设置 SSE 连接的请求头 // const sse = context.sse?.({ // keepalive: false, // 是否保持连接,默认开启,可关闭 // headers: { // 'Mcp-Session-ID': 'this-is-a-mcp-session-id', // 'X-ABC': ['A', 'B', 'C'], // } // }) if (sse && !sse.closed) { sse.on("close", () => { console.log("sse closed"); }); sse.send({ data: "hello from sse function, abcedfg..., €€€€€⭐⭐⭐⭐⭐" }); sse.send({ data: "message with linebreak symbol:\\nThis is the first line.\\n\\nThis is the second line.\\r\\r\\r\n", }); // 单次发送多个事件 sse.send([ { data: "This is the first message." }, { data: "This is the second message, it\n\r\r\n\r\r\rhas two lines." }, { data: "This is the third message." }, ]); // 以下为发送原始消息的示例 // 该方式用于扩展 SSE 协议,例如发送其他 Event Field 字段 // 注意:末尾必须有换行符数据才会立即发送 sse.send("message: This is a raw message. "); sse.send(["message: This is another raw message.\n\n"]); const msgs = [ "This is a raw message. \n", "This is another raw message.\n\n", ]; sse.send(msgs); } return ""; }; // WebSocket export const main: TcbEventFunction = async function (event, context) { if (context.ws) { context.ws.on("open", (msg) => { console.log("open: ", msg); }); context.ws.on("error", (msg) => { console.log("error: ", msg); }); context.ws.on("close", (msg) => { console.log("close: ", msg); }); context.ws.on("message", (msg) => { console.log("message: ", msg); }); context.ws.send("hello from websocket function"); } }; main.handleUpgrade = async function (context) { return { allowWebSocket: true, }; }; ``` ## 项目组织结构 ### 基于 TypeScript 的项目结构 ```tree . ├── README.md # 项目说明 ├── Dockerfile # 构建 ├── src # 云托管函数项目代码目录,每个目录对应一个函数 │ ├── README.md # 云函数说明 │ ├── func-a # 云函数 `func-a` 代码目录,单函数示例多函数 │ │ ├── README.md # 当前云函数说明 │ │ ├── built # TypeScript 编译后代码目录 │ │ ├── src # TypeScript 源代码目录 │ │ ├── package.json │ │ ├── cloudbase-functions.json # 云函数声明及配置文件 │ │ └── tsconfig.json # TypeScript 配置文件 │ └── func-b # 云函数 `func-b` 代码目录,单函数示例单函数 │ ├── README.md # 当前云函数说明 │ ├── built # TypeScript 编译后代码目录 │ ├── src # TypeScript 源代码目录 │ ├── package.json │ └── tsconfig.json # TypeScript 配置文件 ├── node_modules # node_modules [可能有] ├── package.json # package.json [可能有] ├── tsconfig.json # tsconfig.json [可能有] ``` 该项目结构对应源代码见:https://github.com/TencentCloudBase/func-v2-template 以上,是一个基于 `TypeScript` 的项目结构示例,项目中 需要 `src`、`built` 目录 包含 源代码和编译后的代码,因此需要在云函数代码中配置 `tsconfig.json` 文件。 如果是个 `JavaScript` 编码的项目,则不需要 `src`、`built`、`tsconfig.json` 等目录和文件。 因每个函数是独立部署的,所以每个函数也是相互独立的,但是代码结构比较相似。 非运行时依赖的部分,可以在多个函数之间共享,如 `tsconfig.json` `公共依赖` 等。 项目中还可能包含很多其他类型的文件,其他文件的组织根据项目需要进行即可。 以上是一个相对复杂的项目结构,针对较复杂的项目。 如果整个项目目录中,只有一个云函数,也可以采用单函数的方式进行 `目录结构` 组织,例如如下项目结构: ```txt . ├── README.md # 当前云函数说明 ├── built # TypeScript 编译后代码目录 ├── src # TypeScript 源代码目录 ├── Dockerfile # 构建文件 ├── package.json ├── cloudbase-functions.json # 云函数声明及配置文件 └── tsconfig.json # TypeScript 配置文件 ``` 以目录结构是把 `项目根路径/cloudrunfunctions/func-a/*` 目录中的文件移动到 `项目根路径/` 目录下,这样的目录结构适用于单函数的场景。 --- # 云托管/函数型开发框架/本地开发云函数 > 当前文档链接: https://docs.cloudbase.net/cbrf/how-to-develop-local 云函数不仅可以运行在 `云端环境` 中,通过云开发提供的 `tcb-ff` 命令行工具,云函数也可以运行在 `本地环境` 中,这样就可以方便的在本地环境进行云函数的开发调试。 相比 `云端环境`,`本地环境` 需要通过 `tcb-ff` 命令先将云函数运行起来,然后再通过 `http` 请求触发云函数执行。 各种发起 `http` 的方式都可以调用云函数,例如 `curl`、`postman` 或者 各编程语言提供的请求库进行调用 等。 以下为如何本地开发云函数的详细步骤,可结合示例项目进行体验。 ## 调用本地运行的云函数 触发云函数的方式主要有: 1. 通过云开发提供的SDK进行调用 1. `@cloudbase/js-sdk` 运行在浏览器环境中,例如 小程序、web 等 2. `@cloudbase/node-sdk` 运行在服务端 `node.js` 环境中,例如云函数中 2. 通过 `curl` 命令行工具进行调用 ### 通过云开发提供的SDK进行调用 如下图所示,图中演示了 通过 `@cloudbase/js-sdk` 调用 `云函数-A`,`云函数-A` 中再通过 `@cloudbase/node-sdk` 调用 `云函数-B` 的场景。 调用链路:客户端(`@cloudbase/js-sdk`) ----> `云函数-A`(`@cloudbase/node-sdk`) ----> `云函数-B` 云开发SDK默认会调用云端环境中运行的云函数,如果需要调用本地运行的云函数,则需要将到云端环境的中请求切换到本地运行的云函数。通过增加一个 `本地代理层`,即可实现将请求转发到本地。 ![call-local-cbrf](./images/call-local-cbrf.svg) - `@cloudbase/node-sdk` 调用云函数的参考文档:https://docs.cloudbase.net/api-reference/server/node-sdk/functions#4-%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81 - `@cloudbase/js-sdk` 调用云函数的参考文档: 实现对本地运行的云函数的调用,首先需要启动一个 `本地代理层`,将请求转发到本地运行的云函数。 #### 搭建 `本地代理层` 服务 `whistle` 是一个常用的跨平台的网络代理工具,可以实现将请求转发到本地。可以实现上图描述的 `本地代理层` 的功能,将请求转发到本地运行的云函数。 `whistle` 参考:https://wproxy.org/whistle/install.html 安装 `whistle`: ```sh npm install -g whistle ``` 启动 `whistle`: ```sh $ w2 start [!] whistle@2.9.94 is running [i] 1. use your device to visit the following URL list, gets the IP of the URL you can access: http://127.0.0.1:8899/ http://10.0.0.123:8899/ http://192.168.0.123:8899/ Note: If all the above URLs are unable to access, check the firewall settings For help see https://github.com/avwo/whistle [i] 2. set the HTTP proxy on your device with the above IP & PORT(8899) [i] 3. use Chrome to visit http://local.whistlejs.com/ to get started ``` 浏览器打开配置页面 ```sh open http://127.0.0.1:8899/ ``` 配置规则(将请求重写到本地): ```sh ^https://*.api.tcloudbasegateway.com/v1/cloudrun/*/* http://127.0.0.1:3000/$3?@fn=$2 ^http://*.api.tcloudbasegateway.com/v1/cloudrun/*/* http://127.0.0.1:3000/$3?@fn=$2 ``` 最后,将浏览器 HTTP 代理配置为 http://127.0.0.1:8899 即可,可通过 Chrome 浏览器插件 `ZeroOmega/SwitchyOmega` 配置浏览器代理。 也可以通过配置系统级别的代理进行配置,执行 `whistle` 提供的 `w2 proxy` 命令会配置系统代理。 #### 使用 `@cloudbase/js-sdk` 调用本地运行的云函数 使用 `@cloudbase/js-sdk` 在浏览器环境中调用本地运行的云函数,无需修改任何代码,只需要将浏览器发起的请求转发到本地 `whistle` 代理服务器即可实现调用本地运行的云函数。 例如如下示例的前端代码: ```ts import cloudbase from "@cloudbase/js-sdk"; const tcbapp = cloudbase.init({ env: "your-env-id", clientId: "your-client-id", }); const auth = tcbapp.auth(); await auth.signInAnonymously(); // 调用 cloudbaserunfunctions/helloworld 函数 const result = await tcbapp.callFunction({ name: "helloworld", type: "cloudrun", }); console.log("result:", result); ``` 完整示例项目代码可查看:https://github.com/TencentCloudBase/cloudbase-examples/blob/master/cloudrunfunctions/fullstack-project/webapp/src/index.js #### 使用 `@cloudbase/node-sdk` 调用本地运行的云函数 服务端使用 `@cloudbase/node-sdk` 调用本地运行的云函数,需要 `tcb.init({})` 初始化时,增加 `proxy` 参数,即可将请求转发到本地 `whistle` 代理服务器。 > 注意:通常仅在本地环境中调用本地运行的云函数时,才需要配置 `proxy` 参数。该参数可以通过动态的方式进行配置。 ```ts const tcbapp = tcb.init({ // 这里 Proxy 配置为本地代理层服务 Whistle // Whistle 可以将请求转发到本地运行的云函数 proxy: "http://127.0.0.1:8899", }); ``` 完整代码: ```ts import tcb from "@cloudbase/node-sdk"; exports.main = async (event, context) => { const { httpContext } = context; const { url, httpMethod } = httpContext; const tcbapp = tcb.init({ // 这里 Proxy 配置为本地代理层服务 Whistle proxy: "http://127.0.0.1:8899", context: { extendedContext: { tmpSecret: { secretId: "this-is-a-fak-secretId", secretKey: "this-is-a-fake-secretKey", }, }, ...context, }, }); const result = await tcbapp.callFunction( { name: event.otherFuncName, // 云托管函数 参数 type: "cloudrun", method: "POST", path: "/abc", data: { key1: "test value 1", key2: "test value 2", }, }, { timeout: 5000, }, ); return { result }; }; ``` ### 通过 `curl` 命令行工具进行调用 通过 `curl` 可以直接发起对 云函数的 `http` 请求,无需进行任何配置。 该方式与调用云端运行的云函数时,使用的 云函数默认域名、HTTP访问服务 的方式是类似的。 ```sh # 发送 GET 请求 curl -v -XGET http://localhost:3000/path/to/xxx # 发送 POST 请求 curl -v -XPOST 'http://127.0.0.1:3000/path/to/xxx \ -H 'Content-Type: application/json' \ --data-raw '{"name":"xxxxx"}' # 发送 PUT 请求 curl -v -XPUT 'http://127.0.0.1:3000/path/to/xxx \ -H 'Content-Type: application/json' \ --data-raw '{"name":"xxxxx"}' # 发送 DELETE 请求 curl -v -XDELETE 'http://127.0.0.1:3000/path/to/xxx ``` 一个通过云函数开发AI智能体请求的示例: ```sh curl -XPOST 'http://127.0.0.1:3000/v1/aibot/bots/ibot-xxxx/send-message' \ -H 'Accept: text/event-stream' \ -H 'Content-Type: application/json' \ --data-raw '{"name":"xxxxx"}' ``` 以上是通过 `curl` 发起请求的示例,可以通过任意其他方式发起 `http` 请求,例如 `postman`、`浏览器 fetch`、`node.js http.request` 等。 --- # 云托管/函数型开发框架/在线开发云函数 > 当前文档链接: https://docs.cloudbase.net/cbrf/how-to-develop-online 云开发平台提供在线开发云函数的能力,无需配置本地开发环墋,即可在线开发、调试、部署云函数。 ## 进入在线开发环境 通过点击 云托管服务 详情页的 `在线开发` 按钮,即可进入在线开发环境。 ![goto-develop-online](./images/goto-develop-online.png) 进入环境前可能会需要进行云端环境准备 ![cloudstudio-init](./images/cs-cloudstudio-init.png) ## 工作区介绍 进入在线开发环境会,会来到类似如下的一个工作区,该工作区环境的编辑器界面与 `VSCode` 是相同的,支持代码编辑、调试、终端等功能。 下图展示了 `文件浏览器区域` 的主要内容: ![cs-workspace-intro](./images/workspace-page.png) #### 新建函数 右键点击 `src目录` 后,点击`新建文件夹`,输入函数名称。 #### 编写云函数代码 可在在线编辑器界面进行代码编写。如何编写 `云托管函数` 的代码,可参考 [函数编写指南](./how-to-writing-functions-code)。 --- # 云托管/函数型开发框架/如何调试函数代码? > 当前文档链接: https://docs.cloudbase.net/cbrf/how-to-debug-functions-code 在日常开发过程中,经常需要调试代码,以便高效的完成功能开发与问题定位。`云托管函数框架` 支持用户在线开发和本地开发两种断点调试方式 ## 在线开发断点调试方式 ### 1. 进入“在线开发”模式 在云托管列表界面,找到您想要调试的云函数,点击其对应的「在线开发」按钮,进入 “在线开发” 模式 ![](./images/debug-entrance.png) ### 2. 点击「运行和调试」菜单启动调试服务 ![](./images/debug-button.png) ### 3. 设置断点,填参数,点击「运行测试」 等待调试服务启动,即可开始调试 ![](https://qcloudimg.tencent-cloud.cn/raw/91a122d36e2e2220e52b62b6b1d048bc.png) ## 本地开发断点调试方式 ### 配置说明 通过 `vscode` 调试需创建 `.vscode/launch.json` 配置文件,并配置如下参考内容: > 注意:以下配置需要根据文件内注释的描述进行调整 ```json { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "launch-tcb-ff-global", // 注意:请确保全局安装了 @cloudbase/functions-framework 模块 // npm install -g @cloudbase/functions-framework // 因不同环境下全局安装路径可能不同,需要手动替换 // Windows 系统可通过 `where tcb-ff` 命令查看全局安装路径 // MacOS/Linux 系统下可通过 `which tcb-ff` 命令查看全局安装路径 "program": "please-replace-this-with-the-path-to-your-global-tcb-ff", // 单实例多函数模式下,可通过 --functionsConfigFile 参数指定函数配置文件 "args": ["--functionsConfigFile cloudbase-functions.json"], // 单函数模式下,通过 --source 参数指定函数代码目录,支持 TS/JS 代码 // 注意:--functionsConfigFile 参数会使 --source 参数失效 // "args": ["--source func-abc"], "env": { // 通过 ts-node 可支持直接调试 TS 代码,如不需要可以移除 "NODE_OPTIONS": "--require ts-node/register/transpile-only" }, "skipFiles": ["/**"], "outFiles": ["!**/node_modules/**"] }, { "type": "node", "request": "launch", "name": "launch-tcb-ff-local", // 注意:请确保安装在当前项目下安装了 @cloudbase/functions-framework 模块 // npm install @cloudbase/functions-framework "program": "${workspaceFolder}/node_modules/.bin/tcb-ff", // 直接启动 src 目录下的 ts 函数代码,如需调试编译后的 js 代码,可将 --source 参数替换为编译后的目录 // 单实例多函数模式下,可通过 --functionsConfigFile 参数指定函数配置文件 // 注意:functionsConfigFile 参数会使 --source 参数失效 "args": ["--functionsConfigFile cloudbase-functions.json"], // 单函数模式下,通过 --source 参数指定函数代码目录,支持 TS/JS 代码 // "args": ["--source func-abc"], "env": { // 通过 ts-node 可支持直接调试 TS 代码,如不需要可以移除 "NODE_OPTIONS": "--require ts-node/register/transpile-only" }, "skipFiles": ["/**"], "outFiles": ["!**/node_modules/**"] }, { // 注意:在启动调试进程前,需确保已通过 `NODE_OPTIONS='--inspect=localhost:9229' tcb-ff .` 等调试命令启动了服务 "address": "localhost", "port": 9229, // 配置端口为 9229 "localRoot": "${workspaceFolder}", // 采用 Attach Node.js 进程的方式调试 "name": "attach-to-remote-9229", "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ``` 以上配置文件中包含了两种类型(`launch/attach` )的三种方式配置: #### 1.【推荐】vscode `launch` 启动 node 进程进行调试 该模式分为两种不同的配置: 1. `launch-tcb-ff-global`:使用全局安装的 `tcb-ff` 运行函数代码 2. `launch-tcb-ff-local`:使用本地安装的 `tcb-ff` 运行函数代码 #### 2. vscode `attach` 方式调试 1. `attach-to-remote-9229`:Attach 到远程 9229 端口的 Node.js 进程 如果采用该方式进行调试,则需要独立运行云函数,相比 `launch` 方式更麻烦,调试步骤如下: 首先,在终端手动执行如下命令: ```sh # 指定调试服务器端口为 9229 NODE_OPTIONS="--inspect=localhost:9229" tcb-ff ``` 然后,在 vscode 中,通过快捷键(`F5`)启动调试进程(或在 `Run And Debug` 界面选择 `Attach to Remote 9229` 启动调试),即可开始进行断点调试。 ### 操作示例 #### 调试 TypeScript 代码 > 本示例演示如何调试 `TS` 编写的函数代码,采用全局安装的 `tcb-ff` 命令,对应配置文件中的 `launch-tcb-ff-global`。 前置条件:通过 `npm install -g @cloudbase/functions-framework` 全局安装 `functions-framework` 模块,包含 `tcb-ff` 命令 1. 通过 `GitClone` 下载示例代码:https://github.com/TencentCloudBase/cloudbase-examples 2. vscode 打开 `cloudbase-examples/cloudrunfunctions/ts-multiple-functions` 示例代码目录 3. 可通过 `npm/pnpm/yarn` 安装依赖,例如 `pnpm install` 4. 替换 `.vscode/launch.json` 中的 `program` 为全局安装的 `tcb-ff` 路径 5. 开始调试函数代码 可在 `RunAndDebug` 界面启动调试,操作路径按下图所示: ![functions-debug-ts-1.png](./images/functions-debug-ts-1.png) 设置断点,并通过 `curl` 调用触发函数执行,函数会在断点处停止执行: ![functions-debug-ts-2.png](./images/functions-debug-ts-2.png) 可回到 `RunAndDebug` 界面查看此时的函数堆栈信息: ![functions-debug-ts-3.png](./images/functions-debug-ts-3.png) 点击 `调试控制面板` 的 `继续` 按钮,函数即可继续向下执行。 #### 调试 JavaScript 代码 > 本示例演示如何调试 `JS` 编写的函数代码,采用项目本地安装的 `tcb-ff` 命令,对应配置文件中的 `launch-tcb-ff-local`。 1. 通过 `GitClone` 下载示例代码:https://github.com/TencentCloudBase/func-v2-template 2. vscode 打开 `func-v2-template` 示例代码目录 3. 可通过 `npm/pnpm/yarn` 安装依赖,例如 `pnpm install` 4. 开始调试函数代码 按下图所示的路径启动调试 ![functions-debug-js-1.png](./images/functions-debug-js-1.png) 设置断点,并通过 `curl` 调用触发函数执行,函数会在断点处停止执行: ![functions-debug-js-2.png](./images/functions-debug-js-2.png) 可回到 `RunAndDebug` 界面查看此时的函数堆栈信息: ![functions-debug-js-3.png](./images/functions-debug-js-3.png) 点击 `调试控制面板` 的 `继续` 按钮,函数即可继续向下执行。 --- # 云托管/函数型开发框架/如何迁移云函数代码到云托管 > 当前文档链接: https://docs.cloudbase.net/cbrf/how-to-migrate-to-cbrf 如果需要将原有的云函数代码迁移到云托管函数框架,可以参考如下内容,了解到需要做的一些变更。 ## 函数代码编写的调整 ### 函数入口函数差异 云函数写法: ```js export const main = function(event, context) {} ``` 云托管函数写法: ```js export const main = function(event, context) {} ``` 两种函数的入口函数格式是一致的,参数内容格式存在一定差异: * `event`:云函数触发时传入的事件参数,两种函数基本一致,`云托管函数框架` 支持更多类型的入参格式 * `context`:云函数运行时的上下文参数,两种函数完全不同 因此,如果你在云函数中使用了 `context` 参数,迁移到云托管函数时可能需要进行一定的调整。 ### 函数返回值差异 ### `@cloudbase/node-sdk` 使用差异 云函数写法: ```js import tcb from '@cloudbase/node-sdk' // tcb.init 可以写在入口函数外 tcb.init({ env: 'abc-xyz' }) export const main = function(event, context) { // tcb.init 可以写在入口函数内 const tcbapp = tcb.init({ env: 'abc-xyz' }) // ... return 'done' } ``` 云托管函数写法: ```js import tcb from '@cloudbase/node-sdk' export const main = function(event, context) { // tcb.init 需要写在入口函数内,因依赖 context 参数,所以必须放在 `main` 函数内部 const tcbapp = tcb.init({ context: context, env: 'abc-xyz' }) // ... return 'done' } ``` 在云托管函数中,`tcb.init` 需要传入的 `context` 参数获得相关信息,所以在使用时需要将 `tcb.init` 放在 `main` 函数内。 如何在云托管函数中使用 `@cloudbase/node-sdk` 相关能力,可参考 [如何调用TCB云开发的其他能力?](./how-to-invoke-tcb.md) 文档。 ## 目录结构的调整 云函数代码目录结构示例: ```bash function_nodejs / - index.js - package.json ``` 云托管函数代码目录结构示例 * 单服务内包含一个函数逻辑: ```bash runfunction_nodejs / - index.js - package.json - Dockerfile ``` * 单服务内包含多个函数逻辑: ```bash runfunction_nodejs / - cloudbase-functions.json # 多函数配置文件 - Dockerfile # 构建 - src/funcA # 函数 A 目录 - index.js - src/funcB # 函数 B 目录 - index.mjs - package.json # 指定 index.mjs 为入口文件 ``` 在云托管函数中,既可以按原有函数目录方式组织代码,也可以将多个函数逻辑整合在一个云托管服务中。 在实际使用中,更加建议**将多个函数整合在一个服务中**。这种方式可以充分利用云托管服务的运行实例资源,降低冷启动,同时简化服务管理。 ## 调用函数的方法调整 在小程序中调用云函数的方法示例: ```js wx.cloud .callFunction({ // 云函数名称 name: "hello_world", // 传给云函数的参数 data: { a: 1, b: 2, }, }) .then((res) => { console.log(res.result); // 3 }) .catch(console.error); ``` 在小程序中调用云托管函数的方法示例: * 单服务内包含一个函数逻辑: ```js // 容器调用必填环境id,不能为空 const c1 = new wx.cloud.Cloud({ resourceEnv: "环境id", }); await c1.init(); const r = await c1.callContainer({ path: "/", // 填入业务自定义路径 header: { "X-WX-SERVICE": "xxx", // 填入服务名称 }, // 其余参数同 wx.request method: "POST", }); console.log(r); ``` * 单服务内包含多个函数逻辑: ```js // 调用必填环境id,不能为空 const c1 = new wx.cloud.Cloud({ resourceEnv: '环境id' //填入云开发环境 id }) await c1.init() const r1 = await c1.callContainer({ path: '/', // 默认函数对应的请求路径 header: { 'X-WX-SERVICE': 'testfunc2', // 填入创建时的函数名称 testfunc2 }, // 其余参数同 wx.request method: 'GET', }) console.log(r1) //输出 Hello world! const r2 = await c1.callContainer({ path: '/echo', // echo 函数对应的请求路径 header: { 'X-WX-SERVICE': 'testfunc2', // 填入创建时的函数名称 testfunc2 }, // 其余参数同 wx.request method: 'POST', data: { a: 1, b: 2 } }) console.log(r2) //输出具体请求及时间 ``` 更多的云托管函数的调用方法可见[文档](https://docs.cloudbase.net/cbrf/how-to-writing-functions-code#%E7%AC%AC%E4%B8%89%E6%AD%A5%E8%B0%83%E7%94%A8%E5%87%BD%E6%95%B0)。 --- # 云托管/函数型开发框架/如何调用TCB云开发的其他能力? > 当前文档链接: https://docs.cloudbase.net/cbrf/how-to-invoke-tcb 如 TCB云函数环境中运行的 函数代码 一样,可以在 `云托管函数` 环境中使用 [@cloudbase/node](https://docs.cloudbase.net/api-reference/server/node-sdk/initialization) 调用 TCB的其他能力,如数据库、存储等,当然也可以调用云函数。 因 `云托管函数` 同 `云函数` 运行式环境存在一定的差异,`@cloudbase/node` 在使用上存在细微差异,在 SDK 的初始化时,需要传入 `context` 参数,如下所示: ```ts import { TcbEventFunction } from '@cloudbase/functions-typings' export const main: TcbEventFunction = function(event, context) { // 注意:因依赖 context 参数,所以必须放在 `main` 函数内部,不要缓存 tcbapp 实例,该实例仅有有限的有效期。 const tcbapp = tcb.init({ context: context, env: 'abc-xyz', // 指定调用的环境Id,如果不传 `env` 则调用当前环境 }) tcbapp.uploadFile({cloudPath: '', fileContent: Buffer.from('')}) // ... return 'done' } ``` 传入 `context` 参数后,`@cloudbase/node` 即可调用 TCB云开发的相关能力。 ## 完整示例代码 ```ts import { TcbEventFunction } from '@cloudbase/functions-typings' import tcb from '@cloudbase/node-sdk' async function sleep (time: number) { return await new Promise(resolve => { setTimeout(() => { resolve(undefined) }, time) }) } async function uploadFile (tcbapp: tcb.CloudBase) { const result = await tcbapp.uploadFile({ cloudPath: ' hello world 你好 世界 .png', fileContent: Buffer.from(' hello world 你好 世界 ') }) return await tcbapp.getTempFileURL({ fileList: [result.fileID] }) } async function runCmdWithCloudbaseNodeSDK (tcbapp: tcb.CloudBase) { const result = await tcbapp .database() .collection('articles') .where({ _id: 'not-exists-id' }) .options({ multiple: true }) .update({ 'a.b.c': 'a.b.c' }) return result } export const main: TcbEventFunction = function(event, context) { console.log({ event, context }) const tcbapp = tcb.init({ context }) // 调用 云函数 try { const result = await tcbapp.callFunction({ name: 'test', data: { a: 1 } }) console.log(result) } catch (e) { console.error(e, 'callFunction.error') return e } // 上传文件 try { const result = await uploadFile(tcbapp) console.log(result, 'uploadFile.result') } catch (e) { console.error(e, 'uploadFile.error') return e } // 调用数据库 try { const result = await Promise.all([ runCmdWithCloudbaseNodeSDK(tcbapp) ]) console.log(result, 'run.result') } catch (e) { console.error(e, 'e') return e } await sleep(3) return 'done' } ``` --- # 云托管/函数型开发框架/如何按需构建自定义函数镜像 > 当前文档链接: https://docs.cloudbase.net/cbrf/how-to-customize-functions-image 云托管函数框架服务部署时需要提供函数代码以及 `Dockerfile`。 如对镜像有个性化需求,例如安装特定的依赖包、字体等,可以通过自定义 `Dockerfile` 的方式构建自己的容器镜像。 以下为云端构建时使用的 `Dockerfile` 内容,可在此基础上按需修改,仅供参考: ```Dockerfile # 可通过该变量指定 functions-framework 基础镜像版本 ARG FRAMEWORK_VERSION=20.18.3-1.17.0-33-5b218e FROM crunbuild-az-new.tencentcloudcr.com/cloudbase/functions-framework:${FRAMEWORK_VERSION} RUN PASSWORD=$(dd bs=1 count=12 if=/dev/urandom | md5sum | cut -d' ' -f1) && echo "root:$PASSWORD" | chpasswd -c SHA512 WORKDIR /workspace COPY . . ################################### #### 可以在此执行个性化配置相关指令 #### ################################### RUN . ~/.profile \ && ls -liha \ && rm -rf logs \ && echo "\n\n===> step.1 install specified node.js version" \ && \ if [ -e .node-version ]; then \ echo " File '.node-version' exists, use '.node-version', content: '$(cat .node-version)'"; \ nvm install $(cat .node-version) && nvm alias default $(cat .node-version); \ elif [ -e .nvmrc ]; then \ echo " File '.nvmrc' exists, use '.node-version', content: '$(cat .node-version)'"; \ nvm install && nvm use && nvm alias default $(nvm current); \ else \ echo " File '.node-version' or '.nvmrc' not exists, use buildin node"; \ fi; \ echo " node version $(node -v)" \ && echo "\n\n===> step.2 install dependency packages" \ && \ if [ ! -e package.json ]; then \ echo " File 'package.json' not exists, skip install dependencies"; \ elif [ -e package.json ] && [ -d node_modules ]; then \ echo " File 'node_modules' exists, skip install dependencies"; \ elif [ -e package.json ] && [ ! -d node_modules ]; then \ echo " File 'package.json' exists and 'node_modules' not exists, install dependencies"; \ echo " ----------- package.json begin -----------"; \ cat package.json; \ echo " ----------- package.json end -----------"; \ echo " Configure npm"; \ { \ echo "omit=dev"; \ echo "registry=https://mirrors.tencent.com/npm/"; \ echo "# prebuild-install"; \ echo "faiss_node_binary_host_mirror=https://static.cloudbase.net/npm_binary_mirrors/faiss-node/"; \ echo "# puppeteer"; \ echo "puppeteer-download-base-url=https://cdn.npmmirror.com/binaries/chrome-for-testing"; \ } >> ~/.npmrc; \ if [ -e pnpm-lock.yaml ]; then \ echo " File 'pnpm-lock.yaml' exists, use 'pnpm install'"; \ pnpm install; \ elif [ -e yarn.lock ]; then \ echo " File 'yarn.lock' exists, use 'yarn install'"; \ yarn install; \ yarn cache clean; \ else \ echo " Default, use 'npm install'"; \ npm install; \ npm cache clean --force; \ fi; \ if jq -e '(.dependencies | has("puppeteer")) or (.devDependencies | has("puppeteer"))' package.json; then \ echo " File 'package.json' dependencies include puppeteer, install dependencies libraries(.so) for puppeteer"; \ sed -i 's/archive\.ubuntu\.com/mirrors.cloud.tencent.com/g' /etc/apt/sources.list.d/ubuntu.sources; \ export DEBIAN_FRONTEND=noninteractive; \ apt update && apt upgrade -y; \ # 安装 puppeteer 需要的依赖库,比较耗费空间 # https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/dist_package_versions.json apt install -y libasound2t64 libatk-bridge2.0-0t64 libatk1.0-0 libcairo2 \ libcups2t64 libdbus-1-3 libdrm2 libgbm1 libglib2.0-0t64 libglibd-2.0-0 libnss3 \ libpango-1.0-0 libxcomposite1 libxdamage1 libxfixes3 libxkbcommon0 libxrandr2; \ # 安装 puppeteer 可能需要的字体,如网页截屏场景,比较耗费空间 apt install -y fonts-noto fonts-dejavu fonts-font-awesome; \ apt clean; \ rm -rf /var/lib/apt/lists/*; \ sed -i 's/mirrors\.cloud\.tencent\.com/archive.ubuntu.com/g' /etc/apt/sources.list.d/ubuntu.sources; \ fi \ fi ``` 可通过 [`skopeo`](https://github.com/containers/skopeo) 工具 获得 `functions-framework` 基础镜像版本 ```sh skopeo list-tags docker://crunbuild-az-new.tencentcloudcr.com/cloudbase/functions-framework { "Repository": "crunbuild-az-new.tencentcloudcr.com/cloudbase/functions-framework", "Tags": [ "20.18.3-1.13.0-31-695a62", "20.18.3-1.14.0-32-dafdb5", "20.18.3-1.14.0-arm64", "20.18.3-1.17.0-33-5b218e" ] } ``` 推荐根据 `Tag` 列表,选择最新版本。 --- # 云托管/函数型开发框架/开发函数型智能体 > 当前文档链接: https://docs.cloudbase.net/cbrf/develop-cbrf-agent 云托管函数框架 支持开发函数型智能体,详情可参考:[函数型智能体](../ai/cbrf-agent/intro) --- # 云托管/函数型开发框架/函数代码示例 > 当前文档链接: https://docs.cloudbase.net/cbrf/example 如下提供了若干 云托管函数框架 的使用示例,以下示例为简单起见,主要使用 `JavaScript` 编写。 ## 引入外部模块 在函数中引入外部模块,例如 `lodash`,并使用其提供的方法: 函数代码文件示例: ```js // index.js const _ = require('lodash') exports.main = function(event, context) { return _.kebabCase('Hello world') } ``` package.json文件: ```json // package.json { "name": "example", "version": "1.0.0", "main": "index.js", "dependencies": { "lodash": "^4.17.21" } } ``` Dockerfile文件: 参考[`自定义Dockerfile`](https://docs.cloudbase.net/cbrf/how-to-customize-functions-image) 执行该函数后客户端可以收到 `hello-world` 的响应。 ## 在不同函数间共享代码 利用单实例多函数能力和函数间路由,可以使用一般的模块导入方法,在不同的函数间共享公共模块。 假设有函数 `funcA` 和 `funcB` 需要共享一个获取当前时间的方法 `now`,有如下目录结构: ```shell . ├── cloudbase-functions.json # 多函数配置文件 ├── Dockerfile # 用于云托管构建 ├── cloudrunfunctions/common # 公共方法目录 │ └── time.js # 公共方法 ├── cloudrunfunctions/funcA # 函数 A 目录 │ └── index.js └── cloudrunfunctions/funcB # 函数 B 目录 ├── package.json # 指定 index.mjs 为入口文件 └── index.mjs ``` 在 `time.js` 中导出 `now` 方法: ```js // common/time.js exports.now = function () { return new Date().toLocaleString() } ``` 在函数 A 和函数 B 代码中直接引入即可: ```js // cloudrunfunctions/funcA/index.js const now = require('../common/time').now ``` ```js // cloudrunfunctions/funcB/index.mjs import { now } from '../common/time.js' ``` 在 `cloudbase-functions.json` 中对不同的函数进行声明: ```json { "functionsRoot":"./cloudrunfunctions/", // 函数根目录,指定为当前目录 "functions": [ // 声明各函数及其入口文件 { "name":"funcA", // 声明函数 A "directory":"funcA", "triggerPath": "/a" }, { "name":"funcB", // 声明函数 B "directory":"funcB", "triggerPath": "/b" } ] } ``` 这样函数 A 和函数 B 将部署在同一个服务中,同时均可以使用 `now` 方法 ## 在函数中路由 可根据 `context` 中获取到的 HTTP 相关路径、query 等信息,实现简单的路由功能。 函数代码: ```js exports.main = function(event, context) { const { httpContext } = context const { url } = httpContext const path = new URL(url).pathname // 根据访问路径返回不同的内容 switch (path) { case '/': return { statusCode: 200, body: 'Hello world!' } case '/index.html': return { statusCode: 200, headers: { 'Content-Type': 'text/html' }, body: '

Hello world!

' } default: return { statusCode: 404, body: 'Not found' } } } ``` ## 返回不同类型的响应 可以通过不同的路由路径,返回不同的响应类型及响应内容。 函数代码: ```js exports.main = function(event, context) { const { httpContext } = context const { url } = httpContext const path = new URL(url).pathname // 根据访问路径返回不同的内容 switch (path) { // 直接返回字符串 case '/': return 'Hello world!' // 返回当前时间戳 case '/now': return new Date().getTime() // 使用集成响应返回 HTML case '/index.html': return { statusCode: 200, headers: { 'Content-Type': 'text/html' }, body: '

Hello world!

' } // 使用集成响应返回 JSON default: return { statusCode: 404, headers: { 'Content-Type': 'application/json' }, body: { message: 'Not found' } } } } ``` 可综合使用集成响应、非集成响应,获得更丰富的响应类型。 ## 使用 Server-sent Event 推送消息 为了适配 AI 大模型 API 常用的 SSE 协议,云托管函数框架 可以支持 SSE 方式推送内容。 函数代码: ```js exports.main = async function (event, context) { // 切换到 SSE 模式 const sse = context.sse() // 可选参数,设置 SSE 连接的请求头 // const sse = context.sse({ // keepalive: false, // 是否保持连接,默认开启,可关闭 // headers: { // 'Mcp-Session-ID': 'this-is-a-mcp-session-id', // 'X-ABC': ['A', 'B', 'C'], // } // }) sse.on('close', () => { console.log('sse closed') }) // 发送事件到客户端,发送前先检查是否已经关闭,如未关闭可发送 if (!sse.closed) { // 多次发送多个事件 sse.send({ data: 'No.1 message' }) sse.send({ data: 'No.2 message with\n\r\r\n\r\r\rtwo lines.' }) // 单次发送多个事件 sse.send([ { data: 'No.1 message' }, { data: 'No.2 message with\n\r\r\n\r\r\rtwo lines.' } ]) // 以下为发送原始消息的示例 // 该方式用于扩展 SSE 协议,例如发送其他 Event Field 字段 // 注意:末尾必须有换行符数据才会立即发送 sse.send('message: This is a raw message. ') sse.send(['message: This is another raw message.\n\n']) // 函数执行时间以函数返回时间计算 // 函数返回后,HTTP 请求处理完成,函数内的异步逻辑继续进行处理,不影响函数返回时间 // TCP 网络连接依然被 SSE 占用,在 SSE 连接被客户端或服务端关闭之前,可以继续发送消息到客户端 // SSE 协议已经将 HTTP 转换到长连接模式,需要客户端或服务端在适当的时候主动关闭连接,否则将导致连接一直占用,消耗网络资源 // 因TCP主动关闭的一方将进入TIME_WAIT 状态,大量 TIME_WAIT 状态的连接将导致网络资源耗尽,无法建立新的连接,所以客户端主动关闭连接更符合最佳实践 // 因客户端可能并不知晓在什么时间关闭连接,服务端可以发送一个特殊的消息,告诉客户端消息已经结束,可以关闭连接了 // 浏览器中调用 EventSource#close 关闭连接,见:https://developer.mozilla.org/en-US/docs/Web/API/EventSource/close return '' } } ``` ## 使用 WebSocket 长连接收发消息 函数代码: ```js exports.main = function (event, context) { console.log({ event, context }) if (context.ws) { context.ws.on('close', (msg) => { console.log('close: ', msg) }) context.ws.on('message', (msg) => { console.log('message: ', msg) }) setInterval(() => { context.ws.send(`now: ${new Date().toISOString()}`) }, 100) } } // 支持同步异步 exports.main.handleUpgrade = async function (upgradeContext) { console.log(upgradeContext, 'upgradeContext') if (upgradeContext.httpContext.url === '/upgrade-handle-throw-error') { throw new Error('test throw error') } else if (upgradeContext.httpContext.url === '/upgrade-handle-reject-error') { return Promise.reject(new Error('test reject error')) } else if (upgradeContext.httpContext.url === '/allow-websocket-false') { return { allowWebSocket: false, statusCode: 403, body: JSON.stringify({ code: 'code', message: 'message' }), contentType: 'appliaction/json; charset=utf-8' } } return { allowWebSocket: true } } ``` node.js 客户端代码: ```js import WebSocket from 'ws' function run () { const ws = new WebSocket('ws://127.0.0.1:3000/') ws.on('close', (code, reason) => { console.log('close:', code, `${reason}`) }) ws.on('error', (err) => { console.error('error: ', err) }) ws.on('upgrade', () => { console.log('upgrade') }) ws.on('ping', () => { console.log('recv ping message') }) ws.on('pong', () => { console.log('recv pong message') setTimeout(() => { ws.ping() }, 1000) }) ws.on('unexpected-response', (ws, req, res) => { // 非 upgrade 响应和 3xx 重定向响应认为是 unexpected-response console.log('recv unexpected-response message') }) ws.on('message', (data) => { console.log('received: %s', data) }) ws.on('open', () => { ws.ping() ws.send('string data') ws.send(Buffer.from('buffer data')) }) } run() ``` ## 使用 `multipart/form-data` 提交表单数据(文件) 云托管函数框架 支持前端以 `multipart/form-data` 格式提交表单内容,可以包含文件内容。 函数代码: ```js const path = require('path') const fs = require('fs') exports.main = async function (event, context) { // 从 event.file 属性上获取要保存的文件(与传参对应) // event.file 类型为 PersistentFile,见 https://www.npmjs.com/package/formidable#file // 如有其他传参,可用 form data 传参中的对应名称 event.[your param name] 获取,如 event.name, event.size 等 const file = event.file // 获取原始文件名 const fileName = file.originalFilename; // 保存文件的目录 const fileDir = path.join(process.cwd(), 'tmp') if (!fs.existsSync(fileDir)) { fs.mkdirSync(fileDir) } const filePath = path.join(fileDir, fileName) // 从参数中读取文件流 const readStream = fs.createReadStream(file.filepath) // 尝试保存文件到指定目录 try { await fs.promises.writeFile(filePath, readStream) } catch (error) { return { statusCode: 500, body: `Error saving file: ${error.message}` } } // 注意:删除临时文件 return { statusCode: 200, body: `File saved to: ${filePath}` } } ``` 发送上传文件请求: ```sh curl --location 'url' \ --form 'file=@file.png' ``` 注意: 1. 文件上传完成后会保存到本地文件,您应该在函数执行结束后删除文件,以免占用过多磁盘空间。 2. 上传的文件如需持久化,应该保存到云存储或其他服务中,以免文件丢失,避免保存在本地。 ## 使用 PUT 上传二进制数据或文件 函数代码: ```js const path = require('path') const fs = require('fs') exports.main = async function(event, context) { const { httpContext } = context const { url } = httpContext // 从 query 中获取文件名 const filename = new URL(url).searchParams.get('filename') // 保存文件的目录 const fileDir = path.join(process.cwd(), 'tmp') if (!fs.existsSync(fileDir)) { fs.mkdirSync(fileDir) } // 保存文件的路径 const filePath = path.join(fileDir, filename) try { // 从 event 中获取文件内容 const buffer = Buffer.from(event, 'binary') await fs.promises.writeFile(filePath, buffer); return { statusCode: 200, body: `File saved to: ${filePath}`, }; } catch (error) { return { statusCode: 500, body: `Error saving file: ${error.message}`, }; } } ``` 发送上传文件请求: ```sh curl --location --request PUT 'url?filename=file.png' \ --header 'Content-Type: application/octet-stream' \ --data 'file.png' ``` ## 使用 `puppeteer` 实现网页截屏 ```json { "name": "example", "version": "1.0.0", "main": "index.mjs", "type": "module", "dependencies": { "puppeteer": "^23.11.1" } } ``` ```ts import * as path from 'path' import * as url from 'url' import * as fs from 'fs' import puppeteer from 'puppeteer' const __filename = url.fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) async function screenshotWebPage(webPageUrl, savePath) { const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }) const page = await browser.newPage() await page.goto(webPageUrl) await page.pdf({ path: savePath, format: 'letter', }) await browser.close() } export const main = async function (event, context) { const webPageUrl = 'https://docs.cloudbase.net/cbrf/intro' const savePath = 'cbrf-intro.pdf' await screenshotWebPage(webPageUrl, savePath) return { statusCode: 200, headers: { 'Content-Disposition': `attachment; filename=${savePath}`, 'Content-Type': 'application/pdf', }, body: fs.createReadStream(path.join(__dirname, savePath)) } } ``` --- # 云托管/函数型开发框架/对比其他方案 > 当前文档链接: https://docs.cloudbase.net/cbrf/vs ## 对比云托管 `云托管函数框架` 在云托管的基础上提供了函数式代码开发框架,相比直接使用云托管优势在于: 1. **函数**:`云托管函数框架` 支持云托管中部署函数式代码,更适合函数式编程风格,更容易编写 2. **稳定**:`云托管函数框架` 框架内部内置 Graceful优雅重启、并发控制、超时控制、参数优化、日志等能力,开发者可以更聚焦业务逻辑编写 3. **日志**:`云托管函数框架` 框架提供日志能力,相比直接使用云托管需要自行处理日志更方便 4. **易用**:`云托管函数框架` 提供了更多的开发辅助功能,如 cli 工具,本地调试、热重启、函数间路由和代码复用等 5. **底座**:`云托管函数框架` 基于云托管,支持云托管的所有功能,如自动扩容、自动伸缩等 ## 对比云函数 相比云函数,云托管函数框架 在运行时、触发方式、部署、能力等方面有一些不同。云托管函数框架 可以支持 `SSE、WebSocket` 等长连接能力,支持 `Stream` 流式响应,支持文件上传下载能力,支持单实例多函数,对数据库长连接场景友好,开发调试更便利,调用耗时更短 等特性。下表对比了 `云托管函数框架` 和 `云函数` 的主要区别: ### 运行时 | 差异项 | 云函数 | 云托管函数框架 | | ------------------ | ------------------------------------------------ | ---------------------------------------------------------------------------- | | **并发模型** | 单实例单并发 | 单实例多并发 | | **HPA模式** | 云函数平台根据并发数量HPA,可预置并发 | 基于部署平台HPA能力,如k8s容器平台可以基于CPU/MEM配置HPA | | **内存限制** | 支持MEM大小限制 | 通过运行平台控制 | | **CPU限制** | 不感知CPU核数 | 感知CPU核数,通过运行平台控制 | | **超时控制** | 支持函数执行超时控制,超时后可中断执行 | 支持函数调用超时控制,超时后函数代码可继续执行 | | **调试能力** | 本地调试较难 | 框架支持本地运行,支持热重启,调试更容易 | | **开发体验** | 本地开发不便 | 本地开发较方便,提供 `ts` 类型支持 | | **日志** | 提供调用记录、高级日志 | 提供访问日志、高级日志,访问日志面向用户提供更多信息 | | **依赖安装** | 支持云端 npm 安装依赖 | 支持云端 npm、yarn、pnpm 安装依赖 | | **运行时版本** | 平台提供的指定 node.js 运行时版本 | 可使用满足最低要求的任意 node.js 运行时版本 | | **冷启动** | 存在冷启动,耗时较短,可通过预置并发降低冷启动率 | 存在冷启动,耗时较长,可通过配置最小实例数避免缩容到0产生冷启动 | | **常驻实例** | "不支持"(不感知函数实例) | 感知实例概念,实例可以自定义是否常驻 | | **预置并发** | 支持 | 无此概念,类似于手动扩容 | | **长连接能力** | 不支持 | 支持 SSE、WebSocket 方式的长连接能力 | | **连接数据库** | 基于单实例单并发模型,对数据库长连接不友好 | 跟普通 node.js 程序无区别 | | **异步函数** | 支持,静态配置无法动态切换,限制执行时长 | 支持,函数代码自行控制是否异步执行 | | **异步任务** | 部分版本支持 | 支持,函数代码自行控制是否异步执行 | | **文件上传** | 不支持文件上传 | 支持 `form-data` 等 HTTP原生二进制 上传能力 | | **文件下载** | 不支持文件下载 | 支持文件下载 | | **流式响应** | 不支持 | 支持 Stream 流式响应 | | **内置路由** | 无 | 框架内置路由,支持多函数启动,函数间共享代码更方便,可以在函数间共享内存数据 | | **单实例多函数** | 无此概念 | 支持,可在单个实例内运行多个函数,便于进行代码共享及工程管理 | ### 触发方式 | 分类 | 云函数 | 云托管函数框架 | | ---------------- | ----------------------- | ------------------------------------------------------------------ | | **触发方式** | 通过云开发SDK、HTTP、定时器触发 | 原生HTTP调用 或 `wx.cloud.callContainer`,支持获取 HTTP 请求上下文,目前不支持定时器触发 | | **链路差异** | 链路耗时相对较长 | 链路耗时较短 | ### 部署 | 分类 | 云函数 | 云托管函数框架 | | -------------- | ---------------------------- | ------------------------------------------------------- | | **平台依赖性** | 仅支持部署到云开发云函数平台 | 可以部署到任意支持运行 `Node.js` 的环境中,支持本地运行 | --- # 云托管/函数型开发框架/CLI工具介绍 > 当前文档链接: https://docs.cloudbase.net/cbrf/cli ## CLI工具介绍 当前存在两个 CLI 工具,以下为两个工具的介绍: * `tcb` - `@cloudbase/cli` 云开发统一 CLI 工具,提供 云托管函数代码 部署、下载、运行 等能力,主要是与云端进行交互。参考文档:[tcb cloudrunfunction](/cli-v1/install) * `tcb-ff` - `@cloudbase/functions-framework` 云函数框架 CLI 工具,该框架为云函数运行时,提供函数代码运行能力,`tcb` 命令运行 函数代码 也是通过调用 `tcb-ff` 来实现的。参考文档:[tcb-ff](./tcb-ff-help.md) 两个 `cli` 工具的关系:`tcb` 通过 `tcb-ff` 来运行函数代码,并额外提供了与云端交互的能力,进行函数代码的部署。推荐使用 `tcb`。 --- # 云托管/函数型开发框架/常见问题 FAQ > 当前文档链接: https://docs.cloudbase.net/cbrf/faq ## 产品规划 ### 目前提供云托管函数框架 后,后续会如何处理云函数或云函数 1.0,是否会下线云函数1.0 ? 云托管函数框架 提供了一套与云函数不同的运行机制,在使用体验、运行感受上各有特性。 两者产品能力均会长期存在,不会由于提供了云托管函数框架 而在后续取消下线云函数。 ## 计量、费用相关 ### 云托管函数 如何计费 当前云托管函数 使用了云托管实例方式运行,会根据实例的规格、运行时长来进行计费。使用云托管函数框架 时的计费项包括: * 核x小时:实例启动后,实例使用的核数运行一个小时,计为 1 核x小时,或称为核时;例如:1核的1个实例运行一个小时,计为 1 核*小时; * GBx小时:实例启动后,实例使用的内存运行一个小时,计为 GBx小时,或称为GB时;例如:2GB内存的1个实例运行一个小时,计为 2 GB*小时; * 调用次数:通过callContainer方法,从小程序端或 web 端调用云托管函数,会计入调用次数,跟随云托管整体套餐一同计量;**通过云托管函数 提供的公网地址发起的调用,将不记调用次数** * 出流量:从函数实例内流出的流量,均会计为出流量;例如从函数内向外上传文件、发起请求的请求包,均为出流量;函数内下载文件,发起请求的响应包,为入流量,不计入计费; ### 使用云托管函数框架 时,为什么需要开启云开发环境的超限按量 目前云托管函数 的计费项均未包含在云开发套餐中,而是通过按量计费的方式收取。在云开发环境启用超限按量后,按量计费的计费项才可正常启用。云托管函数 依赖计费项正常启用后,服务才可正常启动运行,因此需要函数所在的云开发环境启用超限按量。 --- # 云托管/最佳实践/解决时区不一致问题 > 当前文档链接: https://docs.cloudbase.net/run/best-practice/fix-timezone ## 问题背景 容器系统时间默认为 UTC 协调世界时间 (Universal Time Coordinated),与本地所属时区 CST (上海时间)相差 8 个小时。当需要获取系统时间用于日志记录、数据库存储等相关操作时,容器内时区不一致问题将会带来一系列困扰。 ## 操作步骤 在构建基础镜像或在基础镜像的基础上制作自定义镜像时,在 Dockerfile 中创建时区文件即可解决单一容器内时区不一致问题,且后续使用该镜像时,将不再受时区问题困扰。 1. 打开 Dockerfile 文件。 2. 写入以下内容,配置时区文件。 ``` FROM centos RUN rm -f /etc/localtime \ && ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone ``` 3. 重新构建容器镜像,使用新的镜像重新部署。或直接上传含新的 Dockerfile 的代码包重新部署。 --- # 云托管/最佳实践/将现有服务迁移到云托管 > 当前文档链接: https://docs.cloudbase.net/run/best-practice/migration 大多数后台服务,通常包含以下组件: - 服务本体 - 持久化服务(各类数据库、文件存储) - 基础设施(如消息队列、服务注册发现中心、监控系统、日志系统等) ## 迁移服务本体 CloudBase 云托管适用于部署**无状态的容器化服务**,您需要将您的服务改造为此种类型。 ### 无状态服务 无状态服务即服务在处理单个请求时,不需要持久性地保存上下文,以保证服务可以做到任意**横向扩容**。 无状态服务的每个服务节点之间是完全等价的,请求可能会由随机的任意节点进行处理,并且节点可能会被动态地销毁、重建、扩容,所以您**不应该在节点上保存任何状态**,例如: - 使用本地内存储存 HTTP Session; - 使用本地文件储存数据; - 业务逻辑中使用某个节点的 IP。 如果您有以上的需求,可以考虑如下解决方法: - 使用 Redis 等外部数据库储存 HTTP Session; - 使用 CFS、对象存储等外部服务保存文件; - 使用服务对外 URL。 ### 容器化 CloudBase 云托管只能部署基于 Docker 容器的应用,为了将服务封装到容器中,您应该使用 Dockerfile 来定义您的应用运行环境。 ### 使用标准输出打印日志 CloudBase 云托管会自动收集您应用产生的标准输出,并提供服务日志查询功能。 ## 迁移基础设施 云托管应用可以通过其所在的 VPC 访问任意云上资源,如果您的基础设施已经部署在腾讯云内,则只需要打通 VPC 即可让您的服务访问您的基础设施。 --- # 云托管/使用限制 > 当前文档链接: https://docs.cloudbase.net/run/limitation ## 按量使用 云托管服务由于目前使用的按量计费模式,相关 CPU、MEM 用量未包含在云开发环境的套餐中。因此在使用云托管服务时,需要开启环境的超限按量服务,以便云托管可以通过按量模式计量及计费。如果关闭了环境的超限按量,会导致被停止服务。 ## 环境隔离 云开发多个环境之间所有资源都互相隔离; 云托管环境内的服务之间可以通过内网调用; 不同环境的资源之间只能使用公网调用; ## 服务及实例相关数量限制 当前在一个云开发环境下,可以创建 15 个服务。 当前在一个云开发环境下,可以最大启动运行 28 个实例。这些数量包含了所有服务启动的实例。达到最大值后无法自动扩容出更多实例。 当前如果需要提升限制,可以[提交工单](https://console.cloud.tencent.com/workorder/category?level1_id=536&level2_id=821&source=14&data_title=%E4%BA%91%E5%BC%80%E5%8F%91%20CloudBase&step=1)进行申请。 ## QPS 限制 云开发环境下的云托管所有服务,同样受限于环境维度的总 QPS(每秒并发请求数)限制。具体环境的 QPS 限制值,可以通过套餐规格查询。 ## 云托管请求超时时间:60s ## 云托管请求包体大小:20M --- # 云托管/概念说明 > 当前文档链接: https://docs.cloudbase.net/run/related ## 私有网络/云托管服务网络 ### 背景知识 关于什么是私有网络(VPC)及子网,请参见[私有网络](https://cloud.tencent.com/document/product/215) 文档。 云托管服务同样在独立的私有网络中。 ### 使用限制 - 同一个环境中的云托管服务,处于独立的私有网络中,可以**通过内网域名相互调用**,不产生公网流量费用,时延更短。 - 不同环境之间的服务无法通过内网互通,仅可以通过公网地址互相访问。 - 云托管服务所在的私有网络,与对应的腾讯云账号中的私有网络隔离。需要使用私有网络配置来打通云托管服务的私有网络与腾讯云账号中的私有网络。 ## 域名备案 ### 背景知识 关于什么是域名备案及如何进行域名备案,请参见[备案](https://cloud.tencent.com/document/product/243) 文档。 ### 使用限制 - 云开发环境、或云托管服务需要绑定的自定义域名,均需要完成域名备案; - 如果不是在腾讯云上完成的备案,需要在腾讯云上进行接入备案; ## 镜像仓库 ### 背景知识 云托管使用的是容器镜像服务(TCR)个人版,没有费用。 您可以在服务详情页面中的「镜像」选项卡中,快速查看当前服务所绑定的镜像仓库中所有镜像和它们的使用情况。 更多关于腾讯云镜像仓库的介绍,请参见 [容器镜像服务](https://cloud.tencent.com/document/product/1141) 文档。 ### 使用限制 - 服务在创建时会绑定一个唯一的腾讯云镜像仓库,管理该服务下的所有版本的相关镜像。 - 不支持同一服务绑定多个镜像仓库,使得不同版本的镜像分散在多个不同的镜像仓库里。 - 所有由云托管创建的镜像仓库都会统一存放于以 “tcb” 开头的同一个命名空间之下,并以环境 id\_ 服务名的格式命名。您可以在 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) 上看到由云托管创建的这个镜像仓库并对它进行管理。 ### 用法推荐 “使用默认仓库(推荐)”:云托管将使用容器镜像服务个人版,在创建服务时自动为您创建一个与服务同名的私有仓库并与您的服务进行绑定。 “绑定已有腾讯云镜像仓库”适用以下场景: 1. 跨场景复用 在云托管的场景之外,您已经使用了腾讯云的镜像仓库,并希望里面的镜像继续用于云托管的某个服务。此时,在创建服务时,可以选择 “绑定已有腾讯云镜像仓库” ,指定任意您账户下的私人镜像仓库。 请注意,若如此做,则您通过云托管产生的镜像(手动上传或自动构建推送),可能也会影响您在其他场景下的镜像使用。 1. 容量限制 个人版容器镜像服务存在相关仓库数限制,当触碰到此限制且不方便进行个人版清理时,可以切换为使用已有的企业版仓库;企业版仓库相关限制可以查询具体镜像仓库说明。 ## 服务域名 - 公网域名:公网任意来源可以用来对服务进行 HTTPS 调用,会产生相应的公网流量费用。 - 内网域名:仅同一环境内其他云托管服务访问,不产生公网流量费用。 - 允许公网通过服务域名访问服务:关闭后,通过默认公网域名/自定义公网域名访问服务将报错。当您希望服务仅内网可访问,可选择关闭。无论是否开启,不影响通过内网域名访问服务。 :::tip 提示 - 关闭“允许公网通过服务域名访问服务”后,若内网域名也同时没有被调用,则从任何角度都没有流量会打到当前服务,会引发服务下所有版本缩容至最小副本个数。 - 同一环境内,通过内网域名访问速度会高于通过公网域名访问。 ::: ### HTTP配置 在服务域名之外,可选择启用 HTTP 网关。 #### 背景知识 关于云开发 HTTP 网关的介绍,请参见[HTTP 网关文档](/service/introduce)。 #### 使用限制 - 是否启用、如何配置 HTTP 网关,均不影响对服务域名的使用。 - 关闭“允许公网通过服务域名访问服务”开关,不影响公网通过在 HTTP 网关中配置的地址访问服务。 - 公网通过在 HTTP 网关中配置的地址访问服务,会产生相应的公网流量费用。 ## 资源互联 ### 应用场景 需要跟自己账号vpc下资源网络打通 ### 使用限制 - 环境纬度支持同地域 - 带宽限制 5Gbps - 授权遇到:Error:InvalidParameter,vpc cidr conflict with others 时说明 cidr 冲突,需要更换自己 vpc 的 cidr 再次尝试 --- # 云托管/常见问题/概述 > 当前文档链接: https://docs.cloudbase.net/run/faq/index - [计费相关](./fee.md) - [构建失败相关](./build.md) - [部署失败相关](./deploy.md) - [服务相关](./server.md) - [网络相关](./network.md) - [http访问相关](./gw.md) --- # 云托管/常见问题/计费相关 > 当前文档链接: https://docs.cloudbase.net/run/faq/fee ### 云托管如何收费 云托管如今在两个不同环境中提供服务: * 微信云托管 * 云开发中的云托管 微信云托管服务采用按量计费,根据实例启动后占用的 CPU 及 内存进行计量,并根据计量情况,每日进行结算并在次日产生账单并扣费。 具体的定价信息可见[微信云托管计费说明](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloudrun/src/Billing/price.html)。 云开发中的云托管同样根据实例启动后的 CPU 及内存使用进行计量,但是目前会换算为云开发中统一的计算资源使用量。计算资源使用量按:环境套餐-资源包-按量 的方式扣量。 具体的信息可见[计算资源使用量计量说明](https://cloud.tencent.com/document/product/876/120342)。 --- # 云托管/常见问题/构建失败相关 > 当前文档链接: https://docs.cloudbase.net/run/faq/build 构建阶段问题多数为 docker 报错,绝大部分是因为没有正确配置 Dockerfile。 可参见优化容器镜像,或在网上搜索 docker 相关解决方案及教程。 ### 遇到报错信息“network connection aborted.”如何处理? 因为访问国外网络不稳定的不可抗力因素,建议尽量选用国内站作为镜像源下载依赖和扩展。 ### 缺少依赖报错如何处理?例如:java.lang.NoClassDefFoundError: Could not initialize class xxx Dockerfile 中缺少对应的依赖安装命令。 建议优先换用更完整的构建基础镜像而非直接增加依赖安装命令(RUN apk xxx),更加简单方便,且可以提高构建速度。 请到 GitHub-Containers 的 dockerhub 官方仓库 自行选择合适的基础镜像(默认已包含了所有您需要的依赖),然后在 Dockerfile 中替换“FROM xxxxx as build” 命令。 ### 遇到报错信息“xxxxxx: no such file or directory”如何处理? 请先检查如下的配置信息: * 确保代码仓库/代码包中确实存在该文件且路径正确。 * 确保 .gitignore 或者 .dockerignore 中,没有包含这个文件。如果包含将会被构建忽略。去除即可。 * 确保 Dockerfile 中的 COPY 命令,拷贝文件的时候没有修改原有的文件路径。 ### 遇到报错:Not Found Project 如何处理? * 请检查输入的代码仓库路径是否存在 ### 无报错日志显示空白该如何处理? 大概率是构建超时(> 10分钟)。请优化容器镜像,提高构建速度,防止超时。 具体可参考[优化容器镜像](https://docs.cloudbase.net/run/develop/image-optimization)。 ### 遇到报错信息“Composer could not find a composer.json file in /var/www/html”如何处理? 这个错误表明 Composer 在当前目录找不到 composer.json 文件 * 请检查 composer.json 是否在正确的目录 * 注意文件拼写(注意是 .json 不是 .jon) * composer.json 文件存在且有读取权限 --- # 云托管/常见问题/部署失败相关 > 当前文档链接: https://docs.cloudbase.net/run/faq/deploy ### 本地运行正常,部署到云托管后却部署失败? 本地调试请尽量基于本地 Docker 进行,如果本地开发调试未基于 Docker,直接转到云托管部署时可能遇到以下情况: * 本地运行的时候,各种依赖组件、扩展在本地都是齐全的,但是没有在 Dockerfile 正确添加依赖安装命令,所以本地运行时不缺依赖没问题,部署到云托管上时就会因为缺乏依赖导致报错。 * 本地运行的时候,连接的是本地数据库(地址为本机 IP 或 localhost),部署到云托管上时没有将数据库地址改为云上的数据库地址。导致因为数据库无法连接而部署失败。 * 本地使用的运行时环境(JDK/python 版本等),与 Dockerfile 中定义的运行时环境不一致。 ### 遇到报错信息“Readiness probe failed: dial tcp xx.xx.xx.xx:xxx: connect: connection refused”如何处理? 无法通过健康检查/端口调用不通。 * 服务启动正常,但是端口填写错误,真实端口与发布时填写端口不符,导致系统误认为部署失败。 * 服务启动正常,但是没有监听任何 0.0.0.0 的本地端口,导致系统认为部署失败。 * 服务启动成功,但反复重启,进程不稳定,导致端口无法稳定调通,无法通过健康检查。请自行结合日志排查代码问题,或考虑是否因为所选容器规格过小导致内存 OOM。 * 如果您的 Dockerfile 中包含了多行独立的 CMD 命令,请注意这是错误的写法,只有最后一行 CMD 命令会被执行,之前的都会被忽略,导致业务报错。请参见 Docker 官方文档之 CMD 命令。 * 检查是否在 Dockerfile 中暴露的端口跟服务配置的端口不一致。 您可以在本地调试时,使用 netstat 等命令查看本地监听的端口和云托管配置的端口是否一致。 ### 部署失败但看业务日志已经在正常运行? 检查服务设置中端口与真实项目端口是否一致。如果服务启动正常,但是**端口填写错误**,真实端口与发布时填写端口不符,会导致系统误认为部署失败,调用服务也会报错。 ### 遇到报错信息“check pod status is not ok”如何处理? 请先检查如下信息: * 服务因代码原因反复重启,状态不稳定。请自行排查代码问题。 * 若服务并未反复重启,请提工单联系我们排查。 ### 遇到报错信息“Back-off restarting failed container”如何处理? 代码原因导致服务启动失败,请借助服务日志自行排查代码问题。没有采集到服务日志,请检查服务的日志采集路径是否配置正确。 ### 为什么部署 eggjs 服务,运行 npm run start 不停重复启动? 需要去掉进程守护参数 --daemon。 * 更改前:"start": "egg-scripts start --daemon --title=node-server", * 更改后:"start": "egg-scripts start --title=node-server" ### 为什么 Dockerfile 中引用了环境变量不生效? 构建或部署阶段,服务还未就绪,此时无法引用环境变量。 ### Dockerfile 中引用了 MySQL/Redis 等其他云产品的内网地址不生效? 构建/部署阶段,服务还未就绪,此时无法通过内网地址访问其他云产品。 ### 遇到报错信息“err=[ResourceUnavailable.ClusterInAbnormalState][err=cloudrun cluster state is invalid(cluster state is deleted)]”如何处理? 底层异常,请提工单联系我们处理。 ### 小程序内嵌 webview 的方式,是否支持云托管内网地址调用? 不支持,小程序的客户端内网和云托管服务端内网不是一个网络空间,不能相互调用。 ### 流水线执行正常,但是服务无法启动如何自行排查? - 查看服务启动日志是否有明显报错 - 客户可在本地使用 docker 部署验证程序启动是否有问题 ### 访问服务报错 CERTIFICATE_VERIFY_FAILED 参考 [使用注意事项](https://developers.weixin.qq.com/minigame/dev/wxcloudrun/src/guide/weixin/open.html#%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9),需要客户服务Dockerfile、客户代码中信任证书 ### 本地监听 127.0.0.1:80 服务正常,部署到云托管无法启动 修改 0.0.0.0:80 监听端口后,重新部署服务。 ### 基于镜像部署,服务启动报错 "exec /bin/sh: exec format error" - 云托管容器仅支持amd的镜像,如果是arm的镜像会报这个错误,可以自行构建amd的镜像 或者选择开源的amd类型的镜像 - 客户可以通过 docker inspect "镜像仓库地址" | grep Architecture 来看镜像的类型 ### 服务部署启动报错 " xxxx recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0" - 服务使用 Java 17 (版本 61.0) 编译的,但你尝试使用 Java 8 (版本 52.0) 的环境来运行它。低版本的 Java 运行时无法加载高版本 Java 编译器生成的类文件 - 可以检查Dockerfile 中Java的版本要与服务代码中 pom.xml 中Java的版本一致 ### CLI部署云托管后调用失败 "Error: cloud.callContainer:fail Error: errCode: -501000 | errMsg: Your server is Forbidden For CallContainer." - 检查CLI版本是否为最新,参考 [CLI 从 v0 升级到 v1](/cli-v1/migrate) --- # 云托管/常见问题/服务相关 > 当前文档链接: https://docs.cloudbase.net/run/faq/server ### 创建服务报错镜像仓库数量超限或镜像仓库命名空间数量超限 需要用户登录腾讯云tcr控制台-选择广州地域-个人版共享实例(首次登录需要根据提示初始化个人镜像仓库账户),删除不必要的镜像仓库或者命名空间,删除后重试创建云托管服务即可 ### 点击云托管遇到“服务冻结提示” 云托管对于长时间不活跃的用户资源会进行冻结,冻结之后服务会无法访问,可以根据弹窗指引自助恢复解冻,解冻之后需要重新部署版本才可以使用,旧的版本无法继续使用 ### 部署新版本后,云托管旧版本实例为什么不会自动销毁? 创建新版本实例后,旧版本实例不能立即缩容,否则已有连接和新版本未成功启动前的流量都会报错。 如果线上存在多个版本,且均有流量,将不会自动缩容。 实例缩容需满足以下两个条件: - 三十分钟无流量(最小副本数设置为0才生效) - 下线延迟缩容(版本无流量) 需要注意的是:配置的最大副本数控制的是**单个**版本的pod,如果有多个版本存在,则: `最大pod数` = `配置的最大副本数` × `版本数` 通过手动删除旧版本也可实现强制缩容。 ### 从监控看CPU和内存使用率均未到达设置的扩容限制,云托管为什么会自动扩容? 监控中您可以看到CPU和内存使用率。但是监控中的内存使用率,并不包含Page Cache。 Page Cache中的数据是也是占用内存的,如使用率一直在设置值以上,将导致无法缩容。 了解Page Cache: - 在Kubernetes中,Pod内存中的Cache通常是指应用程序使用的Page Cache。 - 当应用程序读取或写入文件时,Page Cache会缓存文件数据和元数据,以提高文件访问的性能。 - 如果Pod中的应用程序频繁读写大量的文件,Page Cache可能会占用大量的内存,导致Pod的内存使用率过高 ### 微信云托管调用失败Error: cloud.callContainer:fail #### 具体报错:Your server is Forbidden For CallContainer 如果是使用cli创建的服务版本,可以通过指令查询一下cli版本号 ``` tcb -v ``` 如果版本号低于2.11.X,可以升级一下版本 ``` npm uninstall @cloudbase/cli -g # 先卸载 npm install @cloudbase/cli -g # 再安装 ``` 如果不是cli创建的服务版本,可以参考[文档](https://docs.cloudbase.net/error-code/service/SERVICE_FORBIDDEN) --- # 云托管/常见问题/网络相关 > 当前文档链接: https://docs.cloudbase.net/run/faq/network ### 云托管如何访问 Lighthouse Lighthouse(轻量应用服务器)默认与普通 VPC 互不互通。要通过内网访问 Lighthouse,需要完成以下配置: ```mermaid flowchart LR RUN[云托管服务实例] -->|已关闭公网访问| VPC[您的 VPC] VPC <-->|关联 VPC| LHN[Lighthouse 网络空间] LHN --> LH1[Lighthouse 实例 1] LHN --> LH2[Lighthouse 实例 2] RUN -. 未关闭公网访问时 .-> PLAT[平台公网] style PLAT stroke-dasharray: 5 5,stroke:#999 ``` 1. **在 Lighthouse 控制台建立关联**: - 进入 Lighthouse 控制台 → 「**网络 → 关联 VPC**」,选择云托管已绑定的 VPC,并分配一个用于关联的 IP 段。 - 关联完成后,Lighthouse 实例会获得 VPC 内的内网 IP。 2. **云托管侧开启「关闭公网」**: - 在「服务详情 → 服务设置 → **网络设置**」中,「**公网访问**」选项与「**私有网络**」选项**联动**:必须先打开「私有网络」(绑定 VPC),才能将「公网访问」关闭。 - **必须重新发布版本才能生效**,新启动的实例才会走 VPC 内网。 3. **配置防火墙与路由**: - 在 Lighthouse 防火墙中放通来源 VPC 网段。 - 确认云托管绑定的 VPC 路由表、安全组已正确放通。 4. **使用内网 IP 访问**: - 在服务代码中使用 Lighthouse 实例的**内网 IP**(非公网 IP)访问。 > 注意:未关闭「公网访问」时,由于 Lighthouse 的内网 IP 段不在云托管绑定 VPC 的自身网段内,访问流量会被错误地发往平台公网,导致无法访问 Lighthouse 内网。 ### 云托管如何访问对等连接和 CCN 中的 VPC 服务 要通过内网访问**对等连接**或**云联网(CCN)**打通的对端 VPC 内的服务(包括同账号、跨账号、跨地域),需要完成以下配置: #### 网络拓扑 - **对等连接打通(同/跨账号)**: ```mermaid flowchart LR RUN[云托管服务实例] -->|已关闭公网访问| VPCA[VPC-A] VPCA <-->|对等连接| VPCB[VPC-B] VPCB --> DB[对端 CVM/数据库] ``` - **云联网 CCN 打通(多账号多 VPC)**: ```mermaid flowchart TB CCN[("云联网 CCN")] subgraph A账号 RUN[云托管服务] --> VPCA[VPC-A] end subgraph B账号 VPCB[VPC-B] --> DB1[资源] end subgraph C账号 VPCC[VPC-C] --> DB2[资源] end VPCA --- CCN VPCB --- CCN VPCC --- CCN ``` #### 1. VPC 侧打通 - **对等连接**:在 [VPC 控制台](https://console.cloud.tencent.com/vpc/vc?rid=4) 中建立对等连接,双方 VPC 路由表互相添加对端 CIDR,指向对等连接;安全组放通对端网段。 - **云联网(CCN)**:创建 CCN 实例,将各 VPC 关联到 CCN(跨账号需 CCN 所有者审批),并配置各 VPC 内资源的安全组。 > 各方 VPC 的 CIDR **不能重叠**,否则无法建立对等连接 / CCN 关联。 #### 2. 云托管侧开启「关闭公网」 - 云托管服务先绑定本地 VPC。 - 进入「服务详情 → 服务设置 → **网络设置**」,「**公网访问**」与「**私有网络**」联动:必须先开启「私有网络」(绑定 VPC),才能关闭「公网访问」。 - **必须重新发布版本才能生效**。 > 未关闭「公网访问」时,云托管只会把目的 IP 落在绑定 VPC 网段内的流量送入 VPC,对等连接 / CCN 打通的对端 VPC 网段不在本地 VPC 内,流量会被送往平台公网,导致内网互通失败。 #### 3. 验证访问 - 服务代码中使用对端 VPC 资源的**内网 IP** 进行访问。 - 若仍无法连通,请按以下顺序排查: 1. 是否已关闭「公网访问」并完成版本发布; 2. 双方 VPC 路由表是否都加了指向对等连接 / CCN 的对端 CIDR; 3. 对端资源安全组是否放通了云托管所在 VPC 的网段; 4. 两端 VPC CIDR 是否存在重叠。 > 重要:「关闭公网」控制的是服务实例的**出向**流量路径,**并不会影响通过公网域名从外部访问该服务**。 更多内容可参考[内网互联](../deploy/networking/internal-link) 与[公网出访](../deploy/networking/egress) 文档。 --- # 云托管/常见问题/HTTP 访问相关 > 当前文档链接: https://docs.cloudbase.net/run/faq/gw ### HTTP 访问报错如何排查 判断http状态码是云托管网关返回的,还是业务服务返回的。可以查看请求返回头是否有 X-Cloudbase-Upstream-Status-Code 头,如果有该头并且值和http状态码一致,说明状态码是业务服务返回。 另外如果返回报有具体的错误码,可通过云开发http访问错误码文档排查具体原因。 具体的错误码可见[HTTP 网关错误码](https://docs.cloudbase.net/error-code/service)。 ### websocket 连接中断问题排查 网关层会对空闲连接进行回收,因此 websocket 连接建立后如果长时间没有数据传输,网关会主动关闭非活跃的连接,建议客户端或者服务端每隔10s发送一个心跳包; 另外客户端需要做断线重连,防止网络波动或者网关层服务重启导致的中断。如果已经做了心跳保持机制并且 websocket 连接频繁中断,可以联系我们工程师进行排查。 ### X-Forwarded-For 和 X-Real-IP 获取不到真实的客户端 IP * 尝试使用 X-Original-Forwarded-For 字段来解析获取 * 自定义 header 获取 ### 云托管是否支持 SSE 支持 * 可以通过浏览器直接访问 SSE 地址 * 可以通过 Nodejs 代码 ```javascript // 1. 先安装依赖 npm install eventsource // 2. 将下面代码命名为 main.js,执行 node main.js const {EventSource} = require('eventsource'); const eventSource = new EventSource('你的服务域名或者自定义域名'); // 注意路径是否正确 eventSource.onopen = () => { }; eventSource.onmessage = (event) => { console.log('Received message:', event.data); }; eventSource.onerror = (error) => { }; ``` * 您还可以通过更多的语言来实现 SSE 的交互 --- # 云托管/常见问题/第三方包使用声明 > 当前文档链接: https://docs.cloudbase.net/run/faq/third-packages 当您在云托管上使用第三方代码包时(第三方代码包,包括但不限于任何非微信、腾讯云提供的SDK、软件包、运行时runtime等),云开发、云托管平台不对第三方代码包承担任何责任。 您需要自行承担使用第三方代码包可能产生的风险和后果,您应该自行确保在使用第三方代码包时符合适用法律法规和相关许可协议的规定。 --- # hosting Documentation > 云开发静态网站托管服务,提供CDN加速、自定义域名、HTTPS证书等Web应用托管功能 # 静态网站托管/概述 > 当前文档链接: https://docs.cloudbase.net/hosting/introduce CloudBase 静态网站托管可以为您的 Web 应用、静态资源提供快速、安全的托管服务。只需要一个命令,并可以快速地部署静态资源,并且使用 CDN(内容分发网络)加快资源的访问速度。 ## 主要功能 ### 应用在线部署 CloudBase 静态网站托管支持多种部署方式,满足不同场景的部署需求: **四种部署方式:** - **上传代码包/文件夹**:快速上传本地项目文件,支持 ZIP 格式 - **从官方模板创建**:基于 React、Vue、Next.js、Nuxt 官方模板快速创建新项目 - **Git 仓库部署**:支持 Git 个人仓库及第三方公开仓库 - **CLI 命令部署**:使用 CloudBase CLI 在本地或 CI/CD 流水线中一键部署 **支持的构建配置:** - 10 种主流前端框架自动识别(React、Vue.js、Angular、Next.js、Nuxt.js、VitePress、Astro、Hugo、Hexo、其他) - 自定义安装命令和构建命令 - 选择 Node.js 版本(16.x、18.x、20.x、22.x、24.x) - 配置环境变量 - 设置输出目录 - 启用构建缓存 详情请参考 [在线部署指南](/hosting/web-hosting)。 ### HTTPS CloudBase 静态网站托管内置 HTTP 与 HTTPS,无需额外配置即可使用。 ### 自定义域名 CloudBase 静态网站托管支持自定义域名,用户可以通过使用您的私有域名访问静态资源,详情请参考 [自定义域名](/service/custom-domain)。 ### 快速分发 静态资源将会被缓存在遍布各地的 CDN 边缘服务器上。无论您的用户身处何处,内容都可快速加载。 ### 命令行部署 使用 CloudBase CLI 可在本地或 CI/CD 流水线中一键部署,自动完成 **安装依赖 → 构建 → 上传产物 → 绑定路由** 全流程: ```bash # 全局安装并登录 npm i -g @cloudbase/cli tcb login # 在项目根目录一键部署 tcb app deploy --framework vite -e my-env-id ``` 支持 React / Vue / Vite / Next.js / Nuxt / Angular / 纯静态等多种框架类型。详情参考 [CLI 安装文档](/cli-v1/install) 和 [应用部署命令](/cli-v1/app/management)。 ## 适用场景 静态网站托管适用于以下应用场景: - **单页应用(SPA)**:React、Vue、Angular 等前端框架构建的应用 - **静态网站**:个人博客、企业官网、产品展示页 - **文档站点**:VitePress、Docusaurus、Hexo 等生成的文档 - **静态资源**:图片、视频、字体等资源文件托管 ## 快速开始 1. [快速开始](/hosting/quick-start) - 5 分钟快速上手静态托管 2. [在线部署指南](/hosting/web-hosting) - 了解详细的部署方式和配置 3. [CLI 命令部署](/cli-v1/app/management) - 使用命令行一键部署应用 4. [自定义域名](/service/custom-domain) - 绑定您的专属域名 --- # 静态网站托管/快速开始 > 当前文档链接: https://docs.cloudbase.net/hosting/quick-start 本文档将指导您快速上手云开发静态网站托管服务。根据项目类型,静态托管提供**两种使用方式**: | 使用方式 | 适用场景 | 部署特点 | |---------|---------|---------| | **📁 文件管理** | 已构建好的静态文件(HTML/CSS/JS)、图片/字体等静态资源、第三方工具构建产物 | 直接上传文件,零构建流程 | | **🚀 应用部署** | 有安装依赖 + 构建流程的前端项目(React / Vue / Next.js / Nuxt / Vite 等) | 自动完成安装依赖 → 构建 → 上传产物 → 绑定路由 | > 💡 **如何选择?** 如果您只需要托管现成的静态文件,选择「文件管理」;如果您有前端源码需要构建后部署,选择「应用部署」。 ## 前置条件 在开始之前,请确保您已经: :::tip 说明 - 拥有腾讯云账号并完成实名认证 - 前往 [云开发平台](https://tcb.cloud.tencent.com/dev) 创建云开发环境 ::: --- ## 方式一:文件管理(静态资源托管) 适合已构建好的静态文件或静态资源托管场景。 ### 第 1 步:前往静态网站托管 前往 [云开发控制台 - 静态网站托管](https://tcb.cloud.tencent.com/dev#/static-hosting) ### 第 2 步:准备网站文件 创建一个简单的 HTML 文件作为示例,命名为 `index.html`: ```html 欢迎使用云开发静态网站托管

🎉 恭喜!

您的静态网站已成功部署到云开发平台

现在您可以开始构建您的网站了

查看文档
``` :::info 说明 您也可以准备其他类型的文件:CSS 样式文件、JavaScript 脚本文件、图片、字体等静态资源,或单页应用(SPA)的构建产物。 ::: ### 第 3 步:上传文件 #### 方式 A:控制台上传 1. 在静态网站托管页面,点击「上传文件」或「上传文件夹」按钮 2. 选择准备好的 `index.html` 文件或包含网站文件的整个文件夹 3. 点击「确定」完成上传(上传文件夹时系统会保持原有的目录结构) 上传文件 #### 方式 B:CLI 命令上传 使用 CloudBase CLI 通过命令行管理和部署静态网站文件,适合批量上传、自动化部署和 CI/CD 集成。 ```bash # 全局安装并登录(详见 CLI 安装文档) npm i -g @cloudbase/cli tcb login # 部署当前目录下的所有文件 tcb hosting deploy . -e <环境ID> # 部署指定文件或文件夹 tcb hosting deploy index.html -e <环境ID> tcb hosting deploy dist/ -e <环境ID> ``` > 更多命令(`list` / `delete` / `detail` 等)详见 [CLI 静态托管命令 →](/cli-v1/hosting) 和 [CLI 安装文档 →](/cli-v1/install) :::warning 注意事项 - 单个文件大小限制为 50MB - 文件名不能包含特殊字符,建议使用英文、数字、下划线和连字符 - 上传会覆盖同名文件,请谨慎操作 ::: --- ## 方式二:应用部署(前端项目构建部署) 适合有安装依赖 + 构建流程的前端项目(React / Vue / Next.js / Nuxt / Vite / Angular 等),平台会自动完成 **安装依赖 → 构建 → 上传产物 → 绑定路由** 全流程。 ### 第 1 步:选择部署入口 进入 [云开发控制台 - 静态网站托管](https://tcb.cloud.tencent.com/dev#/static-hosting),根据项目来源选择以下任一方式: - **上传代码包 / 文件夹**:上传本地前端项目源码(ZIP 或文件夹) - **从官方模板创建**:基于 React / Vue / Next.js / Nuxt 模板快速创建新项目 - **Git 仓库部署**:连接 GitHub / GitLab / Gitee / 工蜂等仓库,代码更新自动触发部署 - **CLI 命令部署**:使用 CloudBase CLI 在本地或 CI/CD 流水线中一键部署 ### 第 2 步:配置构建参数 在部署配置页面: 1. **项目框架**:从下拉列表中选择(支持 React、Vue.js、Angular、Next.js、Nuxt.js、VitePress、Astro、Hugo、Hexo 等 10 种主流框架),平台将**自动填充**对应的安装命令、构建命令和目标目录 2. **Node.js 版本**:选择构建环境版本(16 / 18 / 20 / 22 / 24) 3. **构建产物目录**:如 `./dist`、`./build`、`./out` 等 4. **部署路径**:默认 `/`,多项目场景建议使用子路径如 `/app1` 详细配置说明请参考 [部署指南](/hosting/web-hosting-guide)。 ### 第 3 步:使用 CLI 一键部署(可选) 如果您希望在本地或 CI/CD 中使用命令行部署,可使用 `tcb app deploy`: ```bash # 全局安装并登录 npm i -g @cloudbase/cli tcb login # 在项目根目录一键部署(自动完成安装、构建、上传、绑定路由) tcb app deploy --framework vite -e <环境ID> # 裸命令:交互式引导部署 tcb ``` > 更多参数和 `cloudbaserc.json` 零参数部署配置,详见 [CLI 应用部署命令 →](/cli-v1/app/management) --- ## 第 4 步:访问您的网站 两种部署方式完成后,均可通过默认域名访问。 ### 访问规则 静态资源的访问链接格式为:`默认域名/文件路径` - **默认域名**:`环境ID.xxx.tcloudbaseapp.com` - **默认索引文档**:`index.html` ### 访问示例 假设您的环境 ID 为 `my-env-123.xxx`,则访问链接如下: | 文件路径 | 访问链接 | 说明 | |---------|---------|------| | `/index.html` | `https://my-env-123.xxx.tcloudbaseapp.com` | 根目录默认页面 | | `/about/index.html` | `https://my-env-123.xxx.tcloudbaseapp.com/about` | 子目录默认页面 | | `/css/style.css` | `https://my-env-123.xxx.tcloudbaseapp.com/css/style.css` | CSS 文件 | | `/images/logo.png` | `https://my-env-123.xxx.tcloudbaseapp.com/images/logo.png` | 图片文件 | ### 验证部署 1. 复制您的默认域名 2. 在浏览器中打开该链接 3. 如果看到您上传的页面内容,说明部署成功 验证部署成功 ## 下一步 恭喜您成功部署了第一个静态网站!接下来您可以: - [在线部署指南](/hosting/web-hosting) - 了解更多部署方式和构建配置 - [部署配置详解](/hosting/web-hosting-guide) - 各框架构建参数、部署路径、环境变量配置 - [CLI 应用部署](/cli-v1/app/management) - 使用命令行自动化部署 - [配置自定义域名](/service/custom-domain) - 使用您自己的域名访问网站 - [CDN 加速配置](/hosting/cdn) - 优化网站访问速度 - [HTTPS 配置](/hosting/https) - 为网站启用安全连接 --- # 静态网站托管/应用部署/部署方式 > 当前文档链接: https://docs.cloudbase.net/hosting/web-hosting 静态网站托管支持三种部署方式:上传代码包/文件夹、从官方模板创建、Git 仓库部署。本文档介绍如何使用这些部署方式,配置构建命令、设置部署路径,以及常见前端框架的部署方案。 ## 部署入口 前往 [云开发控制台 - 静态网站托管](https://tcb.cloud.tencent.com/dev#/static-hosting) ## 部署方式 ### 方式一:上传代码包/文件夹 适用场景:本地已有项目代码,希望快速部署。 #### 上传代码包 **准备代码包:** 支持上传以下格式的代码包: - `.zip` 格式(推荐) **代码包要求:** - 代码包应包含完整的前端项目源代码 **部署步骤:** 1. 在「静态网站托管」页面,点击「新建部署」 2. 选择「上传代码包」 3. 点击「选择文件」,上传本地 ZIP 文件 4. 配置构建参数 [详见下文](./web-hosting-guide) 5. 点击「部署」 #### 上传文件夹 **部署步骤:** 1. 在「静态网站托管」页面,点击「新建部署」 2. 选择「上传文件夹」 3. 选择本地项目文件夹 4. 配置构建参数(详见下文) 5. 点击「开始部署」 :::tip 提示 上传代码包之前,可以先删除 `node_modules`、`.git` 等常见的非必要目录,避免上传文件过大,发生上传失败的情况。 ::: ### 方式二:从官方模板创建 适用场景:快速创建新项目,基于最佳实践模板开发。 **支持的官方模板:** 目前已覆盖四大主流前端技术栈,开发者可根据团队技术偏好快速创建项目: - **React Web 应用模板**:基于 Create React App 创建的标准 React 项目,适用于 SPA 应用、后台管理系统等场景 - **Vue Web 应用模板**:基于 Vue CLI 创建的标准 Vue 3 项目,适用于 SPA 应用、中后台系统等场景 - **Nextjs Web 应用模板**:基于 Next.js、React 和云开发 CloudBase 的 SSG 静态站点模板,适用于企业官网、博客、文档站等场景 - **Nuxt Web 应用模板**:基于 Nuxt 3、Vue 3 和云开发 CloudBase 的 SSG 静态站点模板,适用于内容型网站、营销落地页等场景 **部署步骤:** 1. 在「静态网站托管」页面,点击「新建部署」 2. 选择「从模板创建」 3. 选择模板类型(React / Vue / Next.js / Nuxt) 4. 配置构建参数 [详见下文](./web-hosting-guide) 5. 点击「部署」 **模板特点:** - ✅ 开箱即用,无需配置 - ✅ 集成 CloudBase SDK,快速调用云开发能力 - ✅ 集成最佳实践 - ✅ 自动配置构建命令 - ✅ 更新部署 ### 方式三:Git 仓库部署 适用场景:持续集成,代码更新自动触发部署。 #### 个人仓库(推荐) 支持从您的 Git 仓库直接部署: - GitHub - GitLab - Gitee(码云) - 腾讯工蜂 **部署步骤:** 1. 在「静态网站托管」页面,点击「新建部署」 2. 选择「Git 仓库」 3. 选择「个人仓库」 4. 授权访问您的 Git 平台账号 5. 选择要部署的仓库和分支 6. 配置构建参数 7. 点击「部署」 #### 公开仓库 支持从任意公开的 Git 仓库部署: **部署步骤:** 1. 在「静态网站托管」页面,点击「新建部署」 2. 选择「Git 仓库」 3. 选择「公开仓库」 4. 输入仓库 URL(如 `https://github.com/username/repo.git`) 5. 选择分支(默认 `main` 或 `master`) 6. 配置构建参数 7. 点击「部署」 ### 方式四:使用 CLI 命令部署 #### 前置条件 全局安装 CloudBase CLI 并登录: ```bash npm i -g @cloudbase/cli tcb login ``` > 更多安装方式(yarn / pnpm)、登录方式(API 密钥 / CI 环境 / 临时密钥等)及网络问题排查,请参考 [CLI 安装文档 →](/cli-v1/install) #### 部署步骤 在项目根目录执行以下任意一种等价命令: ```bash # 完整命令(推荐) tcb app deploy --framework vite -e my-env-id # 顶层别名 tcb deploy --framework vite -e my-env-id # 裸命令:交互式引导部署 tcb ``` 常用参数示例: ```bash # Next.js 项目,指定构建产物目录 tcb app deploy --framework next --output-dir .next -e my-env-id # Monorepo 场景:指定项目子目录 tcb app deploy --cwd ./frontend --framework react -e my-env-id # 纯静态项目(跳过构建) tcb app deploy --framework static --build-command "" --output-dir ./public -e my-env-id # CI/CD 场景:跳过确认,覆盖已有应用 tcb app deploy --framework react --force -e my-env-id ``` #### 通过 `cloudbaserc.json` 零参数部署 在项目根目录配置 `cloudbaserc.json`,后续只需执行 `tcb` 即可完成部署: ```json { "version": "2.0", "envId": "my-env-id", "app": { "serviceName": "my-app", "framework": "react", "installCommand": "npm install", "buildCommand": "npm run build", "outputDir": "build", "deployPath": "/my-app", "envVariables": { "API_URL": "https://api.example.com" } } } ``` :::tip 自动保存配置 首次部署成功后,CLI 会自动将本次部署参数写回 `cloudbaserc.json`,后续部署无需重复输入。 ::: #### 支持的框架类型 `--framework` 支持以下取值:`react` / `vue` / `vite` / `next` / `nuxt` / `angular` / `static`。未指定时 CLI 会按 **Next.js > Nuxt > Angular > Vite > Vue CLI > React > 静态 HTML** 顺序自动检测。 #### 相关命令 | 命令 | 说明 | |------|------| | `tcb app list` | 查看当前环境的应用列表 | | `tcb app info ` | 查看应用详情(状态、版本、访问域名) | | `tcb app versions list ` | 查看历史部署版本 | | `tcb app delete ` | 删除应用记录 | 完整命令参数、配置字段优先级、版本管理等详见 [CLI 应用部署文档 →](/cli-v1/app/management) --- # 静态网站托管/应用部署/部署指南 > 当前文档链接: https://docs.cloudbase.net/hosting/web-hosting-guide 本指南介绍如何配置和部署前端项目到云开发静态托管服务。 部署配置 ## 一、配置说明 ### 1.1 基础配置 | 配置项 | 说明 | 示例 | 必填 | |--------|------|------|------| | 项目名称 | 自定义项目标识 | `my-project` | ✅ | | 项目框架 | 选择前端框架(支持自动识别并填充构建配置) | React、Vue.js、Angular、Next.js、Nuxt.js、VitePress、Astro、Hugo、Hexo、其他 | ✅ | | Node.js 版本 | 构建环境版本(16/18/20/22/24) | Node.js 18 | ✅ | :::tip 主流框架自动识别 「项目框架」下拉已支持 **10 种主流前端框架**:React、Vue.js、Angular、Next.js、Nuxt.js、VitePress、Astro、Hugo、Hexo,以及自定义配置(其他)。选择框架后,平台将**自动推荐并填充**对应的安装命令、构建命令和目标目录,减少手动配置成本,开发者无需手动配置构建参数即可一键部署。 ::: ### 1.2 构建配置 | 配置项 | 说明 | 示例 | 必填 | |--------|------|------|------| | 目标目录 | 代码所在目录(默认根目录) | `/frontend` | - | | 安装命令 | 安装依赖 | `npm install` | 静态项目可留空 | | 构建命令 | 编译构建 | `npm run build` | 静态项目可留空 | | 构建产物目录 | 输出目录 | `./dist`、`./build` | ✅ | | 部署路径 | 访问路径 | `/`、`/app` | - | :::info 纯静态项目部署 如果项目是纯 HTML/CSS/JS 文件,**无需构建**: - **安装命令**和**构建命令**留空 - **构建产物目录**填写静态文件所在目录(如 `.`、`./public`) - 详细说明请参考:[纯静态项目部署](/hosting/static-deployment) ::: ### 1.3 环境变量(可选) 支持配置构建时的环境变量: ```bash NODE_ENV=production VITE_API_URL=https://api.example.com REACT_APP_ENV=production ``` --- ## 二、部署日志展示 配置提交后,系统会执行构建和部署流程,您可以在控制台查看实时日志: ### 日志示例 #### 成功部署日志 ```bash Deployment complete 👉 https://lowcode-0gwpl9v4xxxxxxx-1258057692.tcloudbaseapp.com ✔ Total files: 4 ✔ Successfully uploaded 4 file(s) ┌────────┬─────────────────────────────────────┐ │ Status │ File │ ├────────┼─────────────────────────────────────┤ │ ✔ │ /cosdesign/cosdesign.code-workspace │ ├────────┼─────────────────────────────────────┤ │ ✔ │ /cosdesign/index.html │ ├────────┼─────────────────────────────────────┤ │ ✔ │ /cosdesign/script.js │ ├────────┼─────────────────────────────────────┤ │ ✔ │ /cosdesign/style.css │ └────────┴─────────────────────────────────────┘ ✖ Failed to upload 0 file(s) [2026-01-27 20:03:33] 自定义部署完成 Finished, code: 0, duration: 6.9s ``` ### 如何判断部署是否成功 看到以下关键信息即表示部署成功: :::tip 成功标志 ✅ **`✔ Successfully uploaded X file(s)`** - 文件上传成功数量 ✅ **`Deployment complete 👉 访问地址`** - 部署完成并显示访问链接 ✅ **`Finished, code: 0`** - 进程退出码为 0(表示无错误) ✅ **文件列表中所有文件状态都是 `✔`** - 所有文件上传成功 ::: :::warning 失败标志 如果看到以下信息,说明部署失败或部分失败: - ❌ **`✖ Failed to upload X file(s)`** 且 X > 0 - 有文件上传失败 - ❌ **`Finished, code: 1`** 或其他非 0 值 - 构建或部署过程出错 - ❌ 日志中出现 **`Error:`** 或 **`Failed:`** 等错误提示 ::: --- ## 三、快速配置示例 ### 纯静态项目 ```text 项目框架: 其他 安装命令: (留空) 构建命令: (留空) 构建产物目录: . 部署路径: /my-static-site ``` ### React (Vite) ```text 项目框架: React 安装命令: npm install 构建命令: npm run build 构建产物目录: ./dist 部署路径: /my-react-app ``` ### Vue (Vue CLI) ```text 项目框架: Vue 安装命令: npm install 构建命令: npm run build 构建产物目录: ./dist 部署路径: /my-vue-app ``` ### Angular ```text 项目框架: Angular 安装命令: npm install 构建命令: npm run build 构建产物目录: ./dist/项目名称 部署路径: /my-angular-app ``` ### Next.js(SSG 静态导出) ```text 项目框架: Next.js 安装命令: npm install 构建命令: npm run build 构建产物目录: ./out 部署路径: /my-next-app ``` ### Nuxt(SSG 静态生成) ```text 项目框架: Nuxt.js 安装命令: npm install 构建命令: npm run generate 构建产物目录: ./.output/public 部署路径: /my-nuxt-app ``` ### VitePress ```text 项目框架: VitePress 安装命令: npm install 构建命令: npm run docs:build 构建产物目录: ./docs/.vitepress/dist 部署路径: /my-docs ``` ### Astro ```text 项目框架: Astro 安装命令: npm install 构建命令: npm run build 构建产物目录: ./dist 部署路径: /my-astro-site ``` ### Hugo ```text 项目框架: Hugo 安装命令: (留空) 构建命令: hugo --minify 构建产物目录: ./public 部署路径: /my-hugo-site ``` ### Hexo ```text 项目框架: Hexo 安装命令: npm install 构建命令: npx hexo generate 构建产物目录: ./public 部署路径: /my-blog ``` --- ## 四、框架配置参考 ### React #### Create React App - **构建产物目录**:`./build` - **package.json**: ```json { "scripts": { "build": "react-scripts build" } } ``` #### Vite + React - **构建产物目录**:`./dist` - **vite.config.js**: ```javascript import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], base: './', // 子路径部署使用相对路径 build: { outDir: 'dist' } }) ``` ### Vue #### Vue CLI - **构建产物目录**:`./dist` - **vue.config.js**: ```javascript module.exports = { publicPath: './', // 子路径部署使用相对路径 outputDir: 'dist' } ``` #### Vite + Vue - **构建产物目录**:`./dist` - **vite.config.js**: ```javascript import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], base: './', // 子路径部署使用相对路径 build: { outDir: 'dist' } }) ``` ### Angular - **构建产物目录**:`./dist/项目名称` - **angular.json**: ```json { "projects": { "your-project": { "architect": { "build": { "options": { "outputPath": "dist/your-project", "baseHref": "./" // 子路径部署使用相对路径 } } } } } } ``` ### Next.js 静态网站托管支持部署 **SSG(静态站点生成)** 模式的 Next.js 应用,推荐使用官方的 **Nextjs Web 应用模板**快速创建项目,已内置 CloudBase SDK 集成。 - **构建产物目录**:`./out` - **next.config.js**: ```javascript /** @type {import('next').NextConfig} */ const nextConfig = { output: 'export', // 启用静态导出 images: { unoptimized: true // 静态导出需关闭图片优化 }, basePath: '', // 子路径部署时填写,如 '/my-app' trailingSlash: true } module.exports = nextConfig ``` - **package.json**: ```json { "scripts": { "build": "next build" } } ``` ### Nuxt 静态网站托管支持部署 **SSG(静态站点生成)** 模式的 Nuxt 应用,推荐使用官方的 **Nuxt Web 应用模板**快速创建项目,已内置 CloudBase SDK 集成。 - **构建产物目录**:`./.output/public` - **nuxt.config.ts**: ```typescript export default defineNuxtConfig({ ssr: false, // 或使用 nitro.preset: 'static' app: { baseURL: '/', // 子路径部署时填写,如 '/my-app/' buildAssetsDir: '/_nuxt/' } }) ``` - **package.json**: ```json { "scripts": { "generate": "nuxt generate" } } ``` ### VitePress - **构建产物目录**:`./docs/.vitepress/dist` - **docs/.vitepress/config.js**: ```javascript export default { base: '/', // 子路径部署使用 '/my-docs/' outDir: '.vitepress/dist' } ``` ### Astro - **构建产物目录**:`./dist` - **astro.config.mjs**: ```javascript import { defineConfig } from 'astro/config' export default defineConfig({ base: '/', // 子路径部署填写,如 '/my-astro-site' output: 'static' }) ``` ### Hugo - **构建产物目录**:`./public` - **hugo.toml**(或 `config.toml`): ```toml baseURL = "https://example.com/" # 子路径部署:baseURL = "https://example.com/my-site/" publishDir = "public" ``` - **构建命令**:`hugo --minify` ### Hexo - **构建产物目录**:`./public` - **_config.yml**: ```yaml url: https://example.com root: / # 子路径部署填写 '/my-blog/' public_dir: public ``` - **构建命令**:`npx hexo generate` --- ## 五、部署路径配置 ### 根路径部署(`/`) **适用场景:** - 单一应用,独占整个静态托管环境 - 官网、产品主页等需要域名直接访问 - 访问地址:`https://example.com/` :::danger 注意事项 根路径部署会覆盖静态托管根目录下的现有文件: - 同名文件会被覆盖且无法自动恢复 - 多个项目部署到根路径会互相覆盖 - 建议:多项目场景使用子路径(如 `/app1`、`/app2`) ::: ### 子路径部署(`/app`) **适用场景:** - 同一环境部署多个项目 - 需要隔离不同应用的文件 - 访问地址:`https://example.com/app/` **配置要求:** 在项目配置中将公共路径设置为**相对路径** `./ `,避免静态资源加载失败。 | 框架 | 配置文件 | 配置项 | |------|---------|--------| | Vite | `vite.config.js` | `base: './'` | | Vue CLI | `vue.config.js` | `publicPath: './'` | | Create React App | `package.json` | `"homepage": "./"` | | Angular | `angular.json` | `"baseHref": "./"` | --- ## 六、特殊场景配置 ### Monorepo 项目 前端代码在子目录中: ```text 目标目录: /packages/web 安装命令: npm install 构建命令: npm run build 构建产物目录: ./dist ``` ### 多应用项目 项目包含多个应用: ```text 目标目录: /apps/admin 安装命令: npm install 构建命令: npm run build 构建产物目录: ./dist ``` --- ## 常见问题 ### Q: 部署后静态资源 404? A: 检查是否配置了正确的 `base`/`publicPath`,子路径部署需要使用相对路径 `./` ### Q: 如何部署多个项目? A: 为每个项目配置不同的部署路径,如 `/app1`、`/app2`,避免文件冲突 ### Q: Node.js 版本如何选择? A: 建议选择与本地开发环境相同的版本,避免构建差异 --- # 静态网站托管/应用部署/纯静态项目部署 > 当前文档链接: https://docs.cloudbase.net/hosting/web-hosting-static 本文档介绍如何部署纯前端静态项目(HTML、CSS、JavaScript)到云开发静态网站托管,无需构建步骤,直接上传即可访问。 ## 适用场景 纯静态项目部署适用于以下场景: - 纯 HTML/CSS/JavaScript 项目,无需编译构建 - 静态文档站点 - 落地页、活动页面 - 已经构建完成的前端项目产物 ## 快速开始 ### 前置条件 - 拥有腾讯云账号并完成实名认证 - 前往[云开发平台](https://tcb.cloud.tencent.com/dev),创建云开发环境 - 准备好静态网站文件(HTML、CSS、JS、图片等) ### 部署入口 前往 [云开发控制台 - 静态网站托管](https://tcb.cloud.tencent.com/dev#/static-hosting) 静态网站托管控制台 ## 部署方式 ### 方式一:应用在线部署 适合中大型项目,快速批量部署。 #### 准备代码包 **1. 整理项目文件** 确保项目包含必要的静态文件: ``` my-website/ ├── index.html # 首页(必需) ├── about.html # 其他页面 ├── css/ │ ├── style.css │ └── responsive.css ├── js/ │ ├── main.js │ └── utils.js ├── images/ │ ├── logo.png │ └── background.jpg └── fonts/ └── custom-font.woff2 ``` **2. 上传部署** 1. 在静态网站托管页面,点击「新建部署」 2. 选择「上传代码包」或者 「项目文件夹」 3. 上传 ZIP 文件 或 文件夹 4. **关键配置**: - 项目名称:`my-website` - **安装命令、构建命令**:留空(纯静态项目无需构建) - **构建产物目录**:`.`(当前目录) - 部署路径:`/` 或 `/my-website` 上传代码包 5. 点击「部署」 :::tip 提示 如果您的静态文件已经是构建产物(如 `dist/` 目录下的文件),直接上传该目录即可。 ::: ### 方式二:控制台上传文件 适合小型项目或少量文件的快速部署。 #### 上传单个文件 **步骤:** 1. 在静态网站托管页面,点击「上传文件」 2. 选择要上传的文件(支持 HTML、CSS、JS、图片等) 3. 文件会自动上传到当前目录 4. 上传完成后,通过默认域名即可访问 **示例:** ``` 上传文件: - index.html - style.css - script.js - logo.png 访问: https://your-env-id.xxx.tcloudbaseapp.com/ ``` #### 上传文件夹 **步骤:** 1. 在静态网站托管页面,点击「上传文件夹」 2. 选择包含静态文件的整个文件夹 3. 系统会保持原有目录结构上传 4. 上传完成后即可访问 **项目结构示例:** ``` my-static-site/ ├── index.html ├── css/ │ └── style.css ├── js/ │ └── main.js └── images/ └── banner.jpg ``` **访问路径:** ``` https://your-env-id.xxx.tcloudbaseapp.com/ https://your-env-id.xxx.tcloudbaseapp.com/css/style.css https://your-env-id.xxx.tcloudbaseapp.com/js/main.js https://your-env-id.xxx.tcloudbaseapp.com/images/banner.jpg ``` ### 方式三:CLI 命令行部署 适合开发者和自动化部署场景。 #### 安装 CloudBase CLI ```bash npm install -g @cloudbase/cli ``` #### 登录授权 ```bash cloudbase login ``` #### 部署命令 **部署整个项目:** ```bash # 进入项目目录 cd my-static-site # 部署到根路径 cloudbase hosting deploy . -e <环境ID> # 部署到子路径 cloudbase hosting deploy . /my-site -e <环境ID> ``` **部署指定目录:** ```bash # 仅部署 dist 目录 cloudbase hosting deploy dist/ -e <环境ID> # 部署指定文件 cloudbase hosting deploy index.html -e <环境ID> ``` **常用参数:** ```bash # 查看部署历史 cloudbase hosting list -e <环境ID> # 删除文件 cloudbase hosting delete /old-file.html -e <环境ID> # 查看帮助 cloudbase hosting -h ``` 详细使用方法请参考 [CLI 静态托管命令](/cli-v1/hosting)。 ## 配置说明 ### 目录结构建议 **标准静态网站结构:** ``` website/ ├── index.html # 首页(必需) ├── 404.html # 404 错误页(可选) ├── css/ # 样式文件 │ ├── main.css │ └── normalize.css ├── js/ # 脚本文件 │ ├── app.js │ └── vendor.js ├── images/ # 图片资源 │ ├── logo.svg │ └── banner.jpg ├── fonts/ # 字体文件 │ └── custom-font.woff2 └── assets/ # 其他资源 ├── videos/ └── documents/ ``` ### 访问规则 **默认索引文档:** - 访问目录时自动返回 `index.html` - 例如:`/about/` → `/about/index.html` **访问示例:** | 文件路径 | 访问 URL | |---------|---------| | `/index.html` | `https://your-env.xxx.tcloudbaseapp.com/` | | `/about.html` | `https://your-env.xxx.tcloudbaseapp.com/about.html` | | `/css/style.css` | `https://your-env.xxx.tcloudbaseapp.com/css/style.css` | | `/blog/index.html` | `https://your-env.xxx.tcloudbaseapp.com/blog/` | --- # 静态网站托管/应用部署/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/app/management --- title: 应用部署 --- # 应用部署 :::info v3.0.0+ `tcb app` 命令自 v3.0.0 起提供。 ::: `tcb app` 命令用于部署和管理 CloudBase 云应用。每次部署会覆盖上一次,并保留历史部署记录(可通过 `tcb app versions` 查看)。 :::tip 适用场景:有构建流程的前端项目 适合以下场景: - 有安装依赖 + 构建步骤的前端项目(React / Vue / Next.js / Vite / Angular / Nuxt 等) - 纯静态项目 - CI/CD 流水线中自动化部署 CLI 会自动完成:**安装依赖 → 构建 → 上传产物 → 绑定路由**,无需手动操作文件。每次部署会覆盖上一次,可通过 `tcb app versions list` 查看历史部署记录。 ::: --- ## `tcb app deploy` 部署应用。CLI 会自动执行:安装依赖 → 构建 → 上传构建产物 → 绑定路由。 以下三种调用方式完全等价: ```bash tcb app deploy [serviceName] [options] # 完整命令 tcb deploy [serviceName] [options] # 顶层别名 tcb [options] # 裸命令(不带任何子命令时自动路由到 app deploy) ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `[serviceName]` | 应用名称(可选,不填则自动从 `package.json` 读取或交互输入) | — | | `-e, --env-id ` | 目标环境 ID | — | | `--framework ` | 框架类型:`react` / `vue` / `vite` / `next` / `nuxt` / `angular` / `static` | — | | `--install-command ` | 安装命令(留空则跳过安装) | `npm install` | | `--build-command ` | 构建命令(留空则跳过构建,适合纯静态项目) | — | | `--output-dir ` | 构建产物目录(默认 `./dist`;无需构建时填 `./`) | 自动检测 | | `--deploy-path ` | 静态托管挂载路径 | `/服务名` | | `--cwd ` | 项目目录(相对路径) | 当前目录 | | `-f, --force` | 应用名称已存在时跳过确认直接覆盖 | — | ### 示例 ```bash # 部署 Vite 项目(在项目根目录执行) tcb app deploy --framework vite -e my-env-id # 使用顶层别名,效果完全相同 tcb deploy --framework vite -e my-env-id # 裸命令:什么都不加,交互式引导部署 tcb # 指定项目子目录(monorepo 场景) tcb app deploy --cwd ./frontend -e my-env-id # 部署 Next.js 项目,指定构建产物目录 tcb app deploy --framework next --output-dir .next -e my-env-id # 纯静态项目(跳过构建步骤) tcb app deploy --framework static --build-command "" --output-dir ./public -e my-env-id # CI 场景:跳过确认,覆盖更新已有应用 tcb app deploy --framework react --force -e my-env-id ``` ### 通过 cloudbaserc.json 配置部署 除了通过 CLI 参数指定部署选项外,也可以在 `cloudbaserc.json` 中配置 `app` 字段,实现零命令行参数的部署体验。 **配置文件结构**: ```json { "version": "2.0", "envId": "my-env-id", "app": { "serviceName": "my-app", "root": "./packages/web", "framework": "react", "installCommand": "npm install", "buildCommand": "npm run build", "outputDir": "build", "deployPath": "/my-app", "envVariables": { "API_URL": "https://api.example.com" } } } ``` **配置字段**: | 字段 | 说明 | 默认值 | |------|------|--------| | `serviceName` | 服务名称 | `package.json` 的 `name` 或目录名 | | `root` | 项目根目录(相对路径),用于 monorepo 场景 | 当前目录 | | `framework` | 框架类型:`react` / `vue` / `vite` / `next` / `nuxt` / `angular` / `static` | 自动检测 | | `installCommand` | 安装命令,空字符串跳过安装 | `npm install` | | `buildCommand` | 构建命令,空字符串跳过构建 | 根据框架自动检测 | | `outputDir` | 构建产物目录 | `./dist`(无构建命令时为 `./`) | | `deployPath` | 部署路径(静态托管挂载路径) | `/${serviceName}` | | `envVariables` | 环境变量,键值对形式,云端构建时注入 | — | | `ignore` | 忽略的文件/目录 glob 模式 | — | **优先级总则**:CLI 参数 > `cloudbaserc.json` 配置 > 自动检测 > 交互式输入 > ⚠️ **注意**:部分参数有特殊规则(如 `envVariables` 无 CLI 参数,`buildCommand` 有框架默认值等),详见下表。 **各参数的优先级顺序**: | 参数 | 优先级 | |------|--------| | `serviceName` | CLI 位置参数 > `cloudbaserc.json` 配置 > 交互式选择 > 自动推断(package.json 或目录名) | | `root` | `--cwd` 选项 > `cloudbaserc.json` 配置 > 当前目录 | | `framework` | `--framework` 选项 > `cloudbaserc.json` 配置 > 自动检测(package.json 依赖) > 未指定 | | `installCommand` | `--install-command` 选项 > `cloudbaserc.json` 配置 > 自动推断(`npm install` 或空) > 交互输入 | | `buildCommand` | `--build-command` 选项 > `cloudbaserc.json` 配置 > 自动检测(框架默认值或 package.json scripts) > 交互输入 > 空字符串 | | `outputDir` | `--output-dir` 选项 > `cloudbaserc.json` 配置 > 自动检测(框架默认值) > 交互输入 > `./dist`(有构建)或 `./`(无构建) | | `deployPath` | `--deploy-path` 选项 > `cloudbaserc.json` 配置 > 交互输入(默认 `/{serviceName}`) | | `envVariables` | 仅从 `cloudbaserc.json` 配置读取(无 CLI 参数) | **特殊说明**: - `--cwd` 与 `config.app.root` 都用于指定项目工作目录,`--cwd` 优先级更高 - `envVariables` 无对应 CLI 参数,仅能通过 `cloudbaserc.json` 配置 - 当指定 `--yes` 或 `--json` 时,会跳过所有交互式输入,改用自动推断值 - 框架自动检测遵循优先级:Next.js > Nuxt > Angular > Vite > Vue CLI > React(CRA)> 静态 HTML **自动保存**:首次部署成功后,若 `cloudbaserc.json` 中不存在 `app` 配置,CLI 会自动将本次部署参数写回配置文件,后续部署无需重复输入。 **更多配置字段的详细说明,请参考 [配置文件文档](/cli-v1/config)。** --- ## `tcb app list` 查看当前环境的应用列表。 ```bash tcb app list [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID | — | | `-l, --limit ` | 返回数量 | `20` | | `-o, --offset ` | 偏移量 | `0` | | `--json` | 输出 JSON | — | ### 示例 ```bash tcb app list -e my-env-id ``` --- ## `tcb app info` 查看指定应用的详情(状态、版本、访问域名等)。 ```bash tcb app info [options] ``` ### 示例 ```bash tcb app info my-app -e my-env-id # JSON 输出(适合脚本读取访问域名) tcb app info my-app --json -e my-env-id ``` --- ## `tcb app versions list` 查看应用版本历史列表。 ```bash tcb app versions list [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `` | 应用名称(必填) | — | | `-l, --limit ` | 返回数量 | `20` | | `-o, --offset ` | 偏移量 | `0` | | `-e, --env-id ` | 环境 ID | — | ### 示例 ```bash tcb app versions list my-app -e my-env-id ``` --- ## `tcb app versions detail` 查看应用指定版本的详细信息,默认显示最新版本。 ```bash tcb app versions detail [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `` | 应用名称(必填) | — | | `--version-name ` | 版本名称(不填则查最新版本) | — | | `-e, --env-id ` | 环境 ID | — | ### 示例 ```bash # 查看最新版本详情 tcb app versions detail my-app -e my-env-id # 查看指定版本详情 tcb app versions detail my-app --version-name my-app-001 --env-id env-xxx ``` --- ## `tcb app delete` 删除应用记录,不会删除底层应用资源文件。 ```bash tcb app delete [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `` | 应用名称(必填) | — | | `-e, --env-id ` | 环境 ID | — | | `--dry-run` | 预览删除操作,不实际执行 | — | ### 示例 ```bash tcb app delete my-app -e my-env-id # 预览删除(不实际执行) tcb app delete my-app -e my-env-id --dry-run ``` :::caution 注意 此操作会删除应用的所有版本和资源,不可恢复。项目资源文件需单独在静态托管桶中手动清除。 ::: --- # 静态网站托管/资源管理/控制台管理静态资源 > 当前文档链接: https://docs.cloudbase.net/hosting/manage 本文档介绍如何管理 CloudBase 静态托管服务,包括资源管理、域名配置、安全设置等核心功能。 ## 概述 CloudBase 静态托管为您提供了完整的静态网站管理能力,支持文件上传、域名绑定、缓存配置、安全防护等功能。通过 [云开发控制台 - 静态网站托管](https://tcb.cloud.tencent.com/dev#/static-hosting) ![](https://qcloudimg.tencent-cloud.cn/raw/dbc68441c2f2627092ec1f8a4d474d8f.png) 可以便捷地管理您的静态网站。 ## 资源管理 ![](https://qcloudimg.tencent-cloud.cn/raw/812d6b331ec7f74adbe782b81685838d.png) ### 基本操作 在静态托管控制台中,您可以执行以下操作: * **文件管理**:上传、下载、删除文件 * **文件夹管理**:创建、删除文件夹 * **批量操作**:支持批量上传和删除 ### 文件命名规范 :::tip 命名要求 * 文件和文件夹名称仅支持数字、中英文字符组合 * 名称长度限制为 255 个字符 * 建议使用有意义的命名,便于管理和维护 ::: ### 服务状态 :::info 自动管理 静态托管服务无需手动启停: * 当托管空间内有文件时,服务自动运行 * 当托管空间为空时,服务自动暂停 * 暂停状态下不产生费用 ::: ## 域名管理 ### 默认域名 CloudBase 为每个静态托管提供默认域名,特点如下: * **免费使用**:无需额外配置即可访问 * **测试用途**:适合开发和测试阶段使用 * **访问限制**:存在访问频率限制,超限后暂时不可访问 ### 自定义域名 为了获得更好的访问体验和品牌展示,建议配置自定义域名: * **无访问限制**:不受默认域名的频率限制 * **品牌展示**:使用自己的域名提升品牌形象 * **SEO 友好**:有利于搜索引擎优化 :::tip 推荐配置 建议为生产环境配置自定义域名,详细配置方法请参考 [配置自定义域名](/service/custom-domain)。 ::: ## 索引文档配置 ![](https://qcloudimg.tencent-cloud.cn/raw/0c36b73b387687de6b0c8dd0c1d53264.png) ### 默认索引文档 * **默认文件**:`index.html` * **作用范围**:根目录和所有子目录 * **访问行为**:访问目录时自动返回索引文档 ### 配置建议 :::tip 最佳实践 * 在根目录和重要子目录下都放置 `index.html` 文件 * 确保索引文档内容完整,提供良好的用户体验 * 可以在索引文档中添加导航链接,方便用户访问其他页面 ::: ## 重定向规则 重定向规则帮助您处理 URL 变更和错误页面,提升用户体验。 ### 应用场景 * **文件迁移**:文件移动或重命名后的 URL 重定向 * **URL 简化**:将长 URL 重定向到短 URL * **错误处理**:自定义 404 等错误页面 * **SEO 优化**:保持搜索引擎排名 ### 规则类型 #### 1. 错误码重定向 针对 HTTP 错误状态码(如 404、403)进行重定向: * **支持范围**:4xx 错误码 * **自定义页面**:可以设计友好的错误页面 * **用户引导**:在错误页面提供导航和帮助信息 #### 2. 前缀匹配重定向 基于 URL 前缀进行重定向: * **灵活匹配**:支持文件和文件夹的前缀匹配 * **批量重定向**:一条规则可以处理多个相似 URL * **路径映射**:将旧路径映射到新路径 **示例场景**: ``` 旧路径:docs/guide.html 新路径:documents/guide.html 规则:docs/ → documents/ ``` ### 配置注意事项 :::warning 重要提醒 * **优先级**:重定向规则优先级高于索引文档配置 * **路径格式**:替换路径只需填写相对路径,无需包含域名 * **测试验证**:配置后请充分测试确保重定向正常工作 ::: ## 缓存配置 合理的缓存配置可以显著提升网站访问速度,减少带宽消耗。 ![](https://qcloudimg.tencent-cloud.cn/raw/9810ac5ba3b786bc363523142da6b6d4.png) ### 缓存类型 CloudBase 静态托管支持两级缓存: 1. **浏览器缓存**:在用户浏览器中缓存资源 2. **节点缓存**:在 CDN 节点中缓存资源 ### 配置方式 支持多种缓存配置方式: * **文件后缀**:如 `.jpg`、`.png`、`.css`、`.js` * **文件夹**:如 `/static`、`/assets`、`/images` * **文件路径**:如 `/static/*.js`、`/css/main.css` ### 缓存策略建议 | 资源类型 | 建议缓存时间 | 说明 | |---------|-------------|------| | 图片文件 | 30 天 | 图片通常不经常变更 | | CSS/JS 文件 | 7 天 | 样式和脚本文件相对稳定 | | HTML 文件 | 1 小时 | 页面内容可能经常更新 | | 字体文件 | 30 天 | 字体文件很少变更 | :::tip 缓存机制 * 缓存时间以 `Cache-Control: max-age=` 形式设置 * 控制台上传文件会自动刷新 CDN 缓存 * 用户访问时优先使用浏览器缓存,其次是节点缓存 ::: ## 安全配置 CloudBase 静态托管提供多层安全防护,保护您的资源免受恶意访问。 ![](https://qcloudimg.tencent-cloud.cn/raw/1ff50a97d1c40316afc255c0a3c2a63b.png) ### 防盗链配置 通过 Referer 检查防止资源被盗用: #### 黑名单模式 * **功能**:拒绝指定域名的访问请求 * **适用场景**:已知恶意域名的防护 * **配置方式**:添加需要拒绝的域名列表 #### 白名单模式 * **功能**:仅允许指定域名的访问请求 * **适用场景**:严格控制访问来源 * **配置方式**:添加允许访问的域名列表 :::info 检测机制 CloudBase 通过检查 HTTP 请求头中的 `Referer` 字段来判断请求来源,非法请求将返回 403 状态码。 ::: ### IP 访问控制 #### IP 黑白名单 **支持格式**: * IPv4 地址:`192.168.1.1` * IPv4 网段:`192.168.1.0/24`、`10.0.0.0/8` * IPv6 地址:完整的 IPv6 地址格式 **配置模式**: * **黑名单**:阻止指定 IP 或网段的访问 * **白名单**:仅允许指定 IP 或网段的访问 #### IP 访问限频 * **功能**:限制单个 IP 的访问频率 * **防护效果**:可防御部分 CC 攻击 * **配置建议**:根据业务需求合理设置限频阈值 :::warning 配置提醒 IP 访问限频可能影响正常用户访问,请根据实际业务情况谨慎配置。建议先在测试环境验证配置效果。 ::: --- # 静态网站托管/资源管理/静态网站托管 > 当前文档链接: https://docs.cloudbase.net/cli-v1/hosting 云开发为开发者提供静态网页托管能力,支持 HTML、CSS、JavaScript、字体等静态资源的分发。底层基于腾讯云对象存储 COS 和全球 CDN 网络,为您的网站提供高性能、高可用的访问体验。 :::tip 适用场景:文件级操作 `tcb hosting` 是**文件维度**的操作工具,适合以下场景: - 手动上传 / 同步静态文件(HTML、CSS、JS、图片、字体等) - 纯静态内容,无构建流程(如文档站产物、设计稿导出页面) - 需要精细控制云端文件路径 **如果你的项目有构建步骤**(React / Vue / Next.js / Vite / Angular / Nuxt 等前端框架),推荐使用 **[应用部署(tcb app deploy)](./app/management.md)**,它会自动完成安装依赖 → 构建 → 上传产物 → 绑定路由的完整流程。 ::: ## 前置条件 在使用 CLI 操作静态网站服务前,请确保: 1. 拥有腾讯云账号并完成实名认证 2. 前往[云开发平台](https://tcb.cloud.tencent.com/dev),创建云开发环境 ## 部署网站 ### 全量部署 使用 `tcb hosting deploy` 命令可以将当前目录下的所有文件部署到静态网站。 ```bash # 进入构建目录 cd docs # 部署当前目录下的所有文件 tcb hosting deploy -e envId ``` ### 指定文件部署 您可以指定特定的文件或文件夹进行部署: ```bash # 基本语法 tcb hosting deploy [cloudPath] -e envId ``` **参数说明:** - `localPath`:本地文件或文件夹路径 - `cloudPath`:云端目标路径(可选,默认为根目录) - `envId`:环境 ID **示例:** ```bash # 将 hosting 目录下的所有文件部署到根目录 tcb hosting deploy hosting -e envId # 将本地 index.html 部署到云端根目录 tcb hosting deploy ./index.html -e envId # 将 static 目录下的 index.js 部署到云端 static/index.js tcb hosting deploy ./static/index.js static/index.js -e envId ``` ### 部署限制 - **文件大小**:单个文件最大支持 50TB - **文件数量**:无限制 - **网络优化**:上传默认开启 2 次自动重试,瞬时 `socket hang up` 会自动恢复。弱网或大文件场景可通过以下方式进一步优化: **方式一:调整上传参数(推荐)** | 参数 | 说明 | 默认值 | |------|------|--------| | `--concurrency ` | 上传并发数,大文件或弱网建议调小(如 5) | 20 | | `--retry-count ` | 上传失败重试次数 | 2 | | `--retry-interval ` | 重试时间间隔(毫秒) | 1000 | ```bash # 大文件 / 弱网:降低并发 + 增加重试次数 tcb hosting deploy ./dist --concurrency 5 --retry-count 3 ``` **方式二:关闭 SDK 长连接** ```bash export COS_SDK_KEEPALIVE=false tcb hosting deploy -e envId ``` ### SPA 应用配置 :::caution Vue History 模式 使用 Vue Router 的 history 模式时,需要在 [静态网站控制台](https://tcb.cloud.tencent.com/dev#/static-hosting?tab=config) 的设置页面配置错误页面为应用的入口页面(通常是 `index.html`)。 ::: ## 管理网站 ### 查看服务信息 查看静态网站的状态、访问域名等详细信息: ```bash tcb hosting detail -e envId ``` ### 查看文件列表 列出静态网站存储空间中的所有文件: ```bash tcb hosting list -e envId ``` ### 删除文件 删除静态网站中的指定文件或文件夹: ```bash # 删除指定文件或文件夹 tcb hosting delete -e envId # 删除所有文件(cloudPath 为空) tcb hosting delete -e envId ``` **命令参数:** | 参数 | 说明 | 必填 | | ---------------------- | ------------------------------------------ | -------------------------- | | `cloudPath` | 云端文件或文件夹路径 | 否(不指定则删除所有文件) | | `-e, --env-id ` | 环境 ID | 是 | | `--dir` | 删除目标是文件夹(递归删除) | 否 | | `--force` | 强制删除,跳过确认提示 | 否 | | `--dry-run` | 模拟运行,仅预览将要删除的文件,不实际执行 | 否 | **示例:** ```bash # 删除根目录下的 index.html tcb hosting delete index.html -e envId # 删除 static 文件夹及其所有内容 tcb hosting delete static --dir -e envId # 预览将要删除的文件(不实际执行) tcb hosting delete static --dir --dry-run -e envId # 清空整个静态网站 tcb hosting delete -e envId # 强制删除,跳过确认 tcb hosting delete static --dir --force -e envId ``` ## 路径说明 ### 路径格式 - **localPath**:本地文件或文件夹路径 - 格式:`目录/文件名` - 示例:`./index.js`、`static/css/index.css` - **cloudPath**:云端文件或文件夹的相对路径 - 格式:`目录/文件名`(相对于根目录) - 示例:`index.js`、`static/css/index.js` ### 跨平台注意事项 :::caution Windows 系统 - **localPath**:使用系统路径格式,通常使用 `\` 分隔符 - **cloudPath**:统一使用 `/` 分隔符,与操作系统无关 ::: ## 常见问题 ### 上传失败处理 如果遇到网络连接问题导致上传失败,可以尝试以下方式: **方式一:调整上传参数(推荐)** ```bash # 大文件 / 弱网:降低并发并增加重试次数 tcb hosting deploy ./dist --concurrency 5 --retry-count 3 # 调整重试间隔(毫秒) tcb hosting deploy ./dist --retry-count 5 --retry-interval 2000 ``` **方式二:关闭 SDK 长连接** ```bash export COS_SDK_KEEPALIVE=false tcb hosting deploy -e envId ``` ### 域名访问 部署完成后,您可以通过以下方式访问网站: - 在控制台查看分配的默认域名 - 配置自定义域名(需要 ICP 备案) --- # 静态网站托管/高级功能/静态托管安全配置 > 当前文档链接: https://docs.cloudbase.net/hosting/security 静态托管的安全配置为云开发的静态托管服务提供全方位的安全防护机制,帮助开发者有效保护网站资源,防止恶意访问和资源盗用。通过合理配置以下安全选项,可以显著提升您的静态网站安全性: * 防盗链配置 - 控制资源访问来源 * IP 黑白名单配置 - 精确管理访问权限 * IP 访问限频配置 - 防止恶意高频请求 ## 防盗链配置 防盗链功能通过识别请求来源(Referer)来判断访问是否合法,有效防止资源被其他网站非法引用,保护您的带宽和流量资源。 ### 配置步骤 1. 在云开发控制台中,通过【环境配置】-【安全管控】,进入静态托管安全配置页面 2. 开启防盗链功能开关 3. 选择防盗链策略: - **黑名单模式**:除名单内的来源外,其他来源均可访问 - **白名单模式**:仅允许名单内的来源访问,其他来源均被拒绝 ### 配置规则说明 - 来源 Referer 支持多条记录,每行一条,用换行符分隔 - 支持域名或 IP 格式,如:`example.com`、`192.168.0.1` - 支持通配符(*)匹配子域名,如:`*.cloudbase.net`、`*.example.com` - 支持路径匹配,如:`example.com/path/*` ### 空 Referer 处理 您可以选择是否允许空 Referer 访问: - **允许空 Referer**:直接访问(如地址栏输入URL)或某些特殊请求没有 Referer 信息时允许访问 - **禁止空 Referer**:拒绝所有没有来源信息的请求 ### 应用场景 - 防止图片、视频等静态资源被其他网站直接引用 - 保护网站专有内容不被未授权的第三方嵌入 - 控制内容分发渠道,确保资源只在授权的网站上展示 ## IP 黑白名单配置 IP 黑白名单功能允许您精确控制哪些 IP 地址或网段可以访问您的静态网站资源,是保障网站安全的重要手段。 ### 配置步骤 1. 在云开发控制台中,通过【环境配置】-【安全管控】,进入静态托管安全配置页面 2. 开启 IP 黑白名单功能开关 3. 选择 IP 控制策略: - **黑名单模式**:阻止名单中的 IP 访问,其他 IP 均可访问 - **白名单模式**:仅允许名单中的 IP 访问,其他 IP 均被拒绝 ### 配置规则说明 - 支持添加多个 IP 地址或网段,每行一条,用换行符分隔 - 单个 IP 格式:`192.168.0.1` - IP 网段格式(CIDR 表示法):`192.168.0.0/24`(表示 192.168.0.0-192.168.0.255 范围内的所有 IP) - 支持 IPv4 和 IPv6 地址格式 ### 应用场景 - 限制只允许公司内网 IP 访问管理后台 - 阻止已知的恶意 IP 访问网站资源 - 针对特定地区或网络运营商进行访问控制 - 创建测试环境时限制只允许开发团队访问 ## IP 访问限频配置 访问限频功能通过限制单个 IP 在特定时间内对静态托管资源的请求总次数,有效防止恶意爬虫、DDoS 攻击等高频访问行为,保障网站的稳定运行。 > ⚠️ 注意:IP 访问限频是针对静态托管资源请求的总次数限制,而不是单个文件的请求次数限制。 ### 配置步骤 1. 在云开发控制台中,通过【环境配置】-【安全管控】,进入静态托管安全配置页面 2. 开启 IP 访问限频功能开关 3. 设置 QPS(每秒查询率)阈值,建议根据页面资源数量合理设置 ### 限频机制详解 #### 计算原理 - 系统按照 IP 维度统计对所有静态托管资源的访问频率 - 每个静态资源请求(HTML、CSS、JS、图片等)都会独立计入该 IP 的访问次数 - 当单个 IP 的访问频率超过设定阈值时,超出部分的请求将被拒绝(返回 514 状态码) - 限频计算采用滑动时间窗口算法,精确控制访问频率 #### 实际案例说明 假设您有一个网页包含以下资源: - 1 个 HTML 文件(index.html) - 3 个 CSS 文件 - 5 个 JS 文件 - 10 个图片文件 当用户访问这个页面时,浏览器会发起 **19 个请求**(1+3+5+10)来加载所有资源。 **配置示例:** - 如果设置 QPS 限制为 2 次/秒,那么在加载页面时会有 17 个请求被拒绝,导致页面显示异常 - 如果设置 QPS 限制为 20 次/秒,则可以正常加载完整页面 - 建议根据页面最大资源数量设置合适的 QPS 值,通常为页面资源数的 1.5-2 倍 #### QPS 设置建议 | 页面类型 | 平均资源数 | 建议 QPS 设置 | 说明 | |---------|-----------|--------------|------| | 简单页面 | 5-10 个 | 20-30 | 基础 HTML + 少量 CSS/JS | | 普通网站 | 15-30 个 | 50-80 | 包含图片、样式、脚本等 | | 富媒体页面 | 50+ 个 | 100-200 | 大量图片、视频、复杂交互 | ### 应用场景 - 防止恶意爬虫大量抓取网站内容 - 抵御简单的 DDoS 攻击 - 保护静态资源不被过度访问 - 防止资源下载滥用 ### 常见问题 **Q:为什么页面加载时出现 514 错误?** A:可能是 QPS 设置过低,导致页面资源请求超出限制。建议检查页面资源数量,适当提高 QPS 阈值。 **Q:如何确定合适的 QPS 值?** A:可以通过浏览器开发者工具查看页面加载的资源数量,然后设置为资源数的 1.5-2 倍作为初始值,再根据实际访问情况调整。 ## 最佳实践 ### 安全配置组合策略 根据不同的应用场景,可以组合使用以上安全配置,构建多层次的安全防护体系: 1. **公开网站**: - 开启防盗链(白名单模式),允许空 Referer - 开启 IP 黑名单,阻止已知恶意 IP - 设置适当的访问限频阈值(如 100-300 QPS) 2. **企业内部应用**: - 开启 IP 白名单,仅允许企业网络访问 - 开启防盗链(白名单模式),不允许空 Referer - 设置较高的访问限频阈值 3. **资源分发网站**: - 严格的防盗链白名单配置 - 适当的 IP 访问限频 - 针对高频下载资源设置特殊的访问控制规则 ### 监控与调整 定期检查访问日志和安全配置效果,根据实际情况调整安全策略: - 分析被拒绝的请求模式,识别潜在的安全威胁 - 根据业务增长适时调整访问限频阈值 - 定期更新 IP 黑白名单,移除过时规则 通过合理配置静态托管的安全选项,您可以有效保护网站资源,提升访问体验,降低安全风险。 --- # 静态网站托管/高级功能/访问静态网站资源 > 当前文档链接: https://docs.cloudbase.net/service/access-static-hosting 本文档介绍如何通过 HTTP 网关配置域名关联,实现对静态网站托管资源的访问。 ## 基础配置 您可以在静态托管详情页直接绑定域名(推荐),也可以在 HTTP 网关统一配置。 ### 方式一:详情页就地绑定(推荐) 1. 前往 [静态网站托管](https://tcb.cloud.tencent.com/dev#/static-hosting),进入目标静态托管实例的详情页 2. 在「自定义域名」模块点击「添加域名」按钮,弹出绑定窗口 3. 选择绑定模式: - **应用模式**:将整个静态托管服务映射到该域名,访问域名根路径即可进入静态托管,无需配置触发路径。适合「一个域名/路径只服务一个静态站点」的场景 - **路径模式**:将静态托管服务挂在某个路径前缀下访问,需在「触发路径」中填写具体路径(如 `/test`)。适合「同一域名下挂多个资源」的场景 4. 选择自定义域名后确认,已绑定的所有域名会展示在详情页的「域名管理」列表中 ### 方式二:在 HTTP 网关绑定 如果您更习惯从全局视角管理域名与资源的关系,也可以在 HTTP 网关统一配置: 1. 前往 [HTTP 网关](https://tcb.cloud.tencent.com/dev#/env/http-access) 2. 在「域名关联资源」模块点击「新建」按钮 3. 配置以下信息: | 配置项 | 说明 | 示例 | | ------------ | ---------------------------------------------- | -------- | | 关联资源类型 | 选择「静态网站托管」,然后选择静态托管实例 | - | | 域名 | 选择默认域名、自定义域名或 `*`(匹配所有域名) | 默认域名 | | 触发路径 | 设置 HTTP 访问路径,支持 `/` 或自定义路径 | `/test` | > 💡 **提示**:生产环境建议绑定已备案的自定义域名,以获得完整的服务能力。配置方法请参考 [自定义域名配置](/service/custom-domain)。 ## 自定义根路径访问 ### 配置说明 如果需要通过自定义路径访问静态托管资源,可以在「触发路径」中设置自定义根路径。 **配置示例**: - 触发路径设置为 `/test` - 访问 `https://your-domain/test` 时,将映射到静态网站托管的 `/` 目录 ### 代码调整 使用自定义根路径时,需要调整静态资源中的相对路径引用,在所有资源路径前添加自定义根路径前缀。 **调整示例**: ```html Logo Logo ``` > ⚠️ **注意**:使用自定义根路径时,需要确保静态资源中的所有绝对路径引用都添加了对应的路径前缀,否则会导致资源加载失败。 ## 相关文档 - [HTTP 网关概述](/service/introduce) - [自定义域名配置](/service/custom-domain) - [静态网站托管部署](/hosting/web-hosting-static) --- # 静态网站托管/高级功能/与Git平台CI/CD集成 > 当前文档链接: https://docs.cloudbase.net/hosting/cli-devops 随着前端项目的日益复杂,自动化部署成为了提升开发效率、保证代码质量的关键一环。腾讯云CloudBase(云开发)提供的静态托管服务,结合其强大的CLI工具,可以轻松实现前端项目的快速部署。 本文将详细介绍如何利用CloudBase CLI工具,配合GitHub Actions、GitLab CI/CD和Gitee Go等主流Git平台的流水线能力,实现Git仓库中前端项目的自动部署到CloudBase静态托管服务。 ## 前置准备 ### 安装CLI工具 ```bash # 全局安装(推荐) npm install -g @cloudbase/cli # 验证安装 tcb --version ``` > 💡 **提示**:如果尚未安装,请参考 [CLI安装指南](/cli-v1/install) ### 获取API密钥 1. 登录 [腾讯云控制台/访问管理/API密钥管理](https://console.cloud.tencent.com/cam/capi) 2. 创建或获取现有的 `SecretId` 和 `SecretKey` 3. 记录您的云开发环境ID(在云开发控制台可查看) > ⚠️ **安全提醒**:请妥善保管您的API密钥,不要在代码中硬编码,务必使用CI/CD平台的密钥管理功能 ![API密钥管理](https://qcloudimg.tencent-cloud.cn/raw/eed695cf61aa1d5b3092fb7418b63900.png) ### 准备项目配置 确保您的项目具备以下条件: * ✅ 项目根目录包含构建脚本(如 `npm run build`) * ✅ 构建后生成静态文件目录(如 `dist/`、`build/` 等) * ✅ 项目可以在CI/CD环境中正常构建 * ✅ 已配置正确的Node.js版本 ## 平台集成指南 ### GitHub Actions GitHub Actions是GitHub提供的CI/CD服务,可以直接在GitHub仓库中配置工作流。 #### 步骤1:配置GitHub Secrets 在您的GitHub仓库中,依次点击 **Settings** → **Secrets and variables** → **Actions** → **New repository secret**。 添加以下Secrets: * `TCB_SECRET_ID`: 您的腾讯云SecretId * `TCB_SECRET_KEY`: 您的腾讯云SecretKey * `TCB_ENV_ID`: 您的云开发环境ID > 💡 **多环境提示**:建议为不同环境创建不同的Secrets,如 `TCB_ENV_ID_DEV` 、 `TCB_ENV_ID_PROD` ![](https://qcloudimg.tencent-cloud.cn/raw/7e0cfadf12c79dd26c1799c6123792e9.png) #### 步骤2:创建工作流文件 在项目根目录创建 `.github/workflows/deploy.yml` : 示例参考: ```yaml name: Deploy to CloudBase Static Hosting on: push: branches: - main # 主分支推送时触发 pull_request: branches: - main # PR时触发(可选) jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' cache: 'npm' # 启用npm缓存加速构建 - name: Install dependencies run: npm ci # 使用npm ci替代npm install,更适合CI环境 - name: Build project run: npm run build - name: Install CloudBase CLI run: npm install -g @cloudbase/cli - name: Login CloudBase CLI run: tcb login --apiKeyId ${{ secrets.TCB_SECRET_ID }} --apiKey ${{ secrets.TCB_SECRET_KEY }} - name: Deploy to CloudBase Static Hosting run: tcb hosting deploy ./dist /home -e ${{ secrets.TCB_ENV_ID }} ``` #### 配置参数说明 静态托管命令具体参考:[CLI管理](/cli-v1/hosting) | 参数 | 说明 | 示例 | |------|------|------| | `./dist` | 构建产物目录 | `./build` 、 `./public` | | `/home` | 云端目标路径 | `/` 、 `/static` | ### GitLab CI/CD GitLab CI/CD是GitLab内置的CI/CD服务,通过 `.gitlab-ci.yml` 文件进行配置。 #### 步骤1:配置CI/CD变量 在GitLab项目中,依次点击 **Settings** → **CI/CD** → **Variables** → **Add variable**。 添加以下变量: * `TCB_SECRET_ID`: 您的腾讯云SecretId * `TCB_SECRET_KEY`: 您的腾讯云SecretKey * `TCB_ENV_ID`: 您的云开发环境ID > 🔒 **安全建议**:将变量标记为 "Protected" 和 "Masked" 以增加安全性 #### 步骤2:创建 `.gitlab-ci.yml` 文件 在项目根目录创建 `.gitlab-ci.yml` : 示例参考: ```yaml stages: - build - deploy variables: NODE_VERSION: "18" CACHE_KEY: "$CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR" # 缓存配置 cache: key: ${CACHE_KEY} paths: - node_modules/ - .npm/ build: stage: build image: node:18-alpine before_script: - npm config set cache .npm - npm ci script: - npm run build artifacts: paths: - dist/ expire_in: 1 hour only: - main - merge_requests deploy: stage: deploy image: node:18-alpine dependencies: - build before_script: - npm install -g @cloudbase/cli script: - tcb login --apiKeyId $TCB_SECRET_ID --apiKey $TCB_SECRET_KEY - tcb hosting deploy ./dist /home -e $TCB_ENV_ID only: - main when: on_success ``` #### GitLab CI/CD 特性 * **Pipeline缓存**:自动缓存 `node_modules` 和 `.npm` 目录 * **Artifacts管理**:构建产物在stages间传递 * **环境管理**:支持多环境部署和环境变量 * **条件部署**:基于分支的条件部署 ### Gitee Go Gitee Go是Gitee(码云)提供的CI/CD服务,语法与GitHub Actions相似。 #### 步骤1:配置环境变量 在Gitee仓库中,依次点击 **管理** → **WebHooks** → **Gitee Go** → **环境变量**。 添加以下环境变量: * `TCB_SECRET_ID`: 您的腾讯云SecretId * `TCB_SECRET_KEY`: 您的腾讯云SecretKey * `TCB_ENV_ID`: 您的云开发环境ID > 🔐 **安全提示**:勾选"敏感信息"以保护您的密钥 #### 步骤2:创建工作流文件 在项目根目录创建 `.gitee/workflows/deploy.yml` : 示例参考: ```yaml name: Deploy to CloudBase Static Hosting on: push: branches: - main # 主分支推送时触发 pull_request: branches: - main # PR时触发(可选) jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: 检出代码 uses: actions/checkout@v3 - name: 设置 Node.js 环境 uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' # 启用npm缓存加速构建 - name: 安装依赖 run: npm ci # 使用npm ci替代npm install,更适合CI环境 - name: 构建项目 run: npm run build - name: 安装 CloudBase CLI run: npm install -g @cloudbase/cli - name: 登录 CloudBase CLI run: tcb login --apiKeyId ${{ secrets.TCB_SECRET_ID }} --apiKey ${{ secrets.TCB_SECRET_KEY }} - name: 部署到 CloudBase 静态托管 run: tcb hosting deploy ./dist /home -e ${{ secrets.TCB_ENV_ID }} - name: 部署成功通知 if: success() run: echo "🎉 部署成功!项目已发布到 CloudBase 静态托管" - name: 部署失败通知 if: failure() run: echo "❌ 部署失败,请检查配置和日志" ``` --- # 静态网站托管/其他参考/常见问题 > 当前文档链接: https://docs.cloudbase.net/hosting/faq ## 访问问题 ### 应用更新服务后,访问页面还停留在原有页面? **原因** 静态资源(HTML、CSS、JS、图片等)被 CDN 节点或浏览器缓存了,导致访问的仍是旧版本内容。 **解决方案** #### 方案一:URL 追加路由参数强制刷新(推荐用于快速验证) 在访问链接后追加一个**随机查询参数**,绕过 CDN 和浏览器缓存,立即拉取最新内容: ```bash # 原访问链接 https://my-env-123.xxx.tcloudbaseapp.com/ # 追加时间戳参数强制刷新(参数名可自定义,值唯一即可) https://my-env-123.xxx.tcloudbaseapp.com/?v=20260422 https://my-env-123.xxx.tcloudbaseapp.com/?t=1745304000 https://my-env-123.xxx.tcloudbaseapp.com/index.html?_=random123 ``` :::tip 原理 CDN 会将带不同查询参数的 URL 视为不同资源,触发回源拉取最新内容。此方法适合**快速验证部署结果**,无需等待 CDN 缓存过期或手动刷新。 ::: #### 方案二:手动刷新 CDN 缓存(推荐用于生产环境) 前往 [CDN 控制台 - 刷新预热](https://console.cloud.tencent.com/cdn/refresh),提交 URL 或目录刷新任务。刷新完成后所有用户即可访问到最新版本。 #### 方案三:浏览器强制刷新(本地调试用) - Windows / Linux:`Ctrl + F5` 或 `Ctrl + Shift + R` - macOS:`Cmd + Shift + R` - 或打开浏览器开发者工具(F12)→ Network 面板勾选 **Disable cache** :::info 长期优化建议 对于构建产物,建议在构建阶段为静态资源文件名添加 **内容哈希**(如 `app.a1b2c3.js`),每次内容变更时文件名自动变化,从根本上避免缓存问题。主流构建工具(Vite、Webpack、Next.js 等)默认已开启该能力。 ::: ### 静态资源访问时出现 514 错误是什么原因? **原因** 触发了 IP 访问限频配置。IP 访问限频是针对静态托管资源请求的总次数限制,而不是单个文件的请求次数限制。当用户访问一个网页时,浏览器会同时请求页面中的所有静态资源(HTML、CSS、JS、图片等),每个资源都会计入该 IP 的访问频率统计。 例如,假设您的网页包含: - 1 个 HTML 文件 - 3 个 CSS 文件 - 5 个 JS 文件 - 10 个图片文件 用户访问时会产生 19 个并发请求。如果 IP 访问限频设置为 2 QPS,那么会有 17 个请求被拒绝,返回 514 错误。 **解决方案** 1. **检查页面资源数量** - 使用浏览器开发者工具查看网络请求数量 - 统计页面加载时的总资源数 2. **调整 QPS 设置** - 进入云开发控制台 → 环境配置 → 安全管控 → 静态托管安全配置 - 将 QPS 阈值设置为页面资源数的 1.5-2 倍 - 例如:页面有 20 个资源,建议设置 QPS 为 30-40 3. **分页面类型设置** - 简单页面(5-10 个资源):建议 QPS 20-30 - 普通网站(15-30 个资源):建议 QPS 50-80 - 富媒体页面(50+ 个资源):建议 QPS 100-200 :::warning 注意 设置过低的 QPS 限制会影响正常用户的页面加载体验,建议根据实际业务需求合理配置。 ::: ## 构建问题 ### 构建失败,提示 "command not found" **原因** 缺少必要的构建工具或命令拼写错误。 **解决方案** - 检查 `package.json` 中是否定义了对应的 script - 确认构建命令拼写正确 - 检查是否安装了所需的依赖 ### 构建超时 **原因** 项目依赖过多或构建过程复杂。 **解决方案** - 开启构建缓存 - 优化依赖,移除不必要的包 - 使用更快的包管理器(如 pnpm) ## 部署路径问题 ### 部署后页面空白或 404 **原因** 路径配置不正确或路由模式问题。 **解决方案** - 检查 `base` / `publicPath` 配置是否正确 - 单页应用(SPA)需要配置路由重定向 - 确认输出目录配置正确 ### 静态资源 404 **原因** 资源路径引用错误。参考[子路径部署配置](/hosting/web-hosting-guide#子路径部署) **解决方案** - 使用相对路径引用资源 - 检查 `base` 配置是否与实际部署路径一致 - 确认资源文件已正确构建到输出目录 --- # webapiv2 Documentation > 云开发JS-SDK文档, 包含云函数、数据库、存储、文件、认证、托管等API文档 # JS SDK(V2)/初始化 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/initialization [![NPM Version](https://img.shields.io/npm/v/@cloudbase/js-sdk/v2)](https://www.npmjs.com/package/@cloudbase/js-sdk/v2) **@cloudbase/js-sdk** 让您可以在 Web 端(如 PC Web 页面、微信公众平台 H5 等)使用 JavaScript 访问 Cloudbase 服务和资源。 :::tip 提示 当前 `@cloudbase/js-sdk` 最新版本为 v3,支持在浏览器、Node.js、React Native 等通用 JavaScript 运行环境中使用,具体请参考[v3 文档](../webv3/initialization)。 若需使用 Node.js 服务端 SDK,请参考 [Node.js SDK 文档](/api-reference/server/node-sdk/initialization) ::: ## 前提条件 ### 配置安全域名 在使用 **@cloudbase/js-sdk** 之前,您需要先配置安全域名,否则会遇到 CORS 跨域错误。详情请参考:[安全来源](../../envconfig/security/intro) **配置步骤**: 1. 前往 [云开发平台/环境配置/安全配置](https://tcb.cloud.tencent.com/dev#/env/safety-source) 2. 添加网站域名(例如: `www.example.com`) 3. 配置后约 10 分钟生效 > 💡 注意: > > - 只有在安全域名列表中的域名才能使用云开发 JS SDK,这是为了保护您的数据安全 > - 本地开发时,请添加 `localhost` 或 `127.0.0.1` 以及端口到安全域名列表中 > - 如果遇到 CORS 错误,请检查安全域名配置是否正确 ## 安装并初始化 ### 安装 SDK ```bash # npm npm install @cloudbase/js-sdk@v2 -S # yarn yarn add @cloudbase/js-sdk@v2 ``` **初始化 SDK** ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id region: "ap-shanghai", // 不传默认为上海地域 }); ``` :::info 提示 请指定 `2.x.x` 版本号(当前 latest 已指向 v3),你可以设置为固定版本 ::: ```html ``` :::info 提示 请指定 `2.x.x` 版本号(当前 latest 已指向 v3),你可以设置为固定版本 ::: ```html ``` > 💡 注意:功能模块必须在内核之后引入,并且登录模块必须引入 最新的版本号 version 可以前往 [NPM](https://www.npmjs.com/package/@cloudbase/js-sdk) 查看。 ### 初始化参数 | 字段 | 类型 | 必填 | 默认值 | 说明 | | ----------- | -------- | ---- | ------------- | ----------------------------------------------------------- | | `env` | `string` | 是 | - | TCB 环境 ID | | `region` | `string` | 否 | `ap-shanghai` | 地域:`ap-shanghai`(默认)、`ap-guangzhou`、`ap-singapore` | | `lang` | `string` | 否 | `zh-CN` | 指定语言:`zh-CN`(默认)、`en-US` | | `accessKey` | `string` | 否 | - | 匿名用户鉴权参数,可以暴露在浏览器,用于请求公开访问的资源 | > ⚠️ 注意:当前使用的环境所属地域,必须与当前指定的地域信息一致! ## 登录鉴权 **js-sdk** 使用 **C 端** 用户权限,需要登录后才可调用云开发能力。 :::info 提示 使用此功能请升级 @cloudbase/js-sdk 至 2.21.0 及以后版本 ::: Publishable Key 可以暴露在浏览器,用于请求公开访问的资源。Publishable Key 实际是一个匿名用户,可以有效降低 MAU。详细介绍请 [参考文档说明](./api-key#publishable-key-and-login-authentication) 。 **获取 Publishable Key** 可前往 [云开发平台/API Key 配置](https://tcb.cloud.tencent.com/dev#/env/apikey) 中生成 **Publishable Key** **使用 Publishable Key** ```js const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id accessKey: "Publishable Key", // 填入生成的 Publishable Key }); ``` > ⚠️ 注意:Publishable Key 只能生成一次,并且是长期有效的、不能删除的,请妥善保管 详情请参考: [匿名登录](./authentication#authsigninanonymously) ```js const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const { data, error } = await app.auth.signInAnonymously(); ``` 详情请参考: [账号登录](./authentication#authsignin) ```js const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const { data, error } = await app.auth.signInWithPassword({ username: "your username", password: "your password", }); ``` ## 初始化示例 **新加坡地域** ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id region: "ap-singapore", }); ``` **使用英文提示** ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id lang: "en-US", }); ``` --- # JS SDK(V2)/文档型数据库/查询数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/database/fetch ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.database(); const _ = db.command; // 获取查询指令 ``` ## 单条查询 通过文档 ID 查询指定记录。 ```js db.collection(collectionName).doc(docId).get() ``` - **collectionName**:集合名称 - **docId**: 文档 ID ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ---------------- | | docId | string | 是 | 文档的唯一标识符 | ### 代码示例 ```javascript // 根据文档 ID 查询单条记录 const result = await db.collection('todos') .doc('docId') .get() ``` ### 返回结果 ```javascript { data: [{ _id: "todo-id-123", title: "学习 CloudBase", completed: false, // ... 其他字段 }], } ``` ## 多条查询 查询集合中的多条记录,支持条件筛选、排序、分页等。 ```js db.collection(collectionName).where(conditions).get() ``` - **collectionName**:集合名称 - **conditions**: 查询条件(可选) > ⚠️ 注意:`get()` 方法默认返回100条数据,如需更多数据请使用分页 ### 参数说明 | 方法 | 参数类型 | 必填 | 说明 | | --------- | -------------- | ---- | --------------------------------------------- | | where() | object | 否 | 查询条件,支持操作符 | | orderBy() | string, string | 否 | 排序字段和方向('asc' 或 'desc') | | limit() | number | 否 | 限制返回记录数,默认 100 条,最多返回 1000 条 | | skip() | number | 否 | 跳过记录数,用于分页 | | field() | object | 否 | 指定返回字段,true 表示返回,false 表示不返回 | ```javascript // 查询所有记录 const result = await db.collection('todos').get() // 条件查询 const result = await db.collection('todos') .where({ completed: false, priority: 'high' }) .get() ``` ## 复杂查询 ```javascript const _ = db.command // 复杂条件查询 const result = await db.collection('todos') .where({ // 年龄大于 18 age: _.gt(18), // 标签包含 '技术' tags: _.in(['技术', '学习']), // 创建时间在最近一周内 createdAt: _.gte(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)) }) .orderBy('createdAt', 'desc') // 按创建时间倒序 .limit(10) // 限制 10 条 .skip(0) // 跳过 0 条(分页) .field({ // 只返回指定字段 title: true, completed: true, createdAt: true }) .get() ``` ## 分页查询 ```javascript // 分页查询示例 const pageSize = 10 const pageNum = 1 const result = await db.collection('todos') .orderBy('createdAt', 'desc') .skip((pageNum - 1) * pageSize) .limit(pageSize) .get() ``` ## 聚合查询 聚合语法具体参考 [聚合查询](/api-reference/server/node-sdk/database/aggregate/) ```javascript // 统计查询 const result = await db.collection('todos') .aggregate() .group({ _id: '$priority', count: { $sum: 1 } }) .end() console.log('按优先级统计:', result.list) ``` > ⚠️ 注意:当调用 aggregate 方法并配合使用 lookup 方法时,需要管理员身份,否则会出现 `DATABASE_PERMISSION_DENIED` 错误码 > > lookup 方法会查询其他集合的数据,存在数据访问范围扩大的安全风险。可以使用云函数调用、node-sdk 调用或管理员身份调用js-sdk ## 地理位置查询 > ⚠️ 注意:对地理位置字段进行查询时,请建立地理位置索引,否则查询将失败 ```js const _ = db.command // 查询附近的用户(按距离排序) db.collection("users").where({ location: _.geoNear({ geometry: new db.Geo.Point(116.404, 39.915), // 天安门坐标 maxDistance: 1000, // 最大距离1000米 minDistance: 0 // 最小距离0米 }) }).get() // 查询指定区域内的用户 db.collection("users").where({ location: _.geoWithin({ geometry: polygon // 使用上面定义的多边形 }) }).get() // 查询与路径相交的用户 db.collection("users").where({ location: _.geoIntersects({ geometry: line // 使用上面定义的路径 }) }).get() ``` --- # JS SDK(V2)/文档型数据库/新增数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/database/add ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.database(); const _ = db.command; // 获取查询指令 ``` ## 单条新增 向集合中添加一条新记录。 ```js db.collection(collectionName).add(data) ``` - **collectionName**:集合名称 - **data**: 要新增的数据对象 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ---- | ------ | ---- | ---------------- | | data | object | 是 | 要新增的数据对象 | ### 代码示例 ```javascript // 添加单条记录 const result = await db.collection('todos').add({ title: '学习 CloudBase', content: '完成数据库操作教程', completed: false, priority: 'high', createdAt: new Date(), tags: ['学习', '技术'] }) console.log('新增成功,文档 ID:', result._id) ``` ## 地理位置新增 ```js // 创建地理位置点 const point = new db.Geo.Point(longitude, latitude); // 创建地理路径 const line = new db.Geo.LineString([ new db.Geo.Point(lngA, latA), new db.Geo.Point(lngB, latB) ]); // 创建地理区域 const polygon = new db.Geo.Polygon([ new db.Geo.LineString([ new db.Geo.Point(lngA, latA), new db.Geo.Point(lngB, latB), new db.Geo.Point(lngC, latC), new db.Geo.Point(lngA, latA) // 闭合 ]) ]); const result = await db.collection('todos').add({ location: point, path: line, area: polygon }) ``` --- # JS SDK(V2)/文档型数据库/更新数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/database/update ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.database(); const _ = db.command; // 获取查询指令 ``` ## 单条更新 通过文档 ID 更新指定记录。 ```js db.collection(collectionName).doc(docId).update(data) ``` - **collectionName**:集合名称 - **docId**: 文档 ID - **data**: 要更新的数据对象 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ---------------- | | docId | string | 是 | 要更新的文档 ID | | data | object | 是 | 要更新的数据对象 | ### 代码示例 ```javascript // 更新指定文档 const result = await db.collection('todos') .doc('todo-id') .update({ title: '学习 CloudBase 数据库', completed: true, updatedAt: new Date(), completedBy: 'user123' }) ``` ## 批量更新 根据查询条件批量更新多条记录。 ```js db.collection(collectionName).where(conditions).update(data) ``` - **collectionName**:集合名称 - **conditions**: 查询条件对象 - **data**: 要更新的数据对象 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | -------------------------- | | where | object | 是 | 查询条件,确定要更新的记录 | | data | object | 是 | 要更新的数据对象 | ### 代码示例 ```javascript // 批量更新多条记录 const batchResult = await db.collection('todos') .where({ completed: false, priority: 'low' }) .update({ priority: 'normal', updatedAt: new Date() }) ``` ## 更新或创建 更新文档,如果不存在则创建: ```javascript const setResult = await db.collection('todos') .doc("doc-id") .set({ completed: false, priority: 'low' }) ``` --- # JS SDK(V2)/文档型数据库/删除数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/database/delete ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models ``` ## 单条删除 根据条件删除单条记录。 ```js models.modelName.delete(options) ``` - **modelName**: 数据模型名称 - **options**: 删除参数 ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **filter.where** | `object` | 是 | 查询条件,确定要删除的记录 | ### 代码示例 ```javascript // 删除单条记录 const result = await models.todo.delete({ filter: { where: { _id: { $eq: "todo-id" } } } }) console.log('删除结果:', result) ``` ## 批量删除 根据查询条件批量删除多条记录。 ```js models.modelName.deleteMany(options) ``` - **modelName**: 数据模型名称 - **options**: 删除参数 ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **filter.where** | `object` | 是 | 查询条件,确定要删除的记录 | ### 代码示例 ```javascript // 批量删除已完成的任务 const result = await models.todo.deleteMany({ filter: { where: { completed: { $eq: true }, createdAt: { $lt: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000) // 90天前 } } } }) console.log('批量删除结果:', result) --- # JS SDK(V2)/文档型数据库/操作符 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/database/command CloudBase JS SDK 提供了完整的数据库操作能力,本文将着重介绍查询相关能力 `db.command` 对象用于指定数据库操作符,我们一般如下定义command方便使用: ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", }); const db = app.database(); const _ = db.command; ``` ### 查询操作符 | 操作符 | 说明 | 示例 | | ------ | ------------ | ------------------------------- | | `eq` | 等于 | `_.eq(value)` | | `neq` | 不等于 | `_.neq(value)` | | `gt` | 大于 | `_.gt(value)` | | `gte` | 大于等于 | `_.gte(value)` | | `lt` | 小于 | `_.lt(value)` | | `lte` | 小于等于 | `_.lte(value)` | | `in` | 包含于数组 | `_.in([value1, value2])` | | `nin` | 不包含于数组 | `_.nin([value1, value2])` | | `and` | 逻辑与 | `_.and(condition1, condition2)` | | `or` | 逻辑或 | `_.or(condition1, condition2)` | ### 字段更新操作符 | 操作符 | 说明 | 示例 | | --------- | ------------ | ------------------ | | `set` | 设置字段值 | `_.set(value)` | | `inc` | 数值自增 | `_.inc(1)` | | `mul` | 数值自乘 | `_.mul(2)` | | `remove` | 删除字段 | `_.remove()` | | `push` | 数组末尾添加 | `_.push(value)` | | `pop` | 数组末尾删除 | `_.pop()` | | `unshift` | 数组开头添加 | `_.unshift(value)` | | `shift` | 数组开头删除 | `_.shift()` | ### 地理位置操作符 | 操作符 | 说明 | 示例 | | --------------- | ------------------------ | -------------------------- | | `geoNear` | 按距离查询附近位置 | `_.geoNear(options)` | | `geoWithin` | 查询指定区域内的位置 | `_.geoWithin(options)` | | `geoIntersects` | 查询与指定图形相交的位置 | `_.geoIntersects(options)` | --- # JS SDK(V2)/PostgreSQL 数据库/查询数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/postgresql/fetch ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 基础查询 通过 `select()` 方法查询表数据,支持条件筛选、关联查询等功能。 ```js db.from(tableName).select(columns, options) ``` - **tableName**:表名称 - **columns**:要检索的列,用逗号分隔 - **options**:查询选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | -------------------------------------------------------------------- | | columns | string | 否 | 要检索的列,用逗号分隔。支持使用 `aliasName:foreignKey(columns)` 语法为外键关联字段设置别名
`columns` 取 `*` 为查询所有字段 | | options | object | 否 | 查询选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------- | ---- | ------------------------------------------------------ | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | | head | boolean | 否 | 设置为 `true` 时不返回数据,仅在需要计数时有用 | ### 代码示例 #### 查询所有数据 ```javascript // 查询 articles 表中的所有数据 const { data, error } = await db.from("articles").select(); console.log('查询结果:', data); ``` #### 查询指定列 ```javascript // 只查询文章的标题和创建时间 const { data, error } = await db.from("articles").select("id, title, created_at"); console.log('查询结果:', data); ``` #### 查询外键关联数据 ```javascript // 使用别名 'category' 代替默认的 'categories' 字段名 const { data, error } = await db.from("articles").select(` id, title, created_at, category:categories(name) `); console.log('查询结果:', data); ``` #### 返回结果 ```javascript { data: [ { id: 1, title: "文章标题", created_at: "2023-01-01T00:00:00Z", category: { name: "前端开发" } }, // ... 其他记录 ], error: null } ``` ## 关联表查询 通过关联查询可以同时获取多个表的数据,支持一对一、一对多等关联关系。 ### 基础关联查询 ```javascript // 查询文章数据,同时获取关联的分类信息 const { data, error } = await db.from("articles").select(` title, categories ( name ) `); console.log('查询结果:', data); ``` ### 多重关联查询 ```javascript // 查询文章,同时获取创建者和修改者的信息 const { data, error } = await db.from("articles").select(` title, created_by:users!articles_created_by_fkey(name), updated_by:users!articles_updated_by_fkey(name) `); console.log('查询结果:', data); ``` > 💡 注意:当同一个表通过不同的外键关联多次时,需要使用外键约束名来区分不同的关联关系。 ### 嵌套关联查询 ```javascript // 查询分类及其下的所有文章,以及文章的作者信息 const { data, error } = await db.from("categories").select(` name, articles ( title, users ( name ) ) `); console.log('查询结果:', data); ``` ## 高级查询 ### 条件过滤查询 ```javascript // 查询特定分类下的所有文章 const { data, error } = await db .from("articles") .select("title, categories(*)") .eq("categories.name", "技术文章"); console.log('查询结果:', data); ``` ### 计数查询 ```javascript // 获取每个分类及其包含的文章数量 const { data, error } = await db .from("categories") .select(`*, articles(count)`); console.log('查询结果:', data); ``` ### 仅获取总数 ```javascript // 只获取文章总数,不返回具体数据 const { count, error } = await db .from("articles") .select("*", { count: "exact", head: true }); console.log('总数:', count); ``` ### 内连接查询 ```javascript // 只获取有分类的文章,使用内连接确保分类存在 const { data, error } = await db .from("articles") .select("title, categories!inner(name)") .eq("categories.name", "教程") .limit(10); console.log('查询结果:', data); ``` > 💡 注意:`!inner` 表示内连接,只返回那些在关联表中存在匹配记录的数据。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 - [修饰符](./modifiers) - 了解查询修饰符的使用方法 --- # JS SDK(V2)/PostgreSQL 数据库/新增数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/postgresql/insert ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 新增数据 通过 `insert()` 方法向表中插入数据,支持单行和批量插入。 ```js db.from(tableName).insert(values, options) ``` - **tableName**:表名称 - **values**:要插入的数据 - **options**:插入选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | --------------- | ---- | ------------------------------------------------------ | | values | object \| Array | 是 | 要插入的值。传递对象以插入单行,或传递数组以插入多行。 | | options | object | 否 | 插入选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------ | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 创建记录 ```javascript // 向 articles 表中插入一条记录 const { error } = await db .from("articles") .insert({ title: "新文章标题", content: "文章内容" }); console.log('插入结果:', error ? '失败' : '成功'); ``` #### 创建记录并返回数据 ```javascript // 插入记录并返回插入的数据 const { data, error } = await db .from("articles") .insert({ title: "新文章标题", content: "文章内容" }) .select(); console.log('插入结果:', data); ``` > 💡 注意:仅当表中只有一个主键,且该主键为自增类型时,`.select()` 方法才会返回插入的行。 #### 批量创建记录 ```javascript // 一次性插入多条记录 const { error } = await db.from("articles").insert([ { title: "第一篇文章", content: "第一篇文章内容" }, { title: "第二篇文章", content: "第二篇文章内容" }, ]); console.log('批量插入结果:', error ? '失败' : '成功'); ``` --- # JS SDK(V2)/PostgreSQL 数据库/更新数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/postgresql/update ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 更新数据 通过 `update()` 方法更新表中的数据,需要结合过滤器使用以定位要更新的行。 ```js db.from(tableName).update(values, options).filter() ``` - **tableName**:表名称 - **values**:要更新的数据 - **options**:更新选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ---------- | | values | object | 是 | 要更新的值 | | options | object | 否 | 更新选项 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------------------ | | count | string | 否 | 计数算法,用于计算更新的行数:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 更新数据 ```javascript // 更新 articles 表中 id 为 1 的记录,将 title 字段改为"新标题" const { error } = await db .from("articles") .update({ title: "新标题" }) .eq("id", 1); console.log('更新结果:', error ? '失败' : '成功'); ``` #### 批量更新 ```javascript // 更新所有状态为 draft 的文章,将状态改为 published const { error } = await db .from("articles") .update({ status: "published" }) .eq("status", "draft"); console.log('批量更新结果:', error ? '失败' : '成功'); ``` ### 返回结果 ```javascript { data: null, error: null } ``` > 💡 注意:`update()` 方法必须与 [过滤器](./filters) 结合使用,以定位您希望更新的行。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 --- # JS SDK(V2)/PostgreSQL 数据库/更新或创建数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/postgresql/upsert ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 更新或创建数据 通过 `upsert()` 方法执行更新或插入操作,如果记录不存在则插入,存在则更新。 ```js db.from(tableName).upsert(values, options) ``` - **tableName**:表名称 - **values**:要 upsert 的数据 - **options**:upsert 选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | -------------------- | ---- | ---------------------------------------------------------------- | | values | object \| Array | 是 | 要 upsert 的值。传递对象以 upsert 单行,或传递数组以 upsert 多行 | | options | object | 否 | upsert 选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ---------------- | ------- | ---- | ----------------------------------------------------------------------------------------------------------------------------- | | count | string | 否 | 用于计算 upserted 行数的计数算法:`"exact"` - 底层执行 `COUNT(*)` | | ignoreDuplicates | boolean | 否 | 如果为 true,则忽略重复行。如果为 false,则重复行与现有行合并 | | onConflict | string | 否 | 逗号分隔的唯一索引列,用于指定如何确定重复行。当所有指定的列都相等时,两行被视为重复。在 PostgreSQL 中,这通常对应于唯一索引或主键 | ### 代码示例 #### Upsert 数据 ```javascript // 如果 articles 表中存在 id 为 1 的记录则更新 title 为"PostgreSQL 教程",不存在则插入新记录 const { data, error } = await db .from("articles") .upsert({ id: 1, title: "PostgreSQL 教程" }); console.log('Upsert 结果:', data); ``` #### 批量 Upsert 数据 ```javascript // 批量 upsert 多条记录:id 为 1 的记录 title 设为"PostgreSQL 教程",id 为 2 的记录 title 设为"Redis 指南" const { data, error } = await db.from("articles").upsert([ { id: 1, title: "PostgreSQL 教程" }, { id: 2, title: "Redis 指南" }, ]); console.log('批量 Upsert 结果:', data); ``` #### 指定冲突列 ```javascript // 根据 title 字段判断冲突,如果存在 title 为"唯一标题"的记录则更新,否则插入 id 为 42 的新记录 const { data, error } = await db .from("articles") .upsert( { id: 42, title: "唯一标题", content: "文章内容" }, { onConflict: "title" } ); console.log('指定冲突列 Upsert 结果:', data); ``` ### 返回结果 ```javascript { data: [ { id: 1, title: "PostgreSQL 教程", content: "文章内容" } ], error: null } ``` > 💡 注意:使用 `upsert()` 时必须包含主键列,这样才能正确判断是插入新行还是更新现有行。在 PostgreSQL 中,upsert 通常通过 `ON CONFLICT DO UPDATE` 语法实现,当插入的数据与现有主键或唯一索引冲突时,会执行更新操作。`onConflict` 参数用于指定冲突判断的列,对应 PostgreSQL 中的唯一索引列。 --- # JS SDK(V2)/PostgreSQL 数据库/删除数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/postgresql/delete ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 删除数据 通过 `delete()` 方法删除表中的数据,需要结合过滤器使用以定位要删除的行。 ```js db.from(tableName).delete(options).filter() ``` - **tableName**:表名称 - **options**:删除选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ------------ | | options | object | 否 | 删除选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------ | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 删除单个记录 ```javascript // 删除 articles 表中 id 为 1 的记录 const { error } = await db.from("articles").delete().eq("id", 1); console.log('删除结果:', error ? '失败' : '成功'); ``` #### 批量删除记录 ```javascript // 批量删除 articles 表中 id 为 1、2、3 的多条记录 const { error } = await db.from("articles").delete().in("id", [1, 2, 3]); console.log('批量删除结果:', error ? '失败' : '成功'); ``` #### 条件删除 ```javascript // 删除所有状态为 draft 的文章 const { error } = await db.from("articles").delete().eq("status", "draft"); console.log('条件删除结果:', error ? '失败' : '成功'); ``` ### 返回结果 ```javascript { data: null, error: null } ``` > ⚠️ 注意:`delete()` 方法必须与 [过滤器](./filters) 结合使用,以定位您希望删除的行。确保您提供的条件准确表示您打算删除的所有记录,以避免意外删除数据。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 --- # JS SDK(V2)/PostgreSQL 数据库/过滤器 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/postgresql/filters 过滤器允许只返回符合特定条件的行。 过滤器可以用于 `select()`、`update()`、`upsert()` 和 `delete()` 查询。 ## eq 仅匹配列值等于指定值的行。 要检查列值是否为 `NULL`,应该使用 `.is()` 而不是 `eq`。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .eq("title", "腾讯云开发"); ``` ## neq 仅匹配列值不等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 不等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .neq("title", "腾讯云开发"); ``` ## gt 仅匹配列值大于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 大于 2 的记录 const { data, error } = await db.from("articles").select().gt("id", 2); ``` ## gte 仅匹配列值大于或等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 大于或等于 2 的记录 const { data, error } = await db.from("articles").select().gte("id", 2); ``` ## lt 仅匹配列值小于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 小于 2 的记录 const { data, error } = await db.from("articles").select().lt("id", 2); ``` ## lte 仅匹配列值小于或等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 小于或等于 2 的记录 const { data, error } = await db.from("articles").select().lte("id", 2); ``` ## like 仅匹配列值符合特定模式的行(是否区分大小写受校验规则约束)。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------- | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | pattern | string | 必需 | 要匹配的模式 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 包含 "cloudbase" 的记录 const { data, error } = await db .from("articles") .select() .like("title", "%cloudbase%"); ``` ## ilike 仅匹配列值符合特定模式的行(不区分大小写,不受校验规则约束)。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------- | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | pattern | string | 必需 | 要匹配的模式 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 包含 "cloudbase" 的记录(不区分大小写) const { data, error } = await db .from("articles") .select() .ilike("title", "%cloudbase%"); ``` ## is 仅匹配列值等于指定值的行。 对于非布尔列,主要用于检查列值是否为 `NULL`; 对于布尔列,也可以设置为 `true` 或 `false`,行为与 `.eq()` 相同。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | Object | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 为 null 的记录 const { data, error } = await db.from("articles").select().is("title", null); ``` ## in 仅匹配列值包含在指定数组中的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ---------------- | | column | string | 必需 | 要过滤的列 | | values | Array | 必需 | 用于过滤的值数组 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 在指定数组 ["腾讯云开发", "云开发"] 中的记录 const { data, error } = await db .from("articles") .select() .in("title", ["腾讯云开发", "云开发"]); ``` ## contains 仅匹配列值包含指定值的行。 适用于数组列、范围列和 JSONB 列。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------------- | ---- | ---------------- | | column | string | 必需 | 要过滤的列 | | value | Array\|Object | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 tags 包含 ["技术", "云计算"] 的记录(数组列) const { data, error } = await db .from("articles") .select() .contains("tags", ["技术", "云计算"]); ``` ```javascript // 从 articles 表中查询所有 metadata 包含指定 JSON 对象的记录(JSONB 列) const { data, error } = await db .from("articles") .select() .contains("metadata", { category: "技术" }); ``` ## containedBy 仅匹配列值被指定值包含的行。 适用于数组列、范围列和 JSONB 列。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------------- | ---- | ---------------- | | column | string | 必需 | 要过滤的列 | | value | Array\|Object | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 tags 被 ["技术", "云计算", "前端", "后端"] 包含的记录 const { data, error } = await db .from("articles") .select() .containedBy("tags", ["技术", "云计算", "前端", "后端"]); ``` ## overlaps 仅匹配列值与指定值有重叠元素的行。 适用于数组列和范围列。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------------- | ---- | ---------------- | | column | string | 必需 | 要过滤的列 | | value | Array\|string | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 tags 与 ["技术", "前端"] 有重叠元素的记录 const { data, error } = await db .from("articles") .select() .overlaps("tags", ["技术", "前端"]); ``` ## textSearch 仅匹配 `tsvector` 列值满足指定 `tsquery` 全文搜索条件的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------- | ------ | ---- | ---------------------------------------------------------------- | | column | string | 必需 | 要过滤的文本搜索列 | | query | string | 必需 | 用于匹配的全文搜索查询 | | options | object | 可选 | 命名参数 | | options.config | string | 可选 | 文本搜索配置(如 `'english'`、`'simple'`) | | options.type | string | 可选 | 搜索类型:`'plain'`(默认)、`'phrase'` 或 `'websearch'` | ### 代码示例 ```javascript // 从 articles 表中全文搜索 content 列包含 "云开发" 的记录 const { data, error } = await db .from("articles") .select() .textSearch("content", "云开发"); ``` ```javascript // 使用 websearch 语法进行全文搜索 const { data, error } = await db .from("articles") .select() .textSearch("content", "'云开发' OR '云计算'", { type: "websearch", config: "simple", }); ``` ## rangeGt 仅适用于范围列。仅匹配列中每个元素都大于范围中任何元素的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | -------------- | | column | string | 必需 | 要过滤的范围列 | | range | string | 必需 | 用于过滤的范围 | ### 代码示例 ```javascript // 从 articles 表中查询所有 created_at 大于范围 "[2023-01-01 00:00, 2023-01-02 00:00)" 中的记录 const { data, error } = await db .from("articles") .select() .rangeGt("created_at", "[2023-01-01 00:00, 2023-01-02 00:00)"); ``` ## rangeGte 仅适用于范围列。仅匹配列中每个元素都包含在范围内或大于范围中任何元素的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | -------------- | | column | string | 必需 | 要过滤的范围列 | | range | string | 必需 | 用于过滤的范围 | ### 代码示例 ```javascript // 从 articles 表中查询所有 created_at 大于或等于范围 "[2023-01-01 08:30, 2023-01-01 09:30)" 中的记录 const { data, error } = await db .from("articles") .select() .rangeGte("created_at", "[2023-01-01 08:30, 2023-01-01 09:30)"); ``` ## rangeLt 仅适用于范围列。仅匹配列中每个元素都小于范围中任何元素的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | -------------- | | column | string | 必需 | 要过滤的范围列 | | range | string | 必需 | 用于过滤的范围 | ### 代码示例 ```javascript // 从 articles 表中查询所有 created_at 小于范围 "[2023-01-01 12:00, 2023-01-01 13:00)" 中的记录 const { data, error } = await db .from("articles") .select() .rangeLt("created_at", "[2023-01-01 12:00, 2023-01-01 13:00)"); ``` ## rangeLte 仅适用于范围列。仅匹配列中每个元素都包含在范围内或小于范围中任何元素的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | -------------- | | column | string | 必需 | 要过滤的范围列 | | range | string | 必需 | 用于过滤的范围 | ### 代码示例 ```javascript // 从 articles 表中查询所有 created_at 小于或等于范围 "[2023-01-01 14:00, 2023-01-01 16:00)" 中的记录 const { data, error } = await db .from("articles") .select() .rangeLte("created_at", "[2023-01-01 14:00, 2023-01-01 16:00)"); ``` ## rangeAdjacent 仅适用于范围列。仅匹配列范围与指定范围互斥且没有重叠元素的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | -------------- | | column | string | 必需 | 要过滤的范围列 | | range | string | 必需 | 用于过滤的范围 | ### 代码示例 ```javascript // 从 articles 表中查询所有 created_at 与范围 "[2023-01-01 12:00, 2023-01-01 13:00)" 互斥且没有重叠元素的记录 const { data, error } = await db .from("articles") .select() .rangeAdjacent("created_at", "[2023-01-01 12:00, 2023-01-01 13:00)"); ``` ## match 仅匹配查询键中每个列都等于其关联值的行,相当于多个 `.eq()` 的简写形式。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ----- | ------------------------------------- | ---- | -------------------------------------- | | query | `Record` | 必需 | 过滤对象,列名作为键映射到它们的过滤值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 等于 2 且 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .match({ id: 2, title: "腾讯云开发" }); ``` ## not 仅匹配不满足过滤条件的行。 与大多数过滤器不同,操作符和值按原样使用,需要遵循 PostgreSQL 语法,还需要确保它们已正确转义。 `not()` 期望使用原始的 PostgreSQL 语法作为过滤器值。 ```javascript .not('id', 'in', '(5,6,7)') // 对 `in` 过滤器使用 `()` .not('name', 'like', '%test%') // 使用 `not like` 进行模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | -------- | ------ | ---- | ----------------------------------- | | column | string | 必需 | 要过滤的列 | | operator | string | 必需 | 要取反的过滤操作符,遵循 PostgreSQL 语法 | | value | any | 必需 | 过滤值,遵循 PostgreSQL 语法 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 不为 null 的记录 const { data, error } = await db .from("articles") .select() .not("title", "is", null); ``` ## or 仅匹配满足至少一个过滤条件的行。 与大多数过滤器不同,过滤器按原样使用,需要遵循 PostgreSQL 语法,还需要确保它们已正确转义。 目前无法跨多个表进行 `.or()` 过滤。 `or()` 期望使用原始的 PostgreSQL 语法作为过滤器名称和值。 ```javascript .or('id.in.(5,6,7), name.like.%test%') // 对 `in` 过滤器使用 `()`,对模糊匹配使用 `like` 和 `%` .or('id.in.(5,6,7), name.not.like.%test%') // 使用 `not.like` 进行反向模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | ----------------------- | ------ | ---- | ------------------------------- | | filters | string | 必需 | 要使用的过滤器,遵循 PostgreSQL 语法 | | options | object | 必需 | 命名参数 | | options.referencedTable | string | 可选 | 设置为过滤引用表而不是父表 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 等于 2 或者 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .or(`id.eq.2,title.eq.腾讯云开发`); ``` ## filter 仅匹配满足过滤条件的行,这是一个逃生舱口,应该尽可能使用特定的过滤器方法。 与大多数过滤器不同,操作符和值按原样使用,需要遵循 PostgreSQL 语法,还需要确保它们已正确转义。 `filter()` 期望使用原始的 PostgreSQL 语法作为过滤器值。 ```javascript .filter('id', 'in', '(5,6,7)') // 对 `in` 过滤器使用 `()` .filter('name', 'like', '%test%') // 使用 `like` 进行模糊匹配 .filter('name', 'not.like', '%test%') // 使用 `not.like` 进行反向模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | -------- | ------ | ---- | --------------------------- | | column | string | 必需 | 要过滤的列 | | operator | string | 必需 | 过滤操作符,遵循 PostgreSQL 语法 | | value | any | 必需 | 过滤值,遵循 PostgreSQL 语法 | ### 代码示例 ```javascript // 查询 title 在指定值列表中的记录 // 从 articles 表中查询所有 title 在指定值列表 ["腾讯云开发", "云开发"] 中的记录 const { data, error } = await db .from("articles") .select() .filter("title", "in", "(腾讯云开发,云开发)"); // 在引用表上过滤 // 从 articles 表中查询所有关联的 categories.name 等于 "技术" 的记录 const { data, error } = await db .from("articles") .select() .filter("categories.name", "eq", "技术"); ``` --- # JS SDK(V2)/PostgreSQL 数据库/修饰符 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/postgresql/modifiers 修饰符用于改变响应的格式,与过滤器不同,它们作用于行级别以上的操作。 过滤器仅返回匹配特定条件的行而不改变行的形状,而修饰符允许改变响应的格式。 ## select 默认情况下,`.insert()` 不会返回插入的行。通过调用此方法,插入的行将在数据中返回。 > ⚠️ 注意:仅当表中只有一个主键,且该主键为自增类型时,`.select()` 方法才会返回插入的行。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------------------- | | columns | string | 否 | 要检索的列,用逗号分隔 | ### 代码示例 ```javascript // 在 articles 表中执行 upsert 操作,并返回修改后的完整记录 const { data, error } = await db .from("articles") .insert({ id: 1, title: "腾讯云开发新功能" }) .select(); ``` ## order 对查询结果进行排序。 可以多次调用此方法来按多个列排序。 可以对引用的表进行排序,但仅当在查询中使用 `!inner` 时,它才会影响父表的排序。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------- | | column | string | 是 | 要排序的列 | | options | object | 否 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------- | ---- | --------------------------------------------------------------- | | ascending | boolean | 否 | 如果为 `true`,结果将按升序排列 | | nullsFirst | boolean | 否 | 如果为 `true`,`null` 值将首先出现。如果为 `false`,`null` 值将最后出现 | | referencedTable | string | 否 | 设置为按引用表的列排序 | ### 代码示例 ```javascript // 按发布时间降序排列文章 const { data, error } = await db .from("articles") .select("id, title, published_at") .order("published_at", { ascending: false }); ``` #### 对引用表排序 ```javascript // 对引用表 categories 按 name 降序排列 const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .order("name", { referencedTable: "categories", ascending: false }); ``` ```javascript // 按引用表 category 的 name 列升序排列 const { data, error } = await db .from("articles") .select(` title, category:categories ( name ) `) .order("category(name)", { ascending: true }); ``` ## limit 限制返回的行数。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------------- | | count | number | 是 | 要返回的最大行数 | | options | object | 否 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------ | ---- | ---------------------------------------- | | referencedTable | string | 否 | 设置为限制引用表的行数,而不是父表的行数 | ### 代码示例 ```javascript // 限制返回 5 篇文章 const { data, error } = await db.from("articles").select("title").limit(5); ``` #### 限制引用表的行数 ```javascript // 每篇文章只返回 3 个分类 const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .limit(3, { referencedTable: "categories" }); ``` ## range 限制查询结果的范围。 通过从偏移量 `from` 开始到 `to` 结束来限制查询结果,只有在此范围内的记录会被返回。 这遵循查询顺序,如果没有排序子句,范围行为可能会不可预期。 `from` 和 `to` 值是基于 0 的且包含边界:`range(1, 3)` 将包括查询的第二、第三和第四行。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ------------------ | | from | number | 是 | 限制结果的起始索引 | | to | number | 是 | 限制结果的结束索引 | | options | object | 是 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------ | ---- | ---------------------------------------- | | referencedTable | string | 否 | 设置为限制引用表的行数,而不是父表的行数 | ### 代码示例 ```javascript // 获取文章列表的第 1-2 条记录(包含边界) const { data, error } = await db.from("articles").select("title").range(0, 1); ``` #### 对引用表使用范围限制 ```javascript // 每篇文章只返回前 2 个分类(索引 0-1) const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .range(0, 1, { referencedTable: "categories" }); ``` ## abortSignal > 💡 注意:此方法仅在 Web 环境下可用。 为 `fetch` 请求设置 `AbortSignal`。 可以使用此功能为请求设置超时。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------ | ----------- | ---- | ----------------------------- | | signal | AbortSignal | 是 | 用于 `fetch` 请求的 `AbortSignal` | ### 代码示例 ```javascript // 使用 AbortController 手动中止请求 const ac = new AbortController(); ac.abort(); const { data, error } = await db .from("articles") .select() .abortSignal(ac.signal); ``` ```javascript // 使用 AbortSignal.timeout 设置 1 秒超时 const { data, error } = await db .from("articles") .select() .abortSignal(AbortSignal.timeout(1000)); ``` ## single 检索单行数据。 将数据作为单个对象返回,而不是对象数组。 查询结果必须只有一行(例如使用 `.limit(1)` ),否则此方法会返回错误。 ### 代码示例 ```javascript // 获取第一篇文章的标题 const { data, error } = await db .from("articles") .select("title") .limit(1) .single(); ``` ## maybeSingle 检索零行或一行数据。 将数据作为单个对象返回,而不是对象数组。 查询结果必须为零行或一行(例如使用 `.limit(1)` ),否则此方法会返回错误。 ### 代码示例 ```javascript // 根据标题查找文章,可能不存在 const { data, error } = await db .from("articles") .select() .eq("title", "腾讯云开发新功能") .maybeSingle(); ``` ## overrideTypes 部分覆盖或替换成功响应的类型。 覆盖响应中 `data` 字段的返回类型,对于类型安全的查询结果转换非常有用。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ------------ | | type | T | 是 | 要覆盖的类型 | | options | object | 否 | 选项对象 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | ------ | ------- | ---- | -------------------------------------- | | merge | boolean | 否 | 如果为 `false`,则完全替换类型而不是合并 | ### 代码示例 #### 完全覆盖数组类型 ```javascript // 将响应数据完全覆盖为自定义数组类型,merge: false 表示完全替换而非合并 const { data } = await db .from("articles") .select() .overrideTypes, { merge: false }>(); ``` #### 完全覆盖对象类型 ```javascript // 与 maybeSingle 一起使用,将单个对象响应完全覆盖为自定义类型 const { data } = await db .from("articles") .select() .maybeSingle() .overrideTypes(); ``` #### 部分覆盖数组类型 ```javascript // 部分覆盖数组元素类型,只指定需要改变的字段类型(如 status 字段) const { data } = await db .from("articles") .select() .overrideTypes>(); ``` #### 部分覆盖对象类型 ```javascript // 部分覆盖单个对象类型,只指定需要改变的字段类型(如 status 字段) const { data } = await db .from("articles") .select() .maybeSingle() .overrideTypes<{ status: "A" | "B" }>(); ``` --- # JS SDK(V2)/PostgreSQL 数据库/RPC 调用 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/postgresql/rpc 通过 JS SDK 调用 PostgreSQL 数据库中的自定义函数(Stored Procedures / Functions)。 > 💡 提示:RPC 调用基于 PostgREST 协议,可以调用在 PostgreSQL 中创建的任何自定义函数。 ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 基础语法 ```js db.rpc(functionName, params, options); ``` ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------------ | ------ | ---- | ------------------------------- | | functionName | string | 是 | 要调用的数据库函数名称 | | params | object | 否 | 传递给函数的参数,以键值对形式 | | options | object | 否 | 选项配置 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | ------ | ------- | ---- | ------------------------------------------------------------------------------------- | | head | boolean | 否 | 如果为 `true`,则不返回数据,仅返回 `count` 值(需配合 `count` 选项使用) | | count | string | 否 | 计数算法,可选值为 `"exact"`、`"planned"` 或 `"estimated"` | | get | boolean | 否 | 如果为 `true`,使用 HTTP GET 请求调用函数(仅适用于 `IMMUTABLE` 或 `STABLE` 的只读函数) | ## 创建函数 在调用 RPC 之前,需要先在 PostgreSQL 数据库中创建函数。可以通过 DMC 数据库管理工具执行 SQL 创建函数。 ```sql -- 创建一个简单的加法函数 CREATE OR REPLACE FUNCTION add_numbers(a integer, b integer) RETURNS integer AS $$ SELECT a + b; $$ LANGUAGE sql; -- 创建一个查询文章的函数 CREATE OR REPLACE FUNCTION search_articles(keyword text) RETURNS SETOF articles AS $$ SELECT * FROM articles WHERE title ILIKE '%' || keyword || '%'; $$ LANGUAGE sql; -- 创建一个统计函数 CREATE OR REPLACE FUNCTION get_article_stats() RETURNS json AS $$ SELECT json_build_object( 'total', COUNT(*), 'latest', MAX(published_at) ) FROM articles; $$ LANGUAGE sql; ``` ## 代码示例 ### 带参数调用 ```javascript // 调用加法函数 const { data, error } = await db.rpc("add_numbers", { a: 1, b: 2 }); console.log(data); // 3 ``` ### 无参数调用 ```javascript // 调用统计函数 const { data, error } = await db.rpc("get_article_stats"); console.log(data); // { total: 15, latest: "2026-04-01T00:00:00Z" } ``` ### 调用返回表数据的函数 当函数返回 `SETOF
` 时,返回值为数组格式,并且支持链式调用过滤器和修饰符。 ```javascript // 搜索包含关键字的文章 const { data, error } = await db.rpc("search_articles", { keyword: "云开发", }); ``` ### 对返回结果进行过滤和排序 当函数返回表数据时,可以像普通查询一样使用过滤器和修饰符。 ```javascript // 搜索文章,按发布时间降序排列,限制返回 5 条 const { data, error } = await db .rpc("search_articles", { keyword: "云开发" }) .select("title, published_at") .order("published_at", { ascending: false }) .limit(5); ``` ### 返回单条记录 ```javascript // 获取单条记录 const { data, error } = await db .rpc("search_articles", { keyword: "云开发" }) .limit(1) .single(); ``` ### 使用过滤器 ```javascript // 对 RPC 返回的表数据应用过滤条件 const { data, error } = await db .rpc("search_articles", { keyword: "云开发" }) .eq("status", "published") .gte("published_at", "2026-01-01"); ``` ### 使用 GET 请求调用只读函数 对于标记为 `IMMUTABLE` 或 `STABLE` 的只读函数,可以使用 GET 请求调用以获得更好的缓存效果。 ```javascript // 使用 GET 请求调用只读函数 const { data, error } = await db.rpc( "add_numbers", { a: 1, b: 2 }, { get: true } ); ``` ### 获取行数统计 ```javascript // 仅获取行数统计,不返回数据 const { count, error } = await db.rpc( "search_articles", { keyword: "云开发" }, { count: "exact", head: true } ); console.log(count); // 返回匹配的记录数 ``` ## 函数参数类型 PostgREST 支持多种 PostgreSQL 函数参数类型: | 参数类型 | 说明 | JS SDK 传参示例 | | :------------------: | :------: | :--------------------------------------: | | `integer` | 整数 | `{ id: 1 }` | | `text` / `varchar` | 文本 | `{ name: "test" }` | | `boolean` | 布尔值 | `{ active: true }` | | `json` / `jsonb` | JSON 对象 | `{ data: { key: "value" } }` | | `integer[]` | 整数数组 | `{ ids: [1, 2, 3] }` | | `text[]` | 文本数组 | `{ names: ["a", "b"] }` | --- # JS SDK(V2)/MySQL 数据库/查询数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/mysql/fetch ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); // 或指定实例和数据库 // const db = app.rdb({ // instance: "", // database: "" // }); ``` ## 基础查询 通过 `select()` 方法查询表数据,支持条件筛选、关联查询等功能。 ```js db.from(tableName).select(columns, options) ``` - **tableName**:表名称 - **columns**:要检索的列,用逗号分隔 - **options**:查询选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | -------------------------------------------------------------------- | | columns | string | 否 | 要检索的列,用逗号分隔。支持使用 `aliasName:foreignKey(columns)` 语法为外键关联字段设置别名
`columns` 取 `*` 为查询所有字段 | | options | object | 否 | 查询选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------- | ---- | ------------------------------------------------------ | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | | head | boolean | 否 | 设置为 `true` 时不返回数据,仅在需要计数时有用 | ### 代码示例 #### 查询所有数据 ```javascript // 查询 articles 表中的所有数据 const { data, error } = await db.from("articles").select(); console.log('查询结果:', data); ``` #### 查询指定列 ```javascript // 只查询文章的标题和创建时间 const { data, error } = await db.from("articles").select("id, title, created_at"); console.log('查询结果:', data); ``` #### 查询外键关联数据 ```javascript // 使用别名 'category' 代替默认的 'categories' 字段名 const { data, error } = await db.from("articles").select(` id, title, created_at, category:categories(name) `); console.log('查询结果:', data); ``` #### 返回结果 ```javascript { data: [ { id: 1, title: "文章标题", created_at: "2023-01-01T00:00:00Z", category: { name: "前端开发" } }, // ... 其他记录 ], error: null } ``` ## 关联表查询 通过关联查询可以同时获取多个表的数据,支持一对一、一对多等关联关系。 ### 基础关联查询 ```javascript // 查询文章数据,同时获取关联的分类信息 const { data, error } = await db.from("articles").select(` title, categories ( name ) `); console.log('查询结果:', data); ``` ### 多重关联查询 ```javascript // 查询文章,同时获取创建者和修改者的信息 const { data, error } = await db.from("articles").select(` title, created_by:users!articles_created_by_fkey(name), updated_by:users!articles_updated_by_fkey(name) `); console.log('查询结果:', data); ``` > 💡 注意:当同一个表通过不同的外键关联多次时,需要使用外键约束名来区分不同的关联关系。 ### 嵌套关联查询 ```javascript // 查询分类及其下的所有文章,以及文章的作者信息 const { data, error } = await db.from("categories").select(` name, articles ( title, users ( name ) ) `); console.log('查询结果:', data); ``` ## 高级查询 ### 条件过滤查询 ```javascript // 查询特定分类下的所有文章 const { data, error } = await db .from("articles") .select("title, categories(*)") .eq("categories.name", "技术文章"); console.log('查询结果:', data); ``` ### 计数查询 ```javascript // 获取每个分类及其包含的文章数量 const { data, error } = await db .from("categories") .select(`*, articles(count)`); console.log('查询结果:', data); ``` ### 仅获取总数 ```javascript // 只获取文章总数,不返回具体数据 const { count, error } = await db .from("articles") .select("*", { count: "exact", head: true }); console.log('总数:', count); ``` ### 内连接查询 ```javascript // 只获取有分类的文章,使用内连接确保分类存在 const { data, error } = await db .from("articles") .select("title, categories!inner(name)") .eq("categories.name", "教程") .limit(10); console.log('查询结果:', data); ``` > 💡 注意:`!inner` 表示内连接,只返回那些在关联表中存在匹配记录的数据。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 - [修饰符](./modifiers) - 了解查询修饰符的使用方法 --- # JS SDK(V2)/MySQL 数据库/新增数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/mysql/insert ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); // 或指定实例和数据库 // const db = app.rdb({ // instance: "", // database: "" // }); ``` ## 新增数据 通过 `insert()` 方法向表中插入数据,支持单行和批量插入。 ```js db.from(tableName).insert(values, options) ``` - **tableName**:表名称 - **values**:要插入的数据 - **options**:插入选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | --------------- | ---- | ------------------------------------------------------ | | values | object \| Array | 是 | 要插入的值。传递对象以插入单行,或传递数组以插入多行。 | | options | object | 否 | 插入选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------ | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 创建记录 ```javascript // 向 articles 表中插入一条记录 const { error } = await db .from("articles") .insert({ title: "新文章标题", content: "文章内容" }); console.log('插入结果:', error ? '失败' : '成功'); ``` #### 创建记录并返回数据 ```javascript // 插入记录并返回插入的数据 const { data, error } = await db .from("articles") .insert({ title: "新文章标题", content: "文章内容" }) .select(); console.log('插入结果:', data); ``` > 💡 注意:仅当表中只有一个主键,且该主键为自增类型时,`.select()` 方法才会返回插入的行。 #### 批量创建记录 ```javascript // 一次性插入多条记录 const { error } = await db.from("articles").insert([ { title: "第一篇文章", content: "第一篇文章内容" }, { title: "第二篇文章", content: "第二篇文章内容" }, ]); console.log('批量插入结果:', error ? '失败' : '成功'); ``` --- # JS SDK(V2)/MySQL 数据库/更新数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/mysql/update ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); // 或指定实例和数据库 // const db = app.rdb({ // instance: "", // database: "" // }); ``` ## 更新数据 通过 `update()` 方法更新表中的数据,需要结合过滤器使用以定位要更新的行。 ```js db.from(tableName).update(values, options).filter() ``` - **tableName**:表名称 - **values**:要更新的数据 - **options**:更新选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ---------- | | values | object | 是 | 要更新的值 | | options | object | 否 | 更新选项 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------------------ | | count | string | 否 | 计数算法,用于计算更新的行数:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 更新数据 ```javascript // 更新 articles 表中 id 为 1 的记录,将 title 字段改为"新标题" const { error } = await db .from("articles") .update({ title: "新标题" }) .eq("id", 1); console.log('更新结果:', error ? '失败' : '成功'); ``` #### 批量更新 ```javascript // 更新所有状态为 draft 的文章,将状态改为 published const { error } = await db .from("articles") .update({ status: "published" }) .eq("status", "draft"); console.log('批量更新结果:', error ? '失败' : '成功'); ``` ### 返回结果 ```javascript { data: null, error: null } ``` > 💡 注意:`update()` 方法必须与 [过滤器](./filters) 结合使用,以定位您希望更新的行。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 --- # JS SDK(V2)/MySQL 数据库/更新或创建数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/mysql/upsert ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); // 或指定实例和数据库 // const db = app.rdb({ // instance: "", // database: "" // }); ``` ## 更新或创建数据 通过 `upsert()` 方法执行更新或插入操作,如果记录不存在则插入,存在则更新。 ```js db.from(tableName).upsert(values, options) ``` - **tableName**:表名称 - **values**:要 upsert 的数据 - **options**:upsert 选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | -------------------- | ---- | ---------------------------------------------------------------- | | values | object \| Array | 是 | 要 upsert 的值。传递对象以 upsert 单行,或传递数组以 upsert 多行 | | options | object | 否 | upsert 选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ---------------- | ------- | ---- | ----------------------------------------------------------------------------------------------------------------------------- | | count | string | 否 | 用于计算 upserted 行数的计数算法:`"exact"` - 底层执行 `COUNT(*)` | | ignoreDuplicates | boolean | 否 | 如果为 true,则忽略重复行。如果为 false,则重复行与现有行合并 | | onConflict | string | 否 | 逗号分隔的唯一索引列,用于指定如何确定重复行。当所有指定的列都相等时,两行被视为重复。在 MySQL 中,这通常对应于唯一索引或主键 | ### 代码示例 #### Upsert 数据 ```javascript // 如果 articles 表中存在 id 为 1 的记录则更新 title 为"MySQL 教程",不存在则插入新记录 const { data, error } = await db .from("articles") .upsert({ id: 1, title: "MySQL 教程" }); console.log('Upsert 结果:', data); ``` #### 批量 Upsert 数据 ```javascript // 批量 upsert 多条记录:id 为 1 的记录 title 设为"MySQL 教程",id 为 2 的记录 title 设为"Redis 指南" const { data, error } = await db.from("articles").upsert([ { id: 1, title: "MySQL 教程" }, { id: 2, title: "Redis 指南" }, ]); console.log('批量 Upsert 结果:', data); ``` #### 指定冲突列 ```javascript // 根据 title 字段判断冲突,如果存在 title 为"唯一标题"的记录则更新,否则插入 id 为 42 的新记录 const { data, error } = await db .from("articles") .upsert( { id: 42, title: "唯一标题", content: "文章内容" }, { onConflict: "title" } ); console.log('指定冲突列 Upsert 结果:', data); ``` ### 返回结果 ```javascript { data: [ { id: 1, title: "MySQL 教程", content: "文章内容" } ], error: null } ``` > 💡 注意:使用 `upsert()` 时必须包含主键列,这样才能正确判断是插入新行还是更新现有行。在 MySQL 中,upsert 通常通过 `ON DUPLICATE KEY UPDATE` 语法实现,当插入的数据与现有主键或唯一索引冲突时,会执行更新操作。`onConflict` 参数用于指定冲突判断的列,对应 MySQL 中的唯一索引列。 --- # JS SDK(V2)/MySQL 数据库/删除数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/mysql/delete ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); // 或指定实例和数据库 // const db = app.rdb({ // instance: "", // database: "" // }); ``` ## 删除数据 通过 `delete()` 方法删除表中的数据,需要结合过滤器使用以定位要删除的行。 ```js db.from(tableName).delete(options).filter() ``` - **tableName**:表名称 - **options**:删除选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ------------ | | options | object | 否 | 删除选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------ | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 删除单个记录 ```javascript // 删除 articles 表中 id 为 1 的记录 const { error } = await db.from("articles").delete().eq("id", 1); console.log('删除结果:', error ? '失败' : '成功'); ``` #### 批量删除记录 ```javascript // 批量删除 articles 表中 id 为 1、2、3 的多条记录 const { error } = await db.from("articles").delete().in("id", [1, 2, 3]); console.log('批量删除结果:', error ? '失败' : '成功'); ``` #### 条件删除 ```javascript // 删除所有状态为 draft 的文章 const { error } = await db.from("articles").delete().eq("status", "draft"); console.log('条件删除结果:', error ? '失败' : '成功'); ``` ### 返回结果 ```javascript { data: null, error: null } ``` > ⚠️ 注意:`delete()` 方法必须与 [过滤器](./filters) 结合使用,以定位您希望删除的行。确保您提供的条件准确表示您打算删除的所有记录,以避免意外删除数据。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 --- # JS SDK(V2)/MySQL 数据库/过滤器 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/mysql/filters 过滤器允许只返回符合特定条件的行。 过滤器可以用于 `select()`、`update()`、`upsert()` 和 `delete()` 查询。 ## eq 仅匹配列值等于指定值的行。 要检查列值是否为 `NULL`,应该使用 `.is()` 而不是 `eq`。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .eq("title", "腾讯云开发"); ``` ## neq 仅匹配列值不等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 不等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .neq("title", "腾讯云开发"); ``` ## gt 仅匹配列值大于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 大于 2 的记录 const { data, error } = await db.from("articles").select().gt("id", 2); ``` ## gte 仅匹配列值大于或等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 大于或等于 2 的记录 const { data, error } = await db.from("articles").select().gte("id", 2); ``` ## lt 仅匹配列值小于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 小于 2 的记录 const { data, error } = await db.from("articles").select().lt("id", 2); ``` ## lte 仅匹配列值小于或等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 小于或等于 2 的记录 const { data, error } = await db.from("articles").select().lte("id", 2); ``` ## like 仅匹配列值符合特定模式的行(是否区分大小写受校验规则约束)。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------- | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | pattern | string | 必需 | 要匹配的模式 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 包含 "cloudbase" 的记录 const { data, error } = await db .from("articles") .select() .like("title", "%cloudbase%"); ``` ## is 仅匹配列值等于指定值的行。 对于非布尔列,主要用于检查列值是否为 `NULL`; 对于布尔列,也可以设置为 `true` 或 `false`,行为与 `.eq()` 相同。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | Object | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 为 null 的记录 const { data, error } = await db.from("articles").select().is("title", null); ``` ## in 仅匹配列值包含在指定数组中的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ---------------- | | column | string | 必需 | 要过滤的列 | | values | Array | 必需 | 用于过滤的值数组 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 在指定数组 ["腾讯云开发", "云开发"] 中的记录 const { data, error } = await db .from("articles") .select() .in("title", ["腾讯云开发", "云开发"]); ``` ## match 仅匹配查询键中每个列都等于其关联值的行,相当于多个 `.eq()` 的简写形式。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ----- | ------------------------------------- | ---- | -------------------------------------- | | query | `Record` | 必需 | 过滤对象,列名作为键映射到它们的过滤值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 等于 2 且 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .match({ id: 2, title: "腾讯云开发" }); ``` ## not 仅匹配不满足过滤条件的行。 与大多数过滤器不同,操作符和值按原样使用,需要遵循 MySQL 语法,还需要确保它们已正确转义。 `not()` 期望使用原始的 MySQL 语法作为过滤器值。 ```javascript .not('id', 'in', '(5,6,7)') // 对 `in` 过滤器使用 `()` .not('name', 'like', '%test%') // 使用 `not like` 进行模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | -------- | ------ | ---- | ----------------------------------- | | column | string | 必需 | 要过滤的列 | | operator | string | 必需 | 要取反的过滤操作符,遵循 MySQL 语法 | | value | any | 必需 | 过滤值,遵循 MySQL 语法 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 不为 null 的记录 const { data, error } = await db .from("articles") .select() .not("title", "is", null); ``` ## or 仅匹配满足至少一个过滤条件的行。 与大多数过滤器不同,过滤器按原样使用,需要遵循 MySQL 语法,还需要确保它们已正确转义。 目前无法跨多个表进行 `.or()` 过滤。 `or()` 期望使用原始的 MySQL 语法作为过滤器名称和值。 ```javascript .or('id.in.(5,6,7), name.like.%test%') // 对 `in` 过滤器使用 `()`,对模糊匹配使用 `like` 和 `%` .or('id.in.(5,6,7), name.not.like.%test%') // 使用 `not.like` 进行反向模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | ----------------------- | ------ | ---- | ------------------------------- | | filters | string | 必需 | 要使用的过滤器,遵循 MySQL 语法 | | options | object | 必需 | 命名参数 | | options.referencedTable | string | 可选 | 设置为过滤引用表而不是父表 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 等于 2 或者 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .or(`id.eq.2,title.eq.腾讯云开发`); ``` ## filter 仅匹配满足过滤条件的行,这是一个逃生舱口,应该尽可能使用特定的过滤器方法。 与大多数过滤器不同,操作符和值按原样使用,需要遵循 MySQL 语法,还需要确保它们已正确转义。 `filter()` 期望使用原始的 MySQL 语法作为过滤器值。 ```javascript .filter('id', 'in', '(5,6,7)') // 对 `in` 过滤器使用 `()` .filter('name', 'like', '%test%') // 使用 `like` 进行模糊匹配 .filter('name', 'not.like', '%test%') // 使用 `not.like` 进行反向模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | -------- | ------ | ---- | --------------------------- | | column | string | 必需 | 要过滤的列 | | operator | string | 必需 | 过滤操作符,遵循 MySQL 语法 | | value | any | 必需 | 过滤值,遵循 MySQL 语法 | ### 代码示例 ```javascript // 查询 title 在指定值列表中的记录 // 从 articles 表中查询所有 title 在指定值列表 ["腾讯云开发", "云开发"] 中的记录 const { data, error } = await db .from("articles") .select() .filter("title", "in", "(腾讯云开发,云开发)"); // 在引用表上过滤 // 从 articles 表中查询所有关联的 categories.name 等于 "技术" 的记录 const { data, error } = await db .from("articles") .select() .filter("categories.name", "eq", "技术"); ``` --- # JS SDK(V2)/MySQL 数据库/修饰符 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/mysql/modifiers 修饰符用于改变响应的格式,与过滤器不同,它们作用于行级别以上的操作。 过滤器仅返回匹配特定条件的行而不改变行的形状,而修饰符允许改变响应的格式。 ## select 默认情况下,`.insert()` 不会返回插入的行。通过调用此方法,插入的行将在数据中返回。 > ⚠️ 注意:仅当表中只有一个主键,且该主键为自增类型时,`.select()` 方法才会返回插入的行。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------------------- | | columns | string | 否 | 要检索的列,用逗号分隔 | ### 代码示例 ```javascript // 在 articles 表中执行 upsert 操作,并返回修改后的完整记录 const { data, error } = await db .from("articles") .insert({ id: 1, title: "腾讯云开发新功能" }) .select(); ``` ## order 对查询结果进行排序。 可以多次调用此方法来按多个列排序。 可以对引用的表进行排序,但仅当在查询中使用 `!inner` 时,它才会影响父表的排序。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------- | | column | string | 是 | 要排序的列 | | options | object | 否 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------- | ---- | --------------------------------------------------------------- | | ascending | boolean | 否 | 如果为 `true`,结果将按升序排列 | | nullsFirst | boolean | 否 | 如果为 `true`,`null` 值将首先出现。如果为 `false`,`null` 值将最后出现 | | referencedTable | string | 否 | 设置为按引用表的列排序 | ### 代码示例 ```javascript // 按发布时间降序排列文章 const { data, error } = await db .from("articles") .select("id, title, published_at") .order("published_at", { ascending: false }); ``` #### 对引用表排序 ```javascript // 对引用表 categories 按 name 降序排列 const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .order("name", { referencedTable: "categories", ascending: false }); ``` ```javascript // 按引用表 category 的 name 列升序排列 const { data, error } = await db .from("articles") .select(` title, category:categories ( name ) `) .order("category(name)", { ascending: true }); ``` ## limit 限制返回的行数。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------------- | | count | number | 是 | 要返回的最大行数 | | options | object | 否 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------ | ---- | ---------------------------------------- | | referencedTable | string | 否 | 设置为限制引用表的行数,而不是父表的行数 | ### 代码示例 ```javascript // 限制返回 5 篇文章 const { data, error } = await db.from("articles").select("title").limit(5); ``` #### 限制引用表的行数 ```javascript // 每篇文章只返回 3 个分类 const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .limit(3, { referencedTable: "categories" }); ``` ## range 限制查询结果的范围。 通过从偏移量 `from` 开始到 `to` 结束来限制查询结果,只有在此范围内的记录会被返回。 这遵循查询顺序,如果没有排序子句,范围行为可能会不可预期。 `from` 和 `to` 值是基于 0 的且包含边界:`range(1, 3)` 将包括查询的第二、第三和第四行。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ------------------ | | from | number | 是 | 限制结果的起始索引 | | to | number | 是 | 限制结果的结束索引 | | options | object | 是 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------ | ---- | ---------------------------------------- | | referencedTable | string | 否 | 设置为限制引用表的行数,而不是父表的行数 | ### 代码示例 ```javascript // 获取文章列表的第 1-2 条记录(包含边界) const { data, error } = await db.from("articles").select("title").range(0, 1); ``` #### 对引用表使用范围限制 ```javascript // 每篇文章只返回前 2 个分类(索引 0-1) const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .range(0, 1, { referencedTable: "categories" }); ``` ## abortSignal > 💡 注意:此方法仅在 Web 环境下可用。 为 `fetch` 请求设置 `AbortSignal`。 可以使用此功能为请求设置超时。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------ | ----------- | ---- | ----------------------------- | | signal | AbortSignal | 是 | 用于 `fetch` 请求的 `AbortSignal` | ### 代码示例 ```javascript // 使用 AbortController 手动中止请求 const ac = new AbortController(); ac.abort(); const { data, error } = await db .from("articles") .select() .abortSignal(ac.signal); ``` ```javascript // 使用 AbortSignal.timeout 设置 1 秒超时 const { data, error } = await db .from("articles") .select() .abortSignal(AbortSignal.timeout(1000)); ``` ## single 检索单行数据。 将数据作为单个对象返回,而不是对象数组。 查询结果必须只有一行(例如使用 `.limit(1)` ),否则此方法会返回错误。 ### 代码示例 ```javascript // 获取第一篇文章的标题 const { data, error } = await db .from("articles") .select("title") .limit(1) .single(); ``` ## maybeSingle 检索零行或一行数据。 将数据作为单个对象返回,而不是对象数组。 查询结果必须为零行或一行(例如使用 `.limit(1)` ),否则此方法会返回错误。 ### 代码示例 ```javascript // 根据标题查找文章,可能不存在 const { data, error } = await db .from("articles") .select() .eq("title", "腾讯云开发新功能") .maybeSingle(); ``` ## overrideTypes 部分覆盖或替换成功响应的类型。 覆盖响应中 `data` 字段的返回类型,对于类型安全的查询结果转换非常有用。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ------------ | | type | T | 是 | 要覆盖的类型 | | options | object | 否 | 选项对象 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | ------ | ------- | ---- | -------------------------------------- | | merge | boolean | 否 | 如果为 `false`,则完全替换类型而不是合并 | ### 代码示例 #### 完全覆盖数组类型 ```javascript // 将响应数据完全覆盖为自定义数组类型,merge: false 表示完全替换而非合并 const { data } = await db .from("articles") .select() .overrideTypes, { merge: false }>(); ``` #### 完全覆盖对象类型 ```javascript // 与 maybeSingle 一起使用,将单个对象响应完全覆盖为自定义类型 const { data } = await db .from("articles") .select() .maybeSingle() .overrideTypes(); ``` #### 部分覆盖数组类型 ```javascript // 部分覆盖数组元素类型,只指定需要改变的字段类型(如 status 字段) const { data } = await db .from("articles") .select() .overrideTypes>(); ``` #### 部分覆盖对象类型 ```javascript // 部分覆盖单个对象类型,只指定需要改变的字段类型(如 status 字段) const { data } = await db .from("articles") .select() .maybeSingle() .overrideTypes<{ status: "A" | "B" }>(); ``` --- # JS SDK(V2)/数据模型/查询数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/model/fetch ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models; ``` ## 单条查询 通过指定条件查询单条记录。 ```js models.modelName.get(options); ``` - **modelName**: 数据模型名称 - **options**: 查询参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/webv2/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | ------------ | | **filter.where** | `object` | 否 | 查询条件 | | **select** | `object` | 是 | 指定返回字段 | ### 代码示例 ```javascript // 查询 todo数据模型 _id 为 todo-id-123 的记录 const todo = await models.todo.get({ filter: { where: { _id: { $eq: "todo-id-123", }, }, }, select: { $master: true, // 返回所有字段 }, }); console.log("查询结果:", todo.data); ``` ### 返回结果 ```javascript { data: { records: [{ _id: "todo-id-123", title: "服务端任务", completed: false, // ... 其他字段 }], total: 1 } } ``` ## 多条查询 查询多条记录,支持条件筛选、排序、分页等 ```js models.modelName.list(options); ``` - **modelName**: 数据模型名称 - **options**: 查询参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/webv2/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | ------------ | | **filter.where** | `object` | 否 | 查询条件 | | **select** | `object` | 是 | 指定返回字段 | ### 代码示例 ```javascript // 查询 todo数据模型 completed 为 false 的记录 const todos = await models.todo.list({ filter: { where: { completed: { $eq: false }, }, }, }); console.log("查询结果:", todos.data); ``` ### 返回结果 ```javascript { data: { records: [{ _id: "todo-id-1", title: "任务1", completed: false }, { _id: "todo-id-2", title: "任务2", completed: false } ], total: 2 } } ``` --- # JS SDK(V2)/数据模型/新增数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/model/add ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models ``` ## 单条新增 向数据模型中添加一条新记录。 ```js models.modelName.create(options) ``` - **modelName**: 数据模型名称 - **options**: 新增参数 ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | -------- | -------- | ---- | ---------------- | | **data** | `object` | 是 | 要新增的数据对象 | ### 代码示例 ```javascript // 创建单条记录 const todo = await models.todo.create({ data: { title: "客户端任务", description: "使用 js-sdk 创建的任务", priority: "high", completed: false, createdAt: new Date(), createdBy: "system", metadata: { source: "api", version: "1.0" } } }) console.log('创建成功:', todo) ``` ## 批量新增 一次性向数据模型中添加多条记录。 > ⚠️ 注意:暂不支持批量新增关联关系字段 ```js models.modelName.createMany(options) ``` - **modelName**: 数据模型名称 - **options**: 新增参数 ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | -------- | ------- | ---- | ---------------------- | | **data** | `array` | 是 | 包含多个数据对象的数组 | ### 代码示例 ```javascript // 批量创建多条记录 const todos = await models.todo.createMany({ data: [{ title: "批量任务1", priority: "high" }, { title: "批量任务2", priority: "medium" }, { title: "批量任务3", priority: "low" } ] }) console.log('批量创建成功:', todos) --- # JS SDK(V2)/数据模型/更新数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/model/update ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models; ``` ## 单条更新 根据条件更新单条记录。 ```js models.modelName.update(options); ``` - **modelName**: 数据模型名称 - **options**: 更新参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/webv2/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **data** | `object` | 是 | 要更新的数据对象 | | **filter.where** | `object` | 是 | 查询条件,确定要更新的记录 | ### 代码示例 ```javascript // 更新单条记录 const result = await models.todo.update({ data: { title: "更新后的任务", completed: true, updatedAt: new Date(), updatedBy: "admin", }, filter: { where: { _id: { $eq: "todo-id", // 推荐使用 _id 进行精确操作 }, }, }, }); console.log("更新结果:", result); ``` ## 批量更新 根据查询条件批量更新多条记录。 > ⚠️ 注意:暂不支持批量更新关联关系字段 ```js models.modelName.updateMany(options); ``` - **modelName**: 数据模型名称 - **options**: 更新参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/webv2/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **data** | `object` | 是 | 要更新的数据对象 | | **filter.where** | `object` | 是 | 查询条件,确定要更新的记录 | ### 代码示例 ```javascript // 批量更新多条记录 const result = await models.todo.updateMany({ data: { status: "archived", archivedAt: new Date(), }, filter: { where: { completed: { $eq: true, }, createdAt: { $lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // 30天前 }, }, }, }); console.log("批量更新结果:", result); ``` ## 创建或更新数据 根据查询条件创建或更新记录。 > ⚠️ 注意:暂不支持批量新增更新关联关系字段 ```js models.modelName.upsert(options); ``` - **modelName**: 数据模型名称 - **options**: 更新参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/webv2/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **create** | `object` | 是 | 要新增的数据对象 | | **update** | `object` | 是 | 要更新的数据对象 | | **filter.where** | `object` | 是 | 查询条件,确定要更新的记录 | ### 代码示例 ```javascript const post = { title: "Hello World", body: "Hello World", _id: "foo", }; const { data } = await models.post.upsert({ create: post, update: post, filter: { where: { _id: { $eq: post._id, }, }, }, }); console.log(data); ``` --- # JS SDK(V2)/数据模型/删除数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/model/delete ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models; ``` ## 单条删除 根据条件删除单条记录。 ```js models.modelName.delete(options); ``` - **modelName**: 数据模型名称 - **options**: 删除参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/webv2/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **filter.where** | `object` | 是 | 查询条件,确定要删除的记录 | ### 代码示例 ```javascript // 删除单条记录 const result = await models.todo.delete({ filter: { where: { _id: { $eq: "todo-id", }, }, }, }); console.log("删除结果:", result); ``` ## 批量删除 根据查询条件批量删除多条记录。 ```js models.modelName.deleteMany(options); ``` - **modelName**: 数据模型名称 - **options**: 删除参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/webv2/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **filter.where** | `object` | 是 | 查询条件,确定要删除的记录 | ### 代码示例 ```javascript // 批量删除已完成的任务 const result = await models.todo.deleteMany({ filter: { where: { completed: { $eq: true, }, createdAt: { $lt: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000), // 90天前 }, }, }, }); console.log("批量删除结果:", result); ``` --- # JS SDK(V2)/数据模型/查询参数详解 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/model/select 云开发数据模型提供了强大的查询功能,支持复杂的查询条件、字段选择、排序、分页和关联查询。本文将详细介绍数据模型查询操作的各种参数和使用方法。 ## 查询参数概览 数据模型的查询操作主要包含以下参数: | 参数 | 类型 | 说明 | 必填 | | ------------ | ----------------- | ----------------------------------------------------------------------------- | ---- | | `envType` | `pre` \| `prod` | 指定查询的数据环境,不传为正式数据
`pre` :体验数据, `prod` :正式数据 | 否 | | `select` | `SelectParams` | 指定返回的字段 | 否 | | `filter` | `FilterParams` | 查询过滤条件 | 否 | | `orderBy` | `OrderByParams[]` | 排序条件 | 否 | | `pageSize` | `number` | 分页大小, 最大200 | 否 | | `pageNumber` | `number` | 页码 | 否 | | `getCount` | `boolean` | 是否返回总数 | 否 | ## 指定数据环境 `envType` `envType` 参数用于指定查询的数据环境,默认为正式数据, `pre` :体验数据, `prod` :正式数据。 ```javascript const { data } = await models.post.list({ envType: 'pre', filter: { where: {}, }, select: { $master: true, }, }); ``` ## 字段选择 `select` `select` 参数用于指定查询结果中应包含的字段,支持主模型字段和关联模型字段的精确控制。 > ✅ 建议仅选择需要的字段和关系来减少响应数据的大小并提高查询速度。 ### 返回主模型所有字段 使用 `$master: true` 可以返回主模型的所有字段: ```javascript const { data } = await models.post.get({ select: { $master: true, // 查询主模型所有的字段 }, filter: { where: { _id: { $eq: _id, // 推荐传入_id数据标识进行操作 }, }, }, }); console.log(data); // { // "owner": "Anonymous(95fblM7nvPi01yQmYxBvBg)", // "createdAt": 1717488585078, // "createBy": "Anonymous(95fblM7nvPi01yQmYxBvBg)", // "updateBy": "Anonymous(95fblM7nvPi01yQmYxBvBg)", // "_openid": "95fblM7nvPi01yQmYxBvBg", // "_id": "e2764d2d665ecbc9024b058f1d6b33a4", // "title": "你好,世界👋", // "body": "文章内容...", // "slug": "hello-world", // "updatedAt": 1717490751944 // } ``` ### 返回指定字段 通过指定字段名为 `true` 来选择特定字段: ```javascript const { data } = await models.post.list({ select: { _id: true, title: true, updatedAt: true, }, filter: { where: {}, }, getCount: true, }); console.log(data); // { // "records": [ // { // "_id": "e2764d2d665ecbc9024b058f1d6b33a4", // "title": "你好,世界👋", // "updatedAt": 1717492882289 // } // // ... 更多记录 // ], // "total": 51 // } ``` ### 返回关联模型字段 可以同时查询关联模型的字段: ```javascript const { data } = await models.post.list({ select: { _id: true, title: true, updatedAt: true, // 关联查询评论数据 comments: { _id: true, createdAt: true, comment: true, }, // 也可以直接传 true 返回评论中所有的字段 }, filter: { where: {}, }, getCount: true, }); console.log(data); // { // "records": [ // { // "_id": "9FSAHWM9VV", // "title": "Bonjour le Monde👋", // "updatedAt": 1718096503886, // "comments": [ // { // "_id": "9FSAJF3GLG", // "createdAt": 1718096509916, // "comment": "这是一条评论" // } // ] // } // // ... 更多记录 // ], // "total": 2 // } ``` ## 查询过滤 `filter` `filter` 参数用于指定查询条件,支持基础查询和关联查询两种方式。 ### 基础查询 `where` 基础查询用于对主模型字段进行过滤: ```javascript const { data } = await models.post.list({ filter: { where: { // 标题包含"世界" title: { $search: "世界", }, // 正文不为空 body: { $nempty: true, }, }, }, select: { $master: true, }, }); ``` ### 关联查询 `relateWhere` 关联查询用于根据关联模型的条件进行过滤: ```javascript const { data } = await models.post.list({ filter: { // 查询有评论的文章 relateWhere: { comments: { where: { comment: { $nempty: true, // 评论内容不为空 }, }, }, }, where: {}, // 主模型查询条件 }, select: { $master: true, comments: { comment: true, }, }, }); ``` ### 组合查询示例 需要用到与或关系时,可以使用 `where` 参数的 `$and` 和 `$or` 操作符 如果用到了 `$and` 和 `$or` 操作符,需要将它们放在一个数组中: ```javascript const { data } = await models.post.list({ filter: { where: { $and: [{ // 标题包含"技术"或"教程" $or: [{ title: { $search: "技术" } }, { title: { $search: "教程" } } ] }, { // 创建时间在最近30天内 createdAt: { $gte: Date.now() - 30 * 24 * 60 * 60 * 1000 } }, { // 状态为已发布 status: { $eq: "published" } } ] }, }, select: { _id: true, title: true, createdAt: true, status: true, }, }); ``` ### 查询运算符 | 操作符 | 说明 | 示例 | | --------------- | ------------------------------------------ | ----------------------------------------------------------- | | **$eq** | 等于 | `{status: {$eq: "active"}}` | | **$ne** | 不等于 | `{status: {$ne: "deleted"}}` | | **$gt** | 大于 | `{score: {$gt: 80}}` | | **$gte** | 大于等于 | `{age: {$gte: 18}}` | | **$lt** | 小于 | `{price: {$lt: 100}}` | | **$lte** | 小于等于 | `{discount: {$lte: 0.5}}` | | **$in** | 在数组中 | `{category: {$in: ["tech", "news"]}}` | | **$nin** | 不在数组中 | `{status: {$nin: ["deleted", "banned"]}}` | | **$and** | 逻辑与 | `{$and: [{age: {$gte: 18}}, {status: {$eq: "active"}}]}` | | **$or** | 逻辑或 | `{$or: [{priority: {$eq: "high"}}, {urgent: {$eq: true}}]}` | | **$search** | 模糊查询, 大小写敏感 | `{status: {$search: "active"}}` | | **$search_ci** | 模糊查询, 大小写不敏感 | `{status: {$search_ci: "active"}}` | | **$nsearch** | 查找不包含指定字符串值的记录, 大小写敏感 | `{status: {$nsearch: "active"}}` | | **$nsearch_ci** | 查找不包含指定字符串值的记录, 大小写不敏感 | `{status: {$nsearch_ci: "active"}}` | | **$empty** | 数据为 null | `{status: {$empty: true}}` | | **$nempty** | 数据不为 null | `{status: {$nempty: true}}` | ## 排序 `orderBy` `orderBy` 参数用于指定查询结果的排序方式,支持多字段排序(最多3个字段)。 ### 单字段排序 ```javascript const { data } = await models.post.list({ filter: { where: {}, }, orderBy: [{ createdAt: "desc", // 按创建时间倒序 }, ], select: { $master: true, }, }); ``` ### 多字段排序 ```javascript const { data } = await models.post.list({ filter: { where: {}, }, orderBy: [{ featured: "desc", // 首先按推荐状态倒序 }, { createdAt: "desc", // 然后按创建时间倒序 }, { title: "asc", // 最后按标题升序 }, ], select: { $master: true, }, }); ``` 排序方向: * `"asc"`: 升序排列 * `"desc"`: 降序排列 ## 分页参数 ### `pageSize` 和 `pageNumber` 用于实现分页查询: ```javascript const { data } = await models.post.list({ filter: { where: {}, }, pageSize: 10, // 每页10条记录 pageNumber: 2, // 第2页(从1开始) getCount: true, // 获取总数用于计算总页数 select: { $master: true, }, }); console.log(data); // { // "records": [...], // 第2页的10条记录 // "total": 156 // 总记录数 // } // 计算总页数 const totalPages = Math.ceil(data.total / 10); ``` ### `getCount` 控制是否返回满足条件的记录总数: ```javascript const { data } = await models.post.list({ filter: { where: { status: { $eq: "published" } }, }, getCount: true, // 设置为 true 时返回 total 字段 select: { _id: true, title: true, }, }); console.log(data.total); // 满足条件的总记录数 ``` ## 完整查询示例 以下是一个包含所有参数的完整查询示例: ```javascript const { data } = await models.post.list({ // 字段选择 select: { _id: true, title: true, excerpt: true, createdAt: true, updatedAt: true, author: { _id: true, name: true, avatar: true, }, comments: { _id: true, content: true, createdAt: true, }, }, // 查询条件 filter: { where: { $and: [{ status: { $eq: "published" } }, { $or: [{ title: { $search: "技术" } }, { tags: { $in: ["前端", "后端"] } } ] }, { createdAt: { $gte: Date.now() - 7 * 24 * 60 * 60 * 1000 // 最近7天 } } ] }, relateWhere: { comments: { where: { status: { $eq: "approved" } // 只查询已审核的评论 } } } }, // 排序 orderBy: [{ featured: "desc" }, { createdAt: "desc" } ], // 分页 pageSize: 20, pageNumber: 1, getCount: true, }); console.log("查询结果:", data.records); console.log("总记录数:", data.total); console.log("总页数:", Math.ceil(data.total / 20)); ``` ## 关联查询详解 ### 按关联关系过滤 使用 `relateWhere` 可以根据关联模型的条件来过滤主模型: ```javascript // 查询有特定评论的文章 const { data } = await models.post.list({ filter: { relateWhere: { // 文章必须有评论 comments: { where: { $and: [{ content: { $nempty: true } }, // 评论内容不为空 { status: { $eq: "approved" } }, // 评论已审核 { rating: { $gte: 4 } } // 评论评分>=4 ] } }, // 文章必须有标签 tags: { where: { name: { $in: ["技术", "教程", "分享"] } } } }, where: { status: { $eq: "published" } } }, select: { $master: true, comments: { content: true, rating: true, status: true, }, tags: { name: true, } } }); ``` ### 关联查询性能优化 1. **精确选择字段**:只选择需要的关联字段 2. **合理使用关联过滤**:避免过于复杂的关联条件 3. **分页处理**:对于大量关联数据使用分页 ```javascript // 优化示例:只获取必要的关联数据 const { data } = await models.post.list({ filter: { where: { status: { $eq: "published" } } }, select: { _id: true, title: true, excerpt: true, // 只获取作者的基本信息 author: { _id: true, name: true, }, // 只获取最新的3条评论 comments: { _id: true, content: true, createdAt: true, } }, orderBy: [{ createdAt: "desc" }], pageSize: 10, pageNumber: 1, }); ``` ## 最佳实践 ### 1. 字段选择优化 ```javascript // ❌ 不推荐:查询所有字段 const { data } = await models.post.list({ select: { $master: true }, filter: { where: {} } }); // ✅ 推荐:只选择需要的字段 const { data } = await models.post.list({ select: { _id: true, title: true, excerpt: true, createdAt: true, }, filter: { where: {} } }); ``` ### 2. 查询条件优化 ```javascript // ✅ 推荐:使用索引字段进行查询 const { data } = await models.post.list({ filter: { where: { _id: { $eq: "specific-id" }, // 主键查询,性能最佳 status: { $eq: "published" }, // 索引字段 createdAt: { $gte: timestamp } // 时间范围查询 } } }); // ⚠️ 注意:模糊查询性能较差,谨慎使用 const { data } = await models.post.list({ filter: { where: { title: { $search: "关键词" } // 模糊查询,建议配合其他条件 } } }); ``` ### 3. 分页处理 ```javascript // ✅ 推荐:合理的分页大小 const pageSize = 20; // 建议10-50之间 const pageNumber = 1; const { data } = await models.post.list({ filter: { where: {} }, pageSize, pageNumber, getCount: true, // 获取总数用于分页计算 select: { _id: true, title: true, createdAt: true, } }); // 计算分页信息 const totalPages = Math.ceil(data.total / pageSize); const hasNextPage = pageNumber < totalPages; const hasPrevPage = pageNumber > 1; ``` ### 4. 错误处理 ```javascript try { const { data } = await models.post.list({ filter: { where: { status: { $eq: "published" } } }, select: { _id: true, title: true, }, pageSize: 10, pageNumber: 1, getCount: true, }); console.log("查询成功:", data); } catch (error) { console.error("查询失败:", error); // 处理错误情况 } ``` 通过合理使用这些查询参数,可以实现高效、灵活的数据查询操作,满足各种复杂的业务需求。 --- # JS SDK(V2)/数据模型/执行 SQL 模板 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/model/sql-template ## 初始化 SDK ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models ``` ## 执行 SQL 模板 通过 SQL 模板在 MySQL 数据模型上执行参数化 SQL 查询。 ```js models.modelName.runSQLTemplate(options) ``` - **modelName**: 数据模型名称 - **options**: 执行参数 ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | **templateName** | `string` | 是 | SQL 模板名称 | | **params** | `object` | 否 | 模板参数对象 | ### 代码示例 #### 基础查询 ```javascript // 执行用户查询模板 const result = await models.user.runSQLTemplate({ templateName: "getUserByStatus", params: { status: "active" } }); console.log('查询结果:', result.data); ``` ## 注意事项 > 💡 注意: SQL 模板需要在 [云开发平台/MySQL数据库/数据模型](https://tcb.cloud.tencent.com/dev?#/db/mysql/model/?sourceType=internal_mysql) 中预先创建 > ⚠️ 注意: 参数值会自动进行 SQL 注入防护,请勿在模板中直接拼接用户输入 ## 相关文档 - [SQL 模板使用指南](/model/sql-template) --- # JS SDK(V2)/身份认证/未命名文档 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/authentication --- hide_table_of_contents: true --- import TabItem from '@theme/TabItem'; import ApiIntro from '../components/ApiIntro'; import ParameterTable from '../components/ApiContainer'; import Tabs from '@theme/Tabs'; # 概述 Auth Api 提供了一套完整的认证相关功能,支持多种登录方式、用户管理和会话管理。Auth Api 按照功能用途分为 7 个类别。每个类别包含相关的 API 方法,方便开发者根据具体需求快速找到合适的接口。 - [认证登录](/api-reference/webv2/authentication#认证登录):用户注册和登录相关的 API 方法,支持多种登录方式。 - [会话管理](/api-reference/webv2/authentication/#会话管理):管理用户会话状态和令牌的 API 方法。 - [用户管理](/api-reference/webv2/authentication/#用户管理):获取、更新和管理用户信息的 API 方法。 - [身份源管理](/api-reference/webv2/authentication/#身份源管理):管理第三方身份源绑定和解绑的 API 方法。 - [密码管理](/api-reference/webv2/authentication/#密码管理):密码重置和修改相关的 API 方法。 - [验证管理](/api-reference/webv2/authentication/#验证管理):验证码发送、验证和重发相关的 API 方法。 - [其他工具](/api-reference/webv2/authentication/#其他工具):其他辅助功能的 API 方法。 --- ## 基础使用示例 {#basic-usage-examples} `Publishable Key` 可前往 [云开发平台/API Key 配置](https://tcb.cloud.tencent.com/dev#/env/apikey) 中生成 `auth.detectSessionInUrl` 为初始化可选参数,设置后可以自动检测 URL 中的 OAuth 参数(code、state),适用于[signInWithOAuth](#signinwithoauth)、[linkIdentity](#linkidentity)等使用场景 ```typescript import cloudbase from "@cloudbase/js-sdk"; // 初始化 const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境ID region: "ap-shanghai", // 地域,默认为上海 accessKey: "", // 填入生成的 Publishable Key, auth: { detectSessionInUrl: true, // 可选:自动检测 URL 中的 OAuth 参数,适用于signInWithOAuth、linkIdentity }, }); const auth = app.auth; ``` ```typescript // 检查登录状态 async function checkAuthStatus() { const { data, error } = await auth.getSession(); if (error) { console.error("检查登录状态失败:", error.message); return false; } if (data.session) { console.log("用户已登录:", data.session.user); return true; } else { console.log("用户未登录"); return false; } } ``` ```typescript // 用户注册示例(四步验证流程) async function registerUser(email, password, verificationCode) { // 第一步:发送验证码 const { data, error } = await auth.signUp({ email: email, password: password, }); if (error) { console.error("发送验证码失败:", error.message); return false; } else { console.log("验证码已发送,等待验证..."); // 第二步:验证验证码并完成注册 const { data: loginData, error: loginError } = await data.verifyOtp({ token: verificationCode, }); if (loginError) { console.error("验证失败:", loginError.message); return false; } else { console.log("注册成功:", loginData.user?.email); return true; } } } ``` ```typescript // 用户登出示例 async function logoutUser() { const { data, error } = await auth.signOut(); if (error) { console.error("登出失败:", error.message); return false; } else { console.log("登出成功,会话已清除"); return true; } } ``` ```typescript // 密码登录示例 async function loginWithPassword(email, password) { const { data, error } = await auth.signInWithPassword({ email: email, password: password, }); if (error) { console.error("登录失败:", error.message); return false; } else { console.log("登录成功:", data.user?.email); return true; } } ``` ```typescript // 监听认证状态变化 auth.onAuthStateChange((event, session, info) => { console.log("认证状态变化:", event); switch (event) { case "INITIAL_SESSION": console.log("初始会话已建立"); if (session) { console.log("用户已登录:", session.user); } else { console.log("用户未登录"); } break; case "SIGNED_IN": console.log("用户登录成功:", session.user); break; case "SIGNED_OUT": console.log("用户已登出"); break; case "PASSWORD_RECOVERY": console.log("密码已重置"); break; case "TOKEN_REFRESHED": console.log("令牌已刷新"); break; case "USER_UPDATED": console.log("用户信息已更新"); break; case "BIND_IDENTITY": console.log("身份源绑定结果"); break; } }); ``` ```typescript // 完整的手机验证码登录页面实现 class PhoneLoginPage { constructor() { this.setupEventListeners(); } // 设置事件监听 setupEventListeners() { document.getElementById("sendCodeBtn").addEventListener("click", (e) => { e.preventDefault(); this.sendVerificationCode(); }); document.getElementById("verifyCodeBtn").addEventListener("click", (e) => { e.preventDefault(); this.verifyCodeAndLogin(); }); } // 发送验证码 async sendVerificationCode() { const phone = document.getElementById("phone").value; if (!phone) { alert("请输入手机号码"); return; } // 验证手机号格式 if (!this.validatePhone(phone)) { alert("请输入正确的手机号码格式"); return; } try { const { data, error } = await auth.signInWithOtp({ phone: phone, }); if (error) { this.handleSendCodeError(error); } else { this.handleSendCodeSuccess(data); } } catch (error) { this.handleNetworkError(error); } } // 验证验证码并登录 async verifyCodeAndLogin() { const code = document.getElementById("code").value; if (!code) { alert("请输入验证码"); return; } if (!this.verifyFunction) { alert("请先发送验证码"); return; } try { const { data, error } = await this.verifyFunction({ token: code }); if (error) { this.handleVerifyError(error); } else { this.handleLoginSuccess(data); } } catch (error) { this.handleNetworkError(error); } } // 手机号格式验证 validatePhone(phone) { const phoneRegex = /^1[3-9]\d{9}$/; return phoneRegex.test(phone); } // 处理发送验证码成功 handleSendCodeSuccess(data) { this.verifyFunction = data.verifyOtp; // 显示验证码输入区域 document.getElementById("verificationSection").style.display = "block"; document.getElementById("phoneSection").style.display = "none"; // 开始倒计时 this.startCountdown(60); document.getElementById("success").innerText = "验证码已发送到您的手机,请注意查收"; document.getElementById("success").style.display = "block"; } // 处理发送验证码错误 handleSendCodeError(error) { console.error("发送验证码失败:", error.code, error.category, error.message); switch (error.code) { case "invalid_argument": console.error(error.message || "手机号格式错误,请检查后重试"); break; case "not_found": console.error("该手机号未注册,请先注册或使用其他手机号"); break; case "resource_exhausted": console.error("发送频率过高,请稍后再试"); break; case "unreachable": console.error("网络连接失败,请检查网络设置"); break; default: console.error("发送验证码失败:", error.message); } } // 处理验证错误 handleVerifyError(error) { console.error("验证失败:", error.code, error.category, error.message); switch (error.code) { case "invalid_argument": // 验证码过期/不正确/不匹配 console.error(error.message || "验证码已过期或不正确,请重新获取"); break; case "unauthenticated": // Token 失效,需要重新登录 console.error("登录已过期,请重新登录"); window.location.href = "/login"; break; case "failed_precondition": console.error(error.message); break; case "not_found": console.error("用户不存在"); break; case "unavailable": console.error("服务暂不可用,请稍后再试"); break; case "unreachable": console.error("网络连接失败,请检查网络设置"); break; default: console.error("验证失败:", error.message); } } // 处理登录成功 handleLoginSuccess(data) { document.getElementById("success").innerText = "登录成功!欢迎回来"; document.getElementById("success").style.display = "block"; console.log("用户信息:", data.user); console.log("会话信息:", data.session); // 延迟跳转到首页 setTimeout(() => { window.location.href = "/dashboard"; }, 2000); } // 处理网络错误 handleNetworkError(error) { console.error("网络错误,请检查网络连接后重试:", error); } // 开始倒计时 startCountdown(seconds) { let countdown = seconds; const btn = document.getElementById("resendBtn"); const originalText = btn.innerText; btn.disabled = true; const timer = setInterval(() => { countdown--; btn.innerText = `${countdown}秒后可重发`; if (countdown <= 0) { clearInterval(timer); btn.disabled = false; btn.innerText = originalText; } }, 1000); } } // 页面加载完成后初始化 window.addEventListener("DOMContentLoaded", () => { new PhoneLoginPage(); }); ``` --- ## 认证登录 ## signUp ```typescript async signUp(params: SignUpReq): Promise ``` 注册新用户账户,采用智能注册并登录流程。 :::info 提示 `手机号验证码注册` 仅支持 `上海` 地域 ::: - 创建一个新的用户账户 - 采用智能注册并登录流程:发送验证码 → 等待用户输入 → 智能判断用户存在性 → 自动登录或注册并登录 - 如果用户已存在则直接登录,如果用户不存在则注册新用户并自动登录 Promise", description: "验证码回调函数,支持 messageId 参数,用于验证用户输入的验证码", children: [{ name: "params", type: "verifyParams", required: true, description: "状态变化回调函数入参", children: [ { name: "token", type: "string", required: true, description: "验证码", }, { name: "messageId", type: "string", description: "验证码 ID", } ] }, { name: "return", type: "Promise", required: true, description: "返回",children: [{ name: "data", type: "SignInResData", required: true, description: "", children: [ { name: "user", type: "User", required: true, description: "用户详细信息,包含身份信息和元数据", }, { name: "session", type: "Session", required: true, description: "会话信息,包含访问令牌和刷新令牌", }, ], },{ name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }] }, ] }, ], }, { name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }]}] }} > ```typescript // 第一步:发送邮箱验证码并存储 verificationInfo const { data, error } = await auth.signUp({ email: "newuser@example.com", password: "securePassword123", username: "newuser", }); if (error) { console.error("发送验证码失败:", error.message); } else { console.log("验证码已发送到邮箱,等待用户输入..."); // 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件) const verificationCode = "123456"; // 用户输入的验证码 // 第三步:智能验证流程(自动判断用户存在性) const { data: loginData, error: loginError } = await data.verifyOtp({ token: verificationCode, }); if (loginError) { console.error("验证失败:", loginError.message); } else { // 第四步:自动完成注册或登录 console.log("操作成功,用户信息:", loginData.user); console.log("会话信息:", loginData.session); console.log( "系统已自动判断:", loginData.user?.email ? "新用户注册并登录" : "现有用户直接登录" ); } } ``` ```typescript // 第一步:发送手机验证码 const { data, error } = await auth.signUp({ phone: "13800138000", password: "mypassword", }); if (error) { console.error("发送验证码失败:", error.message); } else { console.log("验证码已发送到手机,等待用户输入..."); // 第二步:等待用户输入验证码 const verificationCode = "123456"; // 用户输入的验证码 // 第三步:智能验证流程(自动判断用户存在性) const { data: loginData, error: loginError } = await data.verifyOtp({ token: verificationCode, }); if (loginError) { console.error("操作失败:", loginError.message); } else { // 系统自动判断:如果用户已存在则直接登录,如果不存在则注册新用户 if (loginData.user?.phone) { console.log("现有用户直接登录成功,用户信息:", loginData.user); } else { console.log("新用户注册并登录成功,用户ID:", loginData.user?.id); } console.log("会话状态:", loginData.session ? "已登录" : "未登录"); } } ``` ```typescript let signUpVerify = null; async function startRegistration(email, password) { const { data, error } = await auth.signUp({ email: email, password: password, }); if (error) { console.error("发送验证码失败:", error.code, error.category, error.message); return false; } else { console.log("验证码已发送,系统将自动判断您是注册新用户还是登录现有账户"); signUpVerify = data.verifyOtp; return true; } } async function completeRegistration(verificationCode) { if (!signUpVerify) { console.error("注册流程未开始"); return false; } const { data, error } = await signUpVerify({ token: verificationCode }); if (error) { console.error("验证失败:", error.code, error.category, error.message); if (error.code === "invalid_argument") { console.error(error.message || "验证码已过期或不正确,请重新获取"); } else if (error.code === "unauthenticated") { console.error("认证失效,请重新登录"); window.location.href = "/login"; } else { console.error("验证失败:", error.message); } return false; } else { // 智能判断结果反馈 if (data.user?.created_at) { console.log("新用户注册成功,欢迎加入!"); } else { console.log("登录成功,欢迎回来!"); } return true; } } // 注册表单提交 document .getElementById("registerForm") .addEventListener("submit", async (e) => { e.preventDefault(); const email = document.getElementById("email").value; const password = document.getElementById("password").value; const nickname = document.getElementById("nickname").value; await startRegistration(email, password, nickname); }); // 验证码表单提交 document .getElementById("verificationForm") .addEventListener("submit", async (e) => { e.preventDefault(); const code = document.getElementById("verificationCode").value; await completeRegistration(code); }); ``` ```typescript try { const { data, error } = await auth.signUp({ email: "existing@example.com", password: "password123", }); if (error) { switch (error.code) { case "already_exists": console.error("邮箱/手机号/用户名已被注册,请使用其他邮箱或直接登录"); break; case "password_too_weak": console.error("密码强度不足,请使用更复杂的密码"); break; case "invalid_argument": console.error("参数格式错误,请检查邮箱或手机号格式"); break; case "resource_exhausted": console.error("注册频率过高,请稍后重试"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; default: console.error("注册失败:", error.message); } } else { console.log("验证码发送成功"); } } catch (error) { console.error("网络错误:", error); } ``` ```typescript async function verifyRegistrationCode(code) { try { const { data, error } = await signUpCallback(code); if (error) { console.error("验证失败:", error.code, error.category, error.message); switch (error.code) { case "invalid_argument": // 验证码过期/不正确/参数缺失 console.error(error.message || "验证码已过期或不正确,请重新获取"); break; case "failed_precondition": // 账号已被绑定等业务校验 console.error(error.message); break; case "unauthenticated": // Token 失效,需要重新登录 console.error("认证失效,请重新登录"); window.location.href = "/login"; break; case "not_found": // 用户不存在 console.error("用户不存在"); break; case "unavailable": // 服务端异常 console.error("服务暂不可用,请稍后再试"); break; case "unreachable": console.error("网络连接失败"); break; default: console.error("验证失败:", error.message); } return false; } else { console.log("注册成功"); return true; } } catch (error) { console.error("网络错误:", error); return false; } } ``` --- ## signInAnonymously ```typescript async signInAnonymously(params?: SignInAnonymouslyReq): Promise ``` 匿名登录,创建一个临时匿名用户账户。 - 创建一个临时匿名用户账户 - 无需提供任何身份验证信息 - 适合需要临时访问权限的场景 - 使用前,请确认已在[云开发平台/身份认证/注册配置](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中开启允许匿名登录(默认开启) ```typescript // 创建匿名用户 const { data, error } = await auth.signInAnonymously(); if (error) { console.error("匿名登录失败:", error.message); console.error("错误代码:", error.code); } else { console.log("匿名登录成功"); console.log("匿名用户ID:", data.user?.id); console.log("会话信息:", data.session); console.log("是否为匿名用户:", data.user?.is_anonymous); } ``` ```typescript async function safeAnonymousLogin() { try { const { data, error } = await auth.signInAnonymously(); if (error) { switch (error.code) { case "resource_exhausted": console.error("请求频率过高,请稍后重试"); break; case "invalid_provider_token": console.error("第三方平台令牌无效,请检查令牌格式"); break; case "provider_not_supported": console.error("不支持的第三方平台,请检查平台标识"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; case "permission_denied": console.error("权限不足,请检查安全域名配置"); break; default: console.error("匿名登录失败:", error.message); } return null; } else { console.log("匿名登录成功"); return data; } } catch (error) { console.error("网络错误:", error); return null; } } // 使用安全登录函数 const result = await safeAnonymousLogin(); if (result) { console.log("登录成功,用户信息:", result.user); } ``` ```typescript // 第一步:匿名登录 const { data: anonymousData, error: anonymousError } = await auth.signInAnonymously(); if (anonymousError) { console.error("匿名登录失败:", anonymousError.message); } else { console.log("匿名登录成功,准备升级为正式用户"); // 第二步:绑定邮箱或手机号(示例:绑定邮箱) const { data: upgradeData, error: upgradeError } = await auth.signUp({ email: "user@example.com", password: "securePassword123", anonymous_token: anonymousData.session?.access_token, }); if (upgradeError) { console.error("升级失败:", upgradeError.message); } else { console.log("升级成功,请输入验证码完成身份验证"); // 第三步:验证验证码 const verificationCode = "123456"; const { data: finalData, error: finalError } = await upgradeData.verifyOtp({ token: verificationCode, }); if (finalError) { console.error("验证失败:", finalError.code, finalError.category, finalError.message); if (finalError.code === "invalid_argument") { console.error(finalError.message || "验证码已过期或不正确,请重新获取"); } else if (finalError.code === "unauthenticated") { console.error("认证失效,请重新登录"); } else { console.error("验证失败:", finalError.message); } } else { console.log("匿名用户成功升级为正式用户"); console.log("新用户信息:", finalData.user); console.log("是否为匿名用户:", finalData.user?.is_anonymous); } } } ``` --- ## signInWithPassword ```typescript async signInWithPassword(params: SignInWithPasswordReq): Promise ``` 使用用户名、邮箱或手机号和密码登录。 - 支持用户名、邮箱、手机号配合密码三种登录方式(三选一) - 使用前,请确认已在[云开发平台/身份认证/常规登录](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中开启用户名密码登录(默认开启) ```typescript const { data, error } = await auth.signInWithPassword({ username: "testuser", password: "password123", }); if (error) { console.error("登录失败:", error.message); } else { console.log("登录成功,用户信息:", data.user); console.log("会话信息:", data.session); } ``` ```typescript const { data, error } = await auth.signInWithPassword({ email: "user@example.com", password: "mypassword", }); if (error) { console.error("登录失败:", error.message); } else { console.log("登录成功,用户ID:", data.user?.id); } ``` ```typescript const { data, error } = await auth.signInWithPassword({ phone: "13800138000", password: "securepassword", }); if (error) { console.error("登录失败:", error.code, error.message); } else { console.log("登录成功,用户ID:", data.user?.id); } ``` ```typescript try { const { data, error } = await auth.signInWithPassword({ username: "wronguser", password: "wrongpassword", }); if (error) { // 根据常见错误代码进行完整错误处理 switch (error.code) { case "not_found": console.error("用户不存在,请检查用户名/邮箱/手机号是否正确"); break; case "password_not_set": console.error("当前用户未设置密码,请使用验证码登录或第三方登录方式"); break; case "invalid_password": console.error("密码不正确,请重新输入"); break; case "user_pending": console.error("该用户未激活,请联系管理员激活账户"); break; case "user_blocked": console.error("该用户被停用,请联系管理员"); break; case "invalid_status": console.error("您已经超过了密码最大重试次数,请稍后重试"); break; case "invalid_two_factor": console.error("二次验证码不匹配或已过时,请重新获取"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; default: console.error("登录失败:", error.message); } } else { console.log("登录成功"); } } catch (error) { console.error("网络错误:", error); } ``` --- ## signInWithOtp ```typescript async signInWithOtp(params: SignInWithOtpReq): Promise ``` 使用一次性密码(OTP)进行登录验证,支持邮箱和手机号验证。 :::info 提示 `短信验证码` 仅支持 `上海` 地域 ::: - 通过邮箱或手机号发送一次性验证码进行登录验证 - 支持完整的验证流程:发送验证码 → 等待用户输入 → 验证并登录 - 适用于无密码登录场景,提供更高的安全性 - 使用前,请确认已在[云开发平台/身份认证/登录方式/常规登录](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中开启邮箱/短信验证码登录 - 如果用户不存在,会默认注册用户,可以通过 `shouldCreateUser`参数控制是否自动创建用户,默认为 true Promise", description: "验证码回调函数,支持 messageId 参数,用于验证用户输入的验证码", children: [{ name: "params", type: "verifyParams", required: true, description: "状态变化回调函数入参", children: [ { name: "token", type: "string", required: true, description: "验证码", }, { name: "messageId", type: "string", description: "验证码 ID", } ] }, { name: "return", type: "Promise", required: true, description: "返回",children: [{ name: "data", type: "SignInResData", required: true, description: "", children: [ { name: "user", type: "User", required: true, description: "用户详细信息,包含身份信息和元数据", }, { name: "session", type: "Session", required: true, description: "会话信息,包含访问令牌和刷新令牌", }, ] },{ name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }] }, ] }, ], }, { name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }]}] }} > ```typescript const { data, error } = await auth.signInWithOtp({ phone: "13800138000", }); if (error) { console.error("发送验证码失败:", error.message); } else { console.log("验证码已发送,等待用户输入..."); // 用户输入验证码后验证 const { data: loginData, error: loginError } = await data.verifyOtp({ token: "123456", }); if (loginError) { console.error("验证失败:", loginError.code, loginError.category, loginError.message); switch (loginError.code) { case "invalid_argument": // 验证码过期/不正确/不匹配,提示重新获取 console.error(loginError.message || "验证码已过期或不正确,请重新获取"); break; case "unauthenticated": // Token 失效,需要重新登录 console.error("认证失效,请重新登录"); window.location.href = "/login"; break; case "failed_precondition": // 账号已被绑定等 console.error(loginError.message); break; case "not_found": console.error("用户不存在"); break; case "unavailable": console.error("服务暂不可用,请稍后再试"); break; default: console.error("验证失败:", loginError.message); } } else { console.log("登录成功:", loginData.user); console.log("会话信息:", loginData.session); } } ``` ```typescript // 发送邮箱验证码 const { data, error } = await auth.signInWithOtp({ email: "user@example.com", }); if (error) { console.error("发送验证码失败:", error.message); } else { console.log("邮箱验证码已发送,请查收邮件..."); // 用户从邮箱获取验证码后验证 const { data: loginData, error: loginError } = await data.verifyOtp({ token: "654321", }); if (loginError) { console.error("验证失败:", loginError.message); } else { console.log("邮箱登录成功:", loginData.user?.email); } } ``` ```typescript const { data, error } = await auth.signInWithOtp({ phone: "13800138000", options: { shouldCreateUser: false, }, }); if (error) { console.error("发送验证码失败:", error.message); } else { console.log("验证码已发送,等待用户输入..."); // 用户输入验证码后验证 const { data: loginData, error: loginError } = await data.verifyOtp({ token: "123456", }); // 未注册用户会提示“user not exist” } ``` ```typescript try { const { data, error } = await auth.signInWithOtp({ phone: "13800138000", }); if (error) { switch (error.code) { case "resource_exhausted": console.error("发送频率过高,请稍后再试"); break; case "invalid_argument": console.error("手机号或邮箱格式错误,请检查后重试"); break; case "failed_precondition": console.error("从第三方获取用户信息失败,请重试"); break; case "aborted": console.error("尝试次数过多,请返回首页,稍后重试"); break; case "permission_denied": console.error("您当前的会话已过期,请返回重试"); break; case "captcha_required": console.error("需要输入验证码,请根据反机器人服务接入"); break; case "captcha_invalid": console.error("验证码不正确,请根据反机器人服务接入"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; default: console.error("发送验证码失败:", error.message); } return; } // 验证验证码 const { data: loginData, error: loginError } = await data.verifyOtp({ token: "123456", }); if (loginError) { console.error("验证失败:", loginError.message); } else { console.log("登录成功"); } } catch (error) { console.error("网络错误:", error); } ``` --- ## signInWithOAuth ```typescript async signInWithOAuth(params: SignInWithOAuthReq): Promise ``` 生成第三方平台授权链接,支持微信、Google 等主流平台。 - 生成第三方平台(如微信、Google 等)的授权页面 URL - 将状态信息保存到浏览器会话中,以便后续验证 - 支持自定义回调地址和状态参数 - 使用前,请确认已在[云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中开启对应的 OAuth 身份源 **注意事项** - 调用此方法后,状态信息会自动保存到浏览器会话中,在 cloudbase.init 时设置`auth.detectSessionInUrl`为 `true` 时,从第三方回调回来后会自动调用 [verifyOAuth](#verifyoauth) 进行验证,否则后续需要手动通过 [verifyOAuth](#verifyoauth) 方法进行验证 - 如果未提供 state 参数,系统会自动生成格式为`prd-{provider}-{随机字符串}`的状态参数 - 回调地址需要配置在云开发平台的安全域名中,否则会返回权限错误 - 目前,使用“微信开放平台”登录时先要确保用户已关联对应的身份源,可以通过 [linkIdentity](#linkidentity) 进行身份源关联 ", description: "额外的查询参数,将合并到授权 URI 中", }, { name: "skipBrowserRedirect", type: "boolean", description: "是否跳转至授权页面,默认为false", }, { name: "type", type: "'sign_in' | 'bind_identity'", description: "类型(可选),默认为'sign_in', sign_in: 登录,bind_identity: 绑定身份", }, ], },] } ], output: [{ name: "Promise", type: "SignInOAuthRes", children: [{ name: "data", type: "SignInOAuthResData", required: true, description: "", children: [ { name: "url", type: "string", required: true, description: "授权页面 URL,用于跳转到第三方平台授权页面", }, { name: "provider", type: "string", required: true, description: "第三方平台标识,用于后续验证和状态管理", }, ], }, { name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }]}] }} > ```typescript // 初始化 const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境ID region: "ap-shanghai", // 地域,默认为上海 accessKey: "", // 填入生成的 Publishable Key, auth: { detectSessionInUrl: true, // 可选:自动检测 URL 中的 OAuth 参数 }, }); const { data, error } = await auth.signInWithOAuth({ provider: "wechat", options: { redirectTo: "https://example.com/callback", state: "wx_auth_123456", }, }); if (error) { console.error("获取微信授权链接失败:", error.message); } else { console.log("微信授权链接:", data.url); console.log("第三方平台:", data.provider); // 跳转到微信授权页面 window.location.href = data.url; } ``` ```typescript const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境ID region: "ap-shanghai", // 地域,默认为上海 accessKey: "", // 填入生成的 Publishable Key, auth: { detectSessionInUrl: true, // 可选:自动检测 URL 中的 OAuth 参数 }, }); const auth = app.auth; const { data, error } = await auth.signInWithOAuth({ provider: "google", }); if (error) { console.error("获取Google授权链接失败:", error.message); } else { console.log("Google授权链接已生成,准备跳转..."); console.log("授权URL:", data.url); // 在新窗口打开授权页面 window.open(data.url, "_blank"); } ``` ```typescript // OAuth登录最佳实践 - 完整的UI交互流程 // 初始化 const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境ID region: "ap-shanghai", // 地域,默认为上海 accessKey: "", // 填入生成的 Publishable Key, auth: { detectSessionInUrl: true, // 可选:自动检测 URL 中的 OAuth 参数,适用于signInWithOAuth、linkIdentity }, }); // 修改认证状态变化监听器,添加浮窗显示 async function setupAuthStateChangeListener() { try { if (!app.auth) return; // 订阅认证状态变化事件 const { data } = await app.auth.onAuthStateChange( (event, session, info) => { console.log("认证状态变化:", { event, session, info }); switch (event) { case "SIGNED_IN": if (session && session.user) { console.log("登录成功!"); } break; default: console.log("未知认证事件:", event); } } ); console.log("认证状态变化监听器已设置"); } catch (error) { console.error("设置认证状态变化监听器失败:", error); } } class OAuthManager { constructor() { this.initEventListeners(); this.provider = "oauth"; } // 初始化事件监听器 initEventListeners() { // OAuth登录按钮点击事件 document.getElementById("oauth-login-btn").addEventListener("click", () => { this.startOAuth(); }); } // 开始OAuth授权流程 async startOAuth() { try { this.showLoading(true); this.hideError(); const { data, error } = await auth.signInWithOAuth({ provider: this.provider, }); if (error) { this.handleOAuthError(error); } else { console.log("OAuth授权链接生成成功,正在跳转..."); // 最佳实践:使用当前窗口跳转,保持用户体验 window.location.href = data.url; } } catch (error) { this.handleOAuthError(error); } finally { this.showLoading(false); } } showLoading(show) { document.getElementById("loading").style.display = show ? "block" : "none"; } } setupAuthStateChangeListener(); // 初始化OAuth登录管理器 const oAuthManager = new OAuthManager(); ``` ```typescript // 初始化 const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境ID region: "ap-shanghai", // 地域,默认为上海 accessKey: "", // 填入生成的 Publishable Key, }); class OAuthManager { constructor() { this.initEventListeners(); this.provider = "oauth"; } // 初始化事件监听器 initEventListeners() { // OAuth登录按钮点击事件 document.getElementById("oauth-login-btn").addEventListener("click", () => { this.startOAuth(); }); // 页面加载时检查是否有OAuth授权回调 document.addEventListener("DOMContentLoaded", () => { this.checkOAuthCallback(); }); } // 开始OAuth授权流程 async startOAuth() { try { this.showLoading(true); this.hideError(); const { data, error } = await auth.signInWithOAuth({ provider: this.provider, }); if (error) { this.handleOAuthError(error); } else { console.log("OAuth授权链接生成成功,正在跳转..."); // 最佳实践:使用当前窗口跳转,保持用户体验 window.location.href = data.url; } } catch (error) { this.handleOAuthError(error); } finally { this.showLoading(false); } } // 检查OAuth授权回调 async checkOAuthCallback() { const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get("code"); const state = urlParams.get("state"); if (code && state) { console.log("检测到OAuth授权回调,正在验证..."); await this.verifyOAuth(code, state); } } // 验证OAuth授权 async verifyOAuth(code, state) { try { this.showLoading(true); const result = await auth.verifyOAuth({ code: code, state: state, provider: this.provider, }); if (result.error) { this.handleOAuthError(result.error); } else { console.log("OAuth登录成功!"); this.showSuccess("OAuth登录成功!"); } } catch (error) { this.handleOAuthError(error); } finally { this.showLoading(false); } } // 错误处理 handleOAuthError(error) { console.error("OAuth登录错误:", error); switch (error.code) { case "provider_not_supported": this.showError("不支持的第三方平台,请检查平台标识是否正确"); break; case "invalid_redirect_uri": this.showError("回调地址格式错误,请检查URL格式"); break; case "failed_precondition": this.showError("从OAuth获取用户信息失败,请检查平台配置"); break; case "permission_denied": this.showError("权限不足,请检查安全域名配置"); break; case "resource_exhausted": this.showError("请求频率过高,请稍后重试"); break; case "unreachable": this.showError("网络连接失败,请检查网络设置后重试"); break; case "invalid_code": this.showError("授权码无效或已过期,请重新授权"); break; case "state_mismatch": this.showError("状态参数不匹配,可能存在安全风险,请重新授权"); break; default: this.showError("OAuth登录失败:" + (error.message || "未知错误")); } } showLoading(show) { document.getElementById("loading").style.display = show ? "block" : "none"; } showError(message) { const errorElement = document.getElementById("error-message"); errorElement.textContent = message; errorElement.style.display = "block"; } hideError() { document.getElementById("error-message").style.display = "none"; } showSuccess(message) { const successElement = document.getElementById("success-message"); successElement.textContent = message; successElement.style.display = "block"; } } ``` --- ## signInWithIdToken ```typescript async signInWithIdToken(params: SignInWithIdTokenReq): Promise ``` 使用第三方平台的身份令牌登录,支持微信、Google 等主流平台。 - 使用第三方平台(如微信、Google 等)的身份令牌进行登录 - 支持指定第三方平台标识,第三方平台需在[云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中先进行配置配置 - 令牌为必填参数 ```typescript const { data, error } = await auth.signInWithIdToken({ provider: "wechat", token: "wx_token_1234567890", }); if (error) { console.error("微信登录失败:", error.message); } else { console.log("微信登录成功,用户信息:", data.user); console.log("会话信息:", data.session); } ``` ```typescript const { data, error } = await auth.signInWithIdToken({ provider: "google", token: "google_token_abcdefg", }); if (error) { console.error("Google登录失败:", error.message); } else { console.log("Google登录成功,用户昵称:", data.user?.user_metadata?.nickName); } ``` ```typescript const { data, error } = await auth.signInWithIdToken({ token: "generic_token_xyz", }); if (error) { console.error("令牌登录失败:", error.message); } else { console.log("令牌登录成功,用户ID:", data.user?.id); } ``` ```typescript try { const { data, error } = await auth.signInWithIdToken({ provider: "wechat", token: "invalid_token", }); if (error) { switch (error.code) { case "invalid_token": console.error("令牌无效或已过期,请重新获取"); break; case "provider_not_supported": console.error("不支持的第三方平台,请使用其他登录方式"); break; case "failed_precondition": console.error("从第三方获取用户信息失败,请重试"); break; case "resource_exhausted": console.error("尝试过于频繁,请稍后重试"); break; case "permission_denied": console.error("权限不足,请检查令牌权限范围"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; default: console.error("登录失败:", error.message); } } else { console.log("登录成功"); } } catch (error) { console.error("网络错误:", error); } ``` ## signInWithCustomTicket ```typescript async signInWithCustomTicket(getTickFn: GetCustomSignTicketFn): Promise ``` 使用自定义登录票据进行登录,支持完全自定义的登录流程。 - 使用自定义的登录票据进行身份验证 - 支持传入获取自定义登录票据的函数 - 适用于需要完全自定义登录流程的场景 - 签发 Ticket 详细流程可参考[自定义登录](/authentication-v2/method/custom-login) " }], output: [{ name: "Promise", type: "SignInRes", children: [{ name: "data", type: "SignInResData", required: true, description: "", children: [ { name: "user", type: "User", required: true, description: "用户详细信息,包含身份信息和元数据", }, { name: "session", type: "Session", required: true, description: "会话信息,包含访问令牌和刷新令牌", }, ], }, { name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }]}] }} > ```typescript // 获取自定义登录票据的函数 const getTickFn = () => Promise.resolve("custom_ticket_123456"); const { data, error } = await auth.signInWithCustomTicket(getTickFn); if (error) { console.error("自定义登录失败:", error.message); } else { console.log("自定义登录成功,用户信息:", data.user); console.log("会话信息:", data.session); } ``` ```typescript // 异步获取自定义登录票据 const getTickFn = async () => { // 模拟从后端API获取票据 const response = await fetch("/api/get-custom-ticket"); const data = await response.json(); return data.ticket; }; const { data, error } = await auth.signInWithCustomTicket(getTickFn); if (error) { console.error("自定义登录失败:", error.message); } else { console.log("自定义登录成功"); } ``` ```typescript try { const getTickFn = () => Promise.resolve("custom_ticket_123456"); const { data, error } = await auth.signInWithCustomTicket(getTickFn); if (error) { switch (error.code) { case "invalid_ticket": console.error("票据无效或已过期,请重新获取"); break; case "ticket_required": console.error("需要提供自定义登录票据"); break; case "network_error": console.error("网络连接失败,请检查网络设置后重试"); break; default: console.error("登录失败:", error.message); } } else { console.log("登录成功"); } } catch (error) { console.error("网络错误:", error); } ``` ## signInWithOpenId ```typescript async signInWithOpenId(params?: SignInWithOpenIdReq): Promise ``` 微信小程序 OpenID 静默登录。如果用户不存在,会根据[云开发平台/登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中对应身份源的`登录模式`配置,判断是否自动注册。 :::info 提示 仅支持在 `微信小程序` 中使用 ::: ```js const { data, error } = await auth.signInWithOpenId(); ``` ```js const { data, error } = await auth.signInWithOpenId({ useWxCloud: false }); ``` ## signInWithPhoneAuth ```typescript async signInWithPhoneAuth(params: SignInWithPhoneAuthReq): Promise ``` 微信小程序手机号授权登录。如果用户不存在,会根据[云开发平台/登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中对应身份源的`登录模式`配置,判断是否自动注册。 :::info 提示 仅支持在 `微信小程序` 中使用 ::: ```js const { data, error } = await auth.signInWithPhoneAuth({ phoneCode: "xxx" }); ``` ## 会话管理 ## getSession ```typescript async getSession(): Promise ``` 获取当前会话信息,检查用户登录状态。 - 获取当前用户的会话信息,包括访问令牌、用户信息等 - 检查用户是否已登录,未登录时返回空会话 ```typescript const { data, error } = await auth.getSession(); if (error) { console.error("获取会话失败:", error.message); } else if (data.session) { console.log("用户已登录:", data.session.user); console.log("访问令牌:", data.session.access_token); console.log("过期时间:", data.session.expires_in, "秒"); } else { console.log("用户未登录,请先登录"); // 显示登录按钮 document.getElementById("loginBtn").style.display = "block"; } ``` ```typescript document.addEventListener("DOMContentLoaded", async () => { const { data, error } = await auth.getSession(); if (error) { console.error("检查登录状态失败:", error.message); return; } if (data.session) { // 用户已登录,显示用户信息 document.getElementById("userInfo").innerHTML = `

欢迎,${data.session.user?.name || data.session.user?.username}

`; document.getElementById("loginBtn").style.display = "none"; document.getElementById("logoutBtn").style.display = "block"; } else { // 用户未登录,显示登录界面 document.getElementById("loginForm").style.display = "block"; } }); ```
```typescript // 定时检查会话状态,自动刷新令牌 function setupSessionMonitor() { setInterval(async () => { const { data, error } = await auth.getSession(); if (error) { console.error("会话检查失败:", error.message); } else if (data.session) { const expiresIn = data.session.expires_in; // 如果令牌将在5分钟内过期,则自动刷新 if (expiresIn < 300) { console.log("令牌即将过期,自动刷新..."); await auth.refreshSession(); } } }, 60000); // 每分钟检查一次 } // 启动会话监控 setupSessionMonitor(); ``` ```typescript try { const { data, error } = await auth.getSession(); if (error) { switch (error.code) { case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; case "token_expired": console.error("访问令牌已过期,请重新登录"); // 自动刷新令牌 await auth.refreshSession(); break; case "invalid_refresh_token": console.error("刷新令牌无效,请重新登录"); break; case "refresh_token_expired": console.error("刷新令牌已过期,请重新登录"); break; case "user_not_found": console.error("用户不存在,请重新登录"); break; case "permission_denied": console.error("权限不足,请检查安全域名配置"); break; default: console.error("获取会话失败:", error.message); } } else { console.log("会话获取成功"); } } catch (error) { console.error("未知错误:", error); } ```
--- ## refreshSession ```typescript async refreshSession(refresh_token?: string): Promise ``` 刷新会话令牌,延长用户登录状态,支持自动续期和错误恢复。 - 使用刷新令牌获取新的访问令牌 - 延长用户会话的有效期 - 支持使用指定的刷新令牌或默认令牌 ```typescript const { data, error } = await auth.refreshSession(); if (error) { console.error("刷新会话失败:", error.message); // 刷新失败,可能需要重新登录 window.location.href = "/login"; } else { console.log("会话刷新成功,新令牌:", data.session?.access_token); console.log("新过期时间:", data.session?.expires_in, "秒"); } ``` ```typescript const savedRefreshToken = "refresh_token"; if (savedRefreshToken) { const { data, error } = await auth.refreshSession(savedRefreshToken); if (error) { console.error("使用保存的令牌刷新失败:", error.message); } else { console.log("使用保存的令牌刷新成功"); } } else { console.log("没有保存的刷新令牌,使用默认方式刷新"); const { data, error } = await auth.refreshSession(); if (error) { console.error("刷新失败:", error.message); } } ``` ```typescript // 设置定时器,在令牌过期前自动刷新 function setupTokenRefresh() { setInterval(async () => { const { data, error } = await auth.getSession(); if (data.session) { const expiresIn = data.session.expires_in; // 如果令牌将在5分钟内过期,则刷新 if (expiresIn < 300) { console.log("令牌即将过期,自动刷新..."); const { data: refreshData, error: refreshError } = await auth.refreshSession(); if (refreshError) { console.error("自动刷新失败:", refreshError.message); } else { console.log("自动刷新成功"); } } } }, 60000); // 每分钟检查一次 } // 启动定时刷新 setupTokenRefresh(); ``` ```typescript try { const { data, error } = await auth.refreshSession(); if (error) { switch (error.code) { case "invalid_refresh_token": console.error("刷新令牌无效,请重新登录"); // 需要重新登录 window.location.href = "/login"; break; case "refresh_token_expired": console.error("刷新令牌已过期,请重新登录"); // 需要重新登录 window.location.href = "/login"; break; case "user_not_found": console.error("用户不存在,请重新注册"); // 清除本地会话 localStorage.removeItem("refresh_token"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; case "permission_denied": console.error("权限不足,请检查安全域名配置"); break; case "resource_exhausted": console.error("刷新频率过高,请稍后重试"); break; default: console.error("刷新失败:", error.message); } } else { console.log("刷新成功"); } } catch (error) { console.error("网络错误:", error); } ``` --- ## setSession ```typescript async setSession(params: SetSessionReq): Promise ``` 使用现有的访问令牌和刷新令牌来设置用户会话,支持外部系统集成和手动会话管理。 - 使用现有的 access_token 和 refresh_token 来设置用户会话 - 适用于从外部系统获取令牌后手动设置会话的场景 - 成功设置会话后会触发 SIGNED_IN 事件 ```typescript const { data, error } = await auth.setSession({ access_token: "your_access_token_here", refresh_token: "your_refresh_token_here", }); if (error) { console.error("会话设置失败:", error.message); } else { console.log("会话设置成功"); console.log("用户信息:", data.user); console.log("会话信息:", data.session); } ``` --- ## signOut ```typescript async signOut(params?: SignOutReq): Promise ``` 用户登出,清除当前会话和本地存储。 - 安全退出当前用户登录状态 - 清除服务器端会话和本地存储 - 支持重定向到指定页面 - 触发认证状态变化事件 ```typescript const { data, error } = await auth.signOut(); if (error) { console.error("登出失败:", error.message); } else { console.log("登出成功"); // 登出后跳转到登录页 window.location.href = "/login"; } ``` ```typescript const { data, error } = await auth.signOut({ options: { redirectTo: "/login", clearStorage: true, }, }); if (error) { console.error("登出失败:", error.message); } else { console.log("登出成功,正在跳转到登录页..."); // 自动重定向到登录页 window.location.href = "/login"; } ``` ```typescript async function safeSignOut() { // 显示确认对话框 if (!confirm("确定要退出登录吗?")) { return; } // 显示加载状态 document.getElementById("logoutBtn").disabled = true; document.getElementById("logoutBtn").innerText = "登出中..."; const { data, error } = await auth.signOut(); if (error) { console.error("登出失败:", error.message); alert("登出失败: " + error.message); // 恢复按钮状态 document.getElementById("logoutBtn").disabled = false; document.getElementById("logoutBtn").innerText = "退出登录"; } else { console.log("登出成功"); // 清除本地存储 localStorage.removeItem("user_session"); sessionStorage.clear(); // 显示成功消息 alert("已安全退出登录"); // 跳转到登录页 window.location.href = "/login"; } } // 登出按钮点击事件 document.getElementById("logoutBtn").addEventListener("click", safeSignOut); ``` ```typescript try { const { data, error } = await auth.signOut(); if (error) { switch (error.code) { case "network_error": console.error("网络连接失败,请检查网络设置后重试"); alert("网络连接失败,请稍后重试"); break; case "session_not_found": console.error("会话不存在,可能已经登出"); // 清除本地存储并跳转 localStorage.clear(); window.location.href = "/login"; break; case "token_invalid": console.error("令牌无效,请重新登录"); // 强制清除并跳转 localStorage.clear(); window.location.href = "/login"; break; case "permission_denied": console.error("权限不足,无法执行登出操作"); alert("权限不足,无法登出"); break; case "unreachable": console.error("服务器连接失败,请检查网络设置"); alert("服务器连接失败,请检查网络"); break; default: console.error("登出失败:", error.message); alert("登出失败: " + error.message); } } else { console.log("登出成功"); // 显示成功消息 alert("已安全退出登录"); // 跳转到登录页 window.location.href = "/login"; } } catch (error) { console.error("未知错误:", error); alert("发生未知错误,请重试"); } ``` ## 用户管理 ## getUser ```typescript async getUser(): Promise ``` 获取当前登录用户的详细信息,包括身份信息、元数据和权限状态,支持用户资料展示和权限验证。 - 获取当前登录用户的完整信息 - 包括用户基本信息、元数据、身份信息等 需要用户已登录状态才能获取完整信息 - 支持检查用户权限和验证状态 ```typescript const { data, error } = await auth.getUser(); if (error) { console.error("获取用户信息失败:", error.message); } else if (data.user) { const user = data.user; console.log("用户ID:", user.id); console.log("邮箱:", user.email); console.log("手机号:", user.phone); console.log("用户名:", user.user_metadata?.username); console.log("昵称:", user.user_metadata?.nickName); console.log("头像:", user.user_metadata?.avatarUrl); console.log("注册时间:", user.created_at); } else { console.log("用户未登录"); } ``` ```typescript // 在用户资料页面显示详细信息 async function loadUserProfile() { const { data, error } = await auth.getUser(); if (error) { console.error("获取用户信息失败:", error.code, error.category, error.message); return; } if (data.user) { const user = data.user; console.log("用户邮箱:", user.email || "未设置"); console.log("用户手机:", user.phone || "未设置"); console.log("用户名:", user.user_metadata?.name || "未设置"); console.log("昵称:", user.user_metadata?.nickName || "未设置"); console.log("注册时间:", new Date(user.created_at).toLocaleString()); } else { console.error("用户未登录"); } } // 页面加载时调用 loadUserProfile(); ``` ```typescript async function checkUserPermissions() { const { data, error } = await auth.getUser(); if (error) { console.error("获取用户信息失败:", error.message); return false; } if (data.user) { const user = data.user; // 检查邮箱是否已验证 if (!user.email_confirmed_at) { console.log("邮箱未验证,需要验证邮箱"); return false; } // 检查用户角色 if (user.role?.includes("administrator")) { console.log("管理员用户,拥有全部权限"); return true; } else if (!!user.role?.length) { console.log("普通用户,拥有基本权限"); return true; } else { console.log("未知用户角色"); return false; } } else { console.log("用户未登录,无权限"); return false; } } // 检查权限并执行操作 if (await checkUserPermissions()) { // 有权限,执行操作 console.log("有权限,继续执行..."); } else { // 无权限,显示错误 console.log("无权限,操作被拒绝"); } ``` ```typescript try { const { data, error } = await auth.getUser(); if (error) { switch (error.code) { case "user_not_found": console.error("用户不存在,请重新登录"); // 可能是会话已过期,需要重新登录 window.location.href = "/login"; break; case "token_expired": console.error("访问令牌已过期,尝试刷新令牌"); // 尝试刷新令牌 await auth.refreshSession(); // 重新获取用户信息 await auth.getUser(); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; case "permission_denied": console.error("权限不足,请检查安全域名配置"); break; case "invalid_refresh_token": console.error("刷新令牌无效,请重新登录"); window.location.href = "/login"; break; case "refresh_token_expired": console.error("刷新令牌已过期,请重新登录"); window.location.href = "/login"; break; default: console.error("获取用户信息失败:", error.message); } } else { console.log("用户信息获取成功"); } } catch (error) { console.error("未知错误:", error); } ``` --- ## refreshUser ```typescript async refreshUser(): Promise ``` 刷新当前登录用户的信息。 - 刷新当前登录用户的完整信息 - 从服务器重新获取最新的用户数据 - 适用于用户信息可能已更新但本地缓存未同步的场景 需要用户已登录状态才能刷新信息 ```typescript const { data, error } = await auth.refreshUser(); if (error) { console.error("刷新用户信息失败:", error.message); } else { console.log("用户信息已刷新"); console.log("最新用户信息:", data.user); console.log("最新会话信息:", data.session); } ``` ```typescript // 当用户修改资料后,刷新页面显示 async function refreshUserProfile() { const { data, error } = await auth.refreshUser(); if (error) { console.error("刷新失败:", error.message); return false; } if (data.user) { const user = data.user; // 更新页面显示 document.getElementById("userEmail").innerText = user.email || "未设置"; document.getElementById("userPhone").innerText = user.phone || "未设置"; document.getElementById("name").innerText = user.user_metadata?.name || "未设置"; document.getElementById("userNickname").innerText = user.user_metadata?.nickName || "未设置"; document.getElementById("userAvatar").src = user.user_metadata?.avatarUrl || "/default-avatar.png"; console.log("用户信息已刷新并更新显示"); return true; } return false; } // 在用户修改资料后调用 await refreshUserProfile(); ``` ```typescript async function safeRefreshUser() { try { const { data, error } = await auth.refreshUser(); if (error) { switch (error.code) { case "user_not_found": console.error("用户不存在,请重新登录"); break; case "token_expired": console.error("访问令牌已过期,尝试刷新会话"); await auth.refreshSession(); // 重新刷新用户信息 return await auth.refreshUser(); case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; default: console.error("刷新用户信息失败:", error.message); } return null; } return data; } catch (error) { console.error("刷新过程中发生未知错误:", error); return null; } } // 安全地刷新用户信息 const refreshedData = await safeRefreshUser(); if (refreshedData) { console.log("用户信息刷新成功"); } ``` --- ## updateUser ```typescript async updateUser(params: UpdateUserReq): Promise ``` 更新当前登录用户的信息。 - 不支持更新密码,更新密码请使用 [resetPasswordForEmail](#resetpasswordforemail) 或 [resetPasswordForOld](#resetPasswordForOld) 或 [reauthenticate](#reauthenticate) - 更新当前登录用户的基本信息和元数据 - 支持更新邮箱、手机号、用户名、昵称、头像等 - 需要用户已登录状态才能更新信息 更新成功后返回更新后的用户信息 Promise", description: "验证码回调函数,修改手机号或邮箱时需要验证码", children: [{ name: "params", type: "verifyParams", required: true, description: "状态变化回调函数入参", children: [ { name: "token", type: "string", required: true, description: "验证码", }, { name: "email", type: "string", description: "邮箱", }, { name: "phone", type: "string", description: "手机号", } ] }, { name: "return", type: "Promise", required: true, description: "返回",children: [{ name: "data", type: "SignInResData", required: true, description: "", children: [ { name: "user", type: "User", required: true, description: "用户详细信息,包含身份信息和元数据", }, ] },{ name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }] }, ] } ], }, { name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }] }] }} > ```typescript const { data, error } = await auth.updateUser({ nickname: "新昵称", gender: "MALE", }); if (error) { console.error("更新用户信息失败:", error.message); } else { console.log("用户信息已更新:", data.user); console.log("新邮箱:", data.user?.email); console.log("新昵称:", data.user?.user_metadata?.nickName); } ``` ```typescript // 更新 email 或 phone(需要验证) const { data } = await app.auth.updateUser({ email: "new@example.com", }); // 调用 verifyOtp 回调验证 await data.verifyOtp({ email: "new@example.com", token: "123456" }); ``` ```typescript async function saveUserProfile(formData) { const { data, error } = await auth.updateUser({ username: formData.username, nickname: formData.nickname, gender: formData.gender, description: formData.description, avatar_url: formData.avatarUrl, }); if (error) { console.error("保存失败:", error.code, error.category, error.message); return false; } else { console.log("资料保存成功"); return true; } } // 调用示例 await saveUserProfile({ username: "new_username", nickname: "新昵称", gender: "male", description: "个人简介", avatarUrl: "https://example.com/avatar.png", }); ``` ```typescript try { // 第一步:更新用户信息(发送验证码阶段) const { data, error } = await auth.updateUser({ email: "new@example.com", }); if (error) { console.error("更新失败:", error.code, error.category, error.message); switch (error.code) { case "invalid_argument": console.error("参数格式错误:", error.message); break; case "failed_precondition": console.error("前置条件不满足:", error.message); break; case "resource_exhausted": console.error("发送频率过高,请稍后再试"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; default: console.error("更新失败:", error.message); } return; } // 第二步:如果修改了邮箱/手机号,需要验证码验证(verify 阶段) if (data.verifyOtp) { const { data: verifyData, error: verifyError } = await data.verifyOtp({ email: "new@example.com", token: "123456", }); if (verifyError) { console.error("验证失败:", verifyError.code, verifyError.category, verifyError.message); switch (verifyError.code) { case "invalid_argument": // 验证码过期/不正确 console.error( verifyError.message || "验证码已过期或不正确,请重新获取" ); break; case "unauthenticated": // Token 失效,需要重新登录 console.error("认证失效,请重新登录"); window.location.href = "/login"; break; case "failed_precondition": // 该邮箱/手机号已被其他账号绑定 console.error(verifyError.message); break; case "not_found": console.error("用户不存在,请重新登录"); break; case "unavailable": console.error("服务暂不可用,请稍后再试"); break; default: console.error("验证失败:", verifyError.message); } } else { console.log("邮箱更新成功"); } } else { console.log("用户信息更新成功"); } } catch (error) { console.error("网络错误:", error); } ``` --- ## deleteUser ```typescript async deleteUser(params: DeleteMeReq): Promise ``` 删除当前登录用户的账户。 - 永久删除当前登录用户的账户 - 需要验证用户密码进行身份确认 - 删除后所有用户数据将被永久移除 - 操作不可逆,请谨慎使用 ```typescript const { data, error } = await auth.deleteUser({ password: "userPassword123", }); if (error) { console.error("账户删除失败:", error.message); } else { console.log("账户删除成功"); // 用户已登出,重定向到首页 window.location.href = "/"; } ``` ```typescript async function deleteAccount(password) { if (!confirm("确定要删除账户吗?此操作不可逆,所有数据将被永久删除!")) { return false; } const { data, error } = await auth.deleteUser({ password }); if (error) { switch (error.code) { case "invalid_password": alert("密码错误,请重新输入"); break; case "user_not_found": alert("用户不存在"); break; default: alert("删除失败: " + error.message); } return false; } else { alert("账户删除成功"); return true; } } // 删除账户表单提交 document .getElementById("deleteAccountForm") .addEventListener("submit", async (e) => { e.preventDefault(); const password = document.getElementById("password").value; const success = await deleteAccount(password); if (success) { // 重定向到首页 window.location.href = "/"; } }); ``` ```typescript async function deleteAccountWithVerification(password, verificationCode) { // 第一步:验证密码 const { data: verifyData, error: verifyError } = await auth.reauthenticate(); if (verifyError) { alert("身份验证失败: " + verifyError.message); return false; } // 第二步:等待验证码输入 const { data: updateData, error: updateError } = await verifyData.updateUser({ nonce: verificationCode, password, }); if (updateError) { alert("验证码错误: " + updateError.message); return false; } // 第三步:删除账户 const { data, error } = await auth.deleteUser({ password }); if (error) { alert("删除失败: " + error.message); return false; } else { alert("账户删除成功"); return true; } } ``` ```typescript class AccountDeletionManager { constructor() { this.deletionAttempts = 0; this.maxAttempts = 3; } async deleteAccount(password) { if (this.deletionAttempts >= this.maxAttempts) { alert("删除尝试次数过多,请稍后再试"); return false; } this.deletionAttempts++; const { data, error } = await auth.deleteUser({ password }); if (error) { if (error.code === "invalid_password") { const remainingAttempts = this.maxAttempts - this.deletionAttempts; alert(`密码错误,剩余尝试次数: ${remainingAttempts}`); } else { alert("删除失败: " + error.message); } return false; } else { alert("账户删除成功"); this.deletionAttempts = 0; return true; } } resetAttempts() { this.deletionAttempts = 0; } } // 使用账户删除管理器 const deletionManager = new AccountDeletionManager(); // 删除账户 document.getElementById("deleteBtn").addEventListener("click", async () => { const password = prompt("请输入密码确认删除账户:"); if (password) { await deletionManager.deleteAccount(password); } }); ``` ## 身份源管理 ## getUserIdentities ```typescript async getUserIdentities(): Promise ``` 获取当前用户绑定的所有身份源信息。 - 获取所有第三方身份源,相关身份源可在[云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中进行配置配置 - 返回身份源的详细信息,包括平台标识、身份源 ID、绑定时间等 - 需要用户已登录状态才能获取身份源信息 ", required: true, description: "身份源信息列表", }, ], }, { name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }] }] }} > ```typescript const { data, error } = await auth.getUserIdentities(); if (error) { console.error("获取身份源失败:", error.message); } else if (data.identities) { console.log("用户绑定的身份源:", data.identities); data.identities.forEach((identity) => { console.log( `- ${identity.name} (${identity.provider}): ${identity.provider_user_id}` ); console.log( ` 绑定时间: ${new Date(identity.created_at).toLocaleString()}` ); }); } else { console.log("用户未绑定任何身份源"); } ``` ```typescript async function loadUserIdentities() { const { data, error } = await auth.getUserIdentities(); if (error) { console.error("获取身份源失败:", error.code, error.category, error.message); return; } if (data.identities && data.identities.length > 0) { data.identities.forEach((identity) => { console.log(`身份源: ${identity.name}, 平台: ${identity.provider}, 绑定时间: ${new Date(identity.created_at).toLocaleString()}`); }); } else { console.log("您还没有绑定任何第三方账号"); } } // 页面加载时调用 loadUserIdentities(); ``` ```typescript async function isProviderBound(provider) { const { data, error } = await auth.getUserIdentities(); if (error) { console.error("检查身份源失败:", error.message); return false; } if (data.identities) { return data.identities.some((identity) => identity.provider === provider); } return false; } // 检查是否绑定了微信 const isWechatBound = await isProviderBound("wechat"); if (isWechatBound) { console.log("已绑定微信账号"); document.getElementById("bindWechatBtn").style.display = "none"; } else { console.log("未绑定微信账号"); document.getElementById("bindWechatBtn").style.display = "block"; } ``` --- ## linkIdentity ```typescript async linkIdentity(params: LinkIdentityReq): Promise ``` 绑定新的身份源到当前用户,会自动跳转第三方 OAuth 授权页面。 - 将新的第三方身份源绑定到当前登录用户,支持绑定微信、Google、GitHub 等第三方平台,身份源需先在[云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中进行配置配置 - 绑定成功后,用户可以使用该身份源进行登录 - 需要用户已登录状态才能绑定身份源 - 在 cloudbase.init 时设置`auth.detectSessionInUrl`为 `true` 时,从第三方回调回来后会自动调用 [verifyOAuth](#verifyoauth) 进行验证,否则后续需要手动通过 [verifyOAuth](#verifyoauth) 方法进行验证 ```typescript const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境ID region: "ap-shanghai", // 地域,默认为上海 accessKey: "", // 填入生成的 Publishable Key, auth: { detectSessionInUrl: true, // 可选:自动检测 URL 中的 OAuth 参数 }, }); // 监听身份源绑定事件 auth.onAuthStateChange((event, session, info) => { console.log("认证状态变化:", { event, session, info }); switch (event) { case "BIND_IDENTITY": if (!!info.error) { console.error("身份源绑定失败:", info.error); // 可以在这里添加UI错误提示 } else { console.log("身份源已绑定"); // 重新加载身份源列表 await auth.getUserIdentities(); } break; default: return; } }); try { const { data, error } = await auth.linkIdentity({ provider: "google", }); if (error) { console.error("绑定身份源失败:", error.message); // 处理绑定失败逻辑 return; } console.log("身份源绑定请求已发送,等待用户授权..."); // 绑定请求成功,等待用户完成OAuth授权流程 } catch (error) { console.error("调用linkIdentity方法时发生错误:", error); // 处理网络错误或其他异常 } ``` ```typescript const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境ID region: "ap-shanghai", // 地域,默认为上海 accessKey: "", // 填入生成的 Publishable Key, auth: { detectSessionInUrl: true, // 可选:自动检测 URL 中的 OAuth 参数 }, }); const auth = app.auth; async function bindProvider(provider) { try { // 显示加载状态 document.getElementById("bindBtn").disabled = true; document.getElementById("status").innerText = "绑定中..."; const { data, error } = await auth.linkIdentity({ provider }); if (error) { console.error("身份源绑定失败:", error); document.getElementById("status").innerText = "绑定失败: " + error.message; document.getElementById("bindBtn").disabled = false; // 根据错误类型提供更具体的提示 if (error.message.includes("already bound")) { document.getElementById("status").innerText = "该身份源已绑定,无需重复绑定"; } else if (error.message.includes("not logged in")) { document.getElementById("status").innerText = "请先登录后再绑定身份源"; } else if (error.message.includes("provider not found")) { document.getElementById("status").innerText = "不支持的身份源类型"; } } else { console.log("身份源绑定请求已发送,等待用户授权..."); document.getElementById("status").innerText = "正在跳转授权页面..."; // 绑定请求成功,等待用户完成OAuth授权流程 // 实际绑定结果将通过onAuthStateChange事件通知 } } catch (error) { console.error("绑定身份源时发生异常:", error); document.getElementById("status").innerText = "绑定过程发生异常,请重试"; document.getElementById("bindBtn").disabled = false; } } // 监听身份源绑定结果 auth.onAuthStateChange((event, session, info) => { if (event === "BIND_IDENTITY") { if (info.error) { console.error("身份源绑定失败:", info.error); document.getElementById("status").innerText = "授权失败: " + info.error.message; document.getElementById("bindBtn").disabled = false; } else { console.log("身份源绑定成功"); document.getElementById("status").innerText = "绑定成功"; document.getElementById("bindBtn").style.display = "none"; // 更新身份源列表 loadUserIdentities(); } } }); // 绑定按钮点击事件 document.getElementById("bindWechatBtn").addEventListener("click", () => { bindProvider("wechat"); }); ``` --- ## unlinkIdentity ```typescript async unlinkIdentity(params: UnlinkIdentityReq): Promise ``` 解绑当前用户绑定的身份源。 - 解绑当前登录用户绑定的第三方身份源,相关身份源可在[云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中进行配置配置 - 解绑成功后需要重新加载身份源列表 - 使用身份源标识(provider)而非身份源 ID(identity_id)进行解绑 ```typescript const { data, error } = await auth.unlinkIdentity({ provider: "wechat", }); if (error) { console.error("解绑身份源失败:", error.message); } else { console.log("身份源解绑成功"); // 重新加载身份源列表 await auth.getUserIdentities(); } ``` ```typescript async function unbindIdentity(provider) { if (!confirm("确定要解绑这个账号吗?")) { return; } const { data, error } = await auth.unlinkIdentity({ provider }); if (error) { alert("解绑失败: " + error.message); } else { alert("解绑成功"); // 重新加载身份源列表 await loadUserIdentities(); } } // 解绑按钮点击事件 document.querySelectorAll(".unbind-btn").forEach((btn) => { btn.addEventListener("click", (e) => { const provider = e.target.dataset.provider; unbindIdentity(provider); }); }); ``` ```typescript async function unbindMultipleProviders(providers) { const results = []; for (const provider of providers) { const result = await auth.unlinkIdentity({ provider }); results.push({ provider, result }); if (result.error) { console.error(`解绑${provider}失败:`, result.error.message); } else { console.log(`解绑${provider}成功`); } } return results; } // 解绑多个身份源 const providers = ["wechat", "google", "github"]; const unbindResults = await unbindMultipleProviders(providers); // 统计解绑结果 const successCount = unbindResults.filter((r) => !r.result.error).length; console.log( `成功解绑 ${successCount} 个身份源,失败 ${ providers.length - successCount } 个` ); ``` ```typescript try { const { data, error } = await auth.unlinkIdentity({ provider: "invalid_provider", }); if (error) { switch (error.code) { case "provider_not_found": console.error("身份源不存在,请检查身份源标识是否正确"); break; case "last_identity_cannot_unlink": console.error("不能解绑最后一个身份源,请至少保留一个登录方式"); break; case "permission_denied": console.error("没有权限解绑此身份源,请检查权限设置"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; case "resource_exhausted": console.error("解绑频率过高,请稍后重试"); break; case "user_not_found": console.error("用户不存在,请重新登录"); break; case "token_expired": console.error("会话已过期,请重新登录"); break; default: console.error("解绑失败:", error.message); } } else { console.log("解绑成功"); } } catch (error) { console.error("网络错误:", error); } ``` ## 密码管理 ## resetPasswordForEmail ```typescript async resetPasswordForEmail(email: string): Promise ``` 通过邮箱重置用户密码,采用四步验证流程。 - 通过邮箱发送验证码来重置用户密码 - 采用四步验证流程:发送验证码 → 等待用户输入 → 验证验证码 → 设置新密码 - 需要用户邮箱已注册且已验证 Promise", required: true, description: "验证码回调函数,支持新密码参数", children: [{ name: "attributes", type: "UpdateUserAttributes", required: true, description: "状态变化回调函数入参", children: [ { name: "nonce", type: "string", required: true, description: "验证码", }, { name: "password", type: "string", required: true, description: "新密码", } ] }, { name: "return", type: "Promise", required: true, description: "返回",children: [{ name: "data", type: "SignInResData", required: true, description: "", children: [ { name: "user", type: "User", required: true, description: "用户详细信息,包含身份信息和元数据", }, { name: "session", type: "Session", required: true, description: "会话信息,包含访问令牌和刷新令牌", }, ] },{ name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }] }, ], }, { name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }] }] }] }} > ```typescript // 第一步:发送验证码到邮箱 const { data, error } = await auth.resetPasswordForEmail("user@example.com"); if (error) { console.error("发送验证码失败:", error.message); } else { console.log("验证码已发送到邮箱,等待用户输入..."); // 第二步:等待用户输入验证码和新密码 const verificationCode = "123456"; // 用户输入的验证码 const newPassword = "newSecurePassword123"; // 用户输入的新密码 // 第三步:验证验证码并设置新密码 const { data: loginData, error: loginError } = await data.updateUser({ nonce: verificationCode, password: newPassword, }); if (loginError) { console.error("重置密码失败:", loginError.message); } else { console.log("密码重置成功,用户已自动登录"); console.log("用户信息:", loginData.user); } } ``` ```typescript let resetPasswordVerify = null; async function startPasswordReset(email) { const { data, error } = await auth.resetPasswordForEmail(email); if (error) { console.error("发送验证码失败:", error.code, error.category, error.message); return false; } else { resetPasswordVerify = data.updateUser; console.log("验证码已发送到您的邮箱,请查收"); return true; } } async function completePasswordReset(code, newPassword) { if (!resetPasswordVerify) { console.error("请先发送验证码"); return false; } const { data, error } = await resetPasswordVerify({ nonce: code, password: newPassword, }); if (error) { console.error("重置密码失败:", error.code, error.category, error.message); if (error.code === "invalid_argument") { console.error(error.message || "验证码已过期或不正确,请重新获取"); } else if (error.code === "unauthenticated") { console.error("登录已过期,请重新登录"); window.location.href = "/login"; } else { console.error("重置密码失败:", error.message); } return false; } else { console.log("密码重置成功,已自动登录"); return true; } } // 调用示例 await startPasswordReset("user@example.com"); // 用户输入验证码后 await completePasswordReset("123456", "newSecurePassword123"); ``` ```typescript try { const { data, error } = await auth.resetPasswordForEmail("invalid-email"); if (error) { switch (error.code) { case "invalid_argument": console.error("邮箱格式错误,请检查邮箱格式"); break; case "failed_precondition": console.error("账号不存在,请检查邮箱是否正确"); break; case "resource_exhausted": console.error("发送频率过高,请稍后再试"); break; default: console.error("发送验证码失败:", error.message); } } } catch (error) { console.error("网络错误:", error); } ``` --- ## resetPasswordForOld ```typescript async resetPasswordForOld(params: ResetPasswordForOldReq): Promise ``` 通过旧密码重置当前登录用户的密码。 - 通过验证旧密码来重置当前登录用户的密码 - 需要用户已登录状态 - 适用于用户记得旧密码的场景 ```typescript const { data, error } = await auth.resetPasswordForOld({ new_password: "newSecurePassword123", old_password: "oldPassword123", }); if (error) { console.error("密码重置失败:", error.message); } else { console.log("密码重置成功"); console.log("用户信息:", data.user); console.log("会话信息:", data.session); } ``` ```typescript async function changePassword(oldPassword, newPassword) { const { data, error } = await auth.resetPasswordForOld({ new_password: newPassword, old_password: oldPassword, }); if (error) { switch (error.code) { case "invalid_password": alert("旧密码不正确"); break; case "password_too_weak": alert("新密码强度不够,请使用更复杂的密码"); break; default: alert("密码重置失败: " + error.message); } return false; } else { alert("密码重置成功"); return true; } } // 表单提交事件 document .getElementById("passwordForm") .addEventListener("submit", async (e) => { e.preventDefault(); const oldPassword = document.getElementById("oldPassword").value; const newPassword = document.getElementById("newPassword").value; const confirmPassword = document.getElementById("confirmPassword").value; if (newPassword !== confirmPassword) { alert("新密码和确认密码不一致"); return; } const success = await changePassword(oldPassword, newPassword); if (success) { // 跳转到首页或用户中心 window.location.href = "/dashboard"; } }); ``` ```typescript function validatePassword(password) { const minLength = 8; const hasUpperCase = /[A-Z]/.test(password); const hasLowerCase = /[a-z]/.test(password); const hasNumbers = /\d/.test(password); const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password); return ( password.length >= minLength && hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar ); } async function resetPasswordWithValidation(oldPassword, newPassword) { if (!validatePassword(newPassword)) { alert("密码必须包含大小写字母、数字和特殊字符,且长度不少于8位"); return false; } const { data, error } = await auth.resetPasswordForOld({ new_password: newPassword, old_password: oldPassword, }); if (error) { alert("密码重置失败: " + error.message); return false; } alert("密码重置成功"); return true; } ``` ```typescript try { const { data, error } = await auth.resetPasswordForOld({ new_password: "newPassword123", old_password: "wrongOldPassword", }); if (error) { switch (error.code) { case "invalid_password": console.error("旧密码不正确,请重新输入"); document.getElementById("oldPassword").classList.add("error"); break; case "password_too_weak": console.error("新密码强度不足,请使用更复杂的密码"); document.getElementById("newPassword").classList.add("error"); break; case "user_not_found": console.error("用户不存在,请重新登录"); window.location.href = "/login"; break; case "token_expired": console.error("会话已过期,请重新登录"); window.location.href = "/login"; break; case "permission_denied": console.error("权限不足,无法修改密码"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; case "resource_exhausted": console.error("重置频率过高,请稍后重试"); break; default: console.error("密码重置失败:", error.message); } } else { console.log("密码重置成功"); } } catch (error) { console.error("网络错误:", error); } ``` --- ## reauthenticate ```typescript async reauthenticate(): Promise ``` 重新认证当前登录用户身份,通过验证码验证并允许修改密码。 :::info 提示 `短信验证码` 仅支持 `上海` 地域 ::: - 重新认证当前已登录用户的身份,支持更新密码 - 通过发送验证码到用户注册的邮箱或手机号进行验证,优先选用邮箱,如果用户未设置邮箱则使用手机号 - 适用于安全敏感操作前的身份验证 Promise", required: true, description: "验证码回调函数,支持新密码参数", children: [{ name: "attributes", type: "UpdateUserAttributes", required: true, description: "状态变化回调函数入参", children: [ { name: "nonce", type: "string", required: true, description: "验证码", }, { name: "password", type: "string", description: "新密码,如果传入则会更新密码", } ] }, { name: "return", type: "Promise", required: true, description: "返回", children: [{ name: "data", type: "SignInResData", required: true, description: "", children: [ { name: "user", type: "User", required: true, description: "用户详细信息,包含身份信息和元数据", }, { name: "session", type: "Session", required: true, description: "会话信息,包含访问令牌和刷新令牌", }, ] },{ name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }] }, ], }, ], }, { name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }] }] }} > ```typescript // 第一步:发送验证码(使用当前用户信息) const { data, error } = await auth.reauthenticate(); if (error) { console.error("发送验证码失败:", error.message); } else { console.log("验证码已发送,等待用户输入..."); // 第二步:等待用户输入验证码和新密码 const verificationCode = "123456"; // 用户输入的验证码 const newPassword = "newSecurePassword123"; // 用户输入的新密码 // 第三步:验证验证码并设置新密码 const { data: loginData, error: loginError } = await data.updateUser({ nonce: verificationCode, password: newPassword, }); if (loginError) { console.error("重新认证失败:", loginError.message); } else { console.log("重新认证成功,密码已更新"); console.log("用户信息:", loginData.user); } } ``` ```typescript async function verifyIdentityBeforeSensitiveOperation() { // 在执行敏感操作前重新认证用户 const { data, error } = await auth.reauthenticate(); if (error) { console.error("身份验证失败:", error.message); return false; } console.log("验证码已发送到您的邮箱/手机,请输入验证码继续"); // 显示验证码输入界面 document.getElementById("verificationModal").style.display = "block"; return new Promise((resolve) => { // 等待用户输入验证码 document.getElementById("verifyBtn").addEventListener("click", async () => { const code = document.getElementById("code").value; const newPassword = document.getElementById("newPassword").value; const { data: verifyData, error: verifyError } = await data.updateUser({ nonce: code, password: newPassword, }); if (verifyError) { console.error("验证失败:", verifyError.message); resolve(false); } else { console.log("身份验证成功,继续执行敏感操作"); document.getElementById("verificationModal").style.display = "none"; resolve(true); } }); }); } // 执行敏感操作前验证身份 if (await verifyIdentityBeforeSensitiveOperation()) { // 执行敏感操作 performSensitiveOperation(); } ``` ```typescript try { const { data, error } = await auth.reauthenticate(); if (error) { switch (error.code) { case "user_not_found": console.error("用户未登录,请先登录"); window.location.href = "/login"; break; case "email_not_set": console.error("用户未设置邮箱,无法发送验证码,请先设置邮箱地址"); break; case "phone_not_set": console.error("用户未设置手机号,无法发送验证码,请先设置手机号码"); break; case "resource_exhausted": console.error("发送频率过高,请稍后再试"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; case "permission_denied": console.error("权限不足,请检查安全域名配置"); break; default: console.error("发送验证码失败:", error.message); } } } catch (error) { console.error("网络错误:", error); } ``` ```typescript async function verifyReauthenticationCode(code, newPassword) { try { const { data, error } = await auth.reauthenticate(); if (error) { console.error("发送验证码失败:", error.message); return false; } const { data: verifyData, error: verifyError } = await data.updateUser({ nonce: code, password: newPassword, }); if (verifyError) { console.error("验证失败:", verifyError.code, verifyError.category, verifyError.message); switch (verifyError.code) { case "invalid_argument": // 验证码过期/不正确/参数缺失(如密码强度不足) console.error( verifyError.message || "验证码已过期或不正确,请重新获取" ); break; case "unauthenticated": // Token 失效,需要重新登录 console.error("认证失效,请重新登录"); window.location.href = "/login"; break; case "failed_precondition": // 账号已被绑定等业务校验 console.error(verifyError.message); break; case "not_found": // 用户不存在 console.error("用户不存在,请重新登录"); break; case "unavailable": // 服务端异常 console.error("服务暂不可用,请稍后再试"); break; default: console.error("验证失败:", verifyError.message); } return false; } else { console.log("重新认证成功"); return true; } } catch (error) { console.error("网络错误:", error); return false; } } ``` ## 验证管理 ## verifyOAuth ```typescript async verifyOAuth(params?: VerifyOAuthReq): Promise ``` 验证第三方平台授权回调,完成 OAuth 登录流程。 - 验证第三方平台授权回调,获取用户信息并完成登录,可以配合[signInWithOAuth](#signinwithoauth)使用 - 支持自动从 URL 参数获取授权码和状态参数 - 提供完整的安全验证机制,防止 CSRF 攻击 ```typescript // 在授权回调页面调用,自动获取URL参数 const { data, error } = await auth.verifyOAuth(); if (error) { console.error("授权验证失败:", error.message); } else { console.log("授权成功,用户信息:", data.user); console.log("会话信息:", data.session); // 验证成功后跳转到首页 window.location.href = "/home"; } ``` ```typescript // 手动获取URL参数并验证 const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get("code"); const state = urlParams.get("state"); const { data, error } = await auth.verifyOAuth({ code: code, state: state, provider: "wechat", }); if (error) { console.error("微信授权验证失败:", error.message); } else { console.log("微信授权成功,用户昵称:", data.user?.user_metadata?.nickName); } ``` ```typescript // 第一步:生成授权链接 const { data, error } = await auth.signInWithOAuth({ provider: "wechat", options: { redirectTo: "https://example.com/oauth-callback", state: "wx_oauth_20241204", }, }); if (error) { console.error("获取授权链接失败:", error.message); return; } // 第二步:跳转到授权页面 window.location.href = data.url; // 在回调页面(oauth-callback)中: // 第三步:验证授权结果 const { data: verifyData, error: verifyError } = await auth.verifyOAuth(); if (verifyError) { console.error("授权验证失败:", verifyError.code, verifyError.category, verifyError.message); } else { console.log("授权成功,用户已登录"); window.location.href = "/dashboard"; } ``` ```typescript try { const { data, error } = await auth.verifyOAuth(); if (error) { switch (error.code) { case "invalid_code": console.error("授权码无效或已过期,请重新授权"); break; case "state_mismatch": console.error("状态参数不匹配,可能存在安全风险,请重新授权"); break; case "provider_mismatch": console.error("第三方平台标识不匹配,请检查平台配置"); break; case "failed_precondition": console.error("从第三方获取用户信息失败,请检查平台配置"); break; case "resource_exhausted": console.error("请求频率过高,请稍后重试"); break; case "permission_denied": console.error("权限不足,请检查安全域名配置"); break; case "unreachable": console.error("网络连接失败,请检查网络设置后重试"); break; case "invalid_code": console.error("授权码无效或已过期,请重新授权"); break; case "state_mismatch": console.error("状态参数不匹配,可能存在安全风险,请重新授权"); break; default: console.error("授权验证失败:", error.message); } } else { console.log("授权验证成功"); } } catch (error) { console.error("网络错误:", error); } ``` --- ## verifyOtp ```typescript async verifyOtp(params: VerifyOtpReq): Promise ``` 验证一次性密码(OTP),完成登录或注册流程。 :::info 提示 `短信验证码` 仅支持 `上海` 地域 ::: - 验证通过邮箱或手机号发送的一次性密码(OTP) - 支持邮箱和手机号两种验证方式(二选一) - 验证成功后自动登录用户并返回会话信息 - 适用于注册、登录、密码重置等场景 ```typescript const phone = "13800138000"; // 1.获取验证码 const { data } = await auth.resend({ phone }); // 2.验证验证码 const { data: verifyData, error } = await auth.verifyOtp({ phone: "13800138000", token: "123456", messageId: data.messageId, }); if (error) { console.error("验证失败:", error.message); } else { console.log("验证成功,用户信息:", data: verifyData.user); console.log("会话信息:", data: verifyData.session); } ``` ```typescript const email = "user@example.com"; // 1.获取验证码 const verificationInfo = await auth.getVerification({ email, }); // 2.验证验证码 const { data, error } = await auth.verifyOtp({ email, token: "654321", verificationInfo, }); if (error) { console.error("邮箱验证失败:", error.message); } else { console.log("邮箱验证成功,用户邮箱:", data.user?.email); } ``` ```typescript async function completeRegistration(email, verificationCode) { const { data, error } = await auth.verifyOtp({ email: email, token: verificationCode, }); if (error) { console.error("验证失败:", error.code, error.category, error.message); return false; } else { console.log("注册成功,欢迎加入!"); // 自动登录用户 window.location.href = "/dashboard"; return true; } } // 注册表单提交 document .getElementById("registerForm") .addEventListener("submit", async (e) => { e.preventDefault(); const email = document.getElementById("email").value; const code = document.getElementById("code").value; await completeRegistration(email, code); }); ``` ```typescript try { const { data, error } = await auth.verifyOtp({ phone: "13800138000", token: "wrong_code", }); if (error) { switch (error.code) { case "invalid_code": console.error("验证码错误,请重新输入"); document.getElementById("code").classList.add("error"); break; case "code_expired": console.error("验证码已过期,请重新获取"); document.getElementById("resendBtn").style.display = "block"; break; case "max_attempts_exceeded": console.error("验证次数过多,请稍后再试"); document.getElementById("verifyBtn").disabled = true; break; default: console.error("验证失败:", error.message); } } else { console.log("验证成功"); } } catch (error) { console.error("网络错误:", error); } ``` --- ## resend ```typescript async resend(params: ResendReq): Promise ``` 重新发送验证码到邮箱或手机号。 :::info 提示 `短信验证码` 仅支持 `上海` 地域 ::: - 向用户重新发送邮箱或手机号的验证码 - 支持注册、邮箱变更、手机号变更等场景 - 重新发送后获取新的消息 ID,用于后续验证流程 - 提供频率限制保护,防止恶意重发 ```typescript // 第一次发送验证码 const { data, error } = await auth.signInWithOtp({ phone: "13800138000" }); // 验证码校验回调 let signUpVerify = data.verifyOtp; // 重新发送验证码,获取新的messageId const { data: resendData, error: resendError } = await auth.resend({ phone: "13800138000", type: "signup", }); if (resendError) { console.error("重发验证码失败:", resendError.message); console.error("错误代码:", resendError.code); } else { console.log("验证码已重发,消息ID:", resendData.messageId); // 将新的messageId传入signInWithOtp()或signUp()的callback参数 const verificationCode = "123456"; // 用户输入的验证码 const messageId = resendData.messageId; // 新的messageId // 使用新的messageId进行验证 const { data: loginData, error: loginError } = await signUpVerify({ token: verificationCode, messageId, }); if (loginError) { console.error("验证失败:", loginError.message); } else { console.log("验证成功"); } // 开始倒计时 startCountdown(60); } ``` ```typescript const { data, error } = await auth.resend({ email: "user@example.com", type: "email_change", }); if (error) { console.error("重发邮箱验证码失败:", error.message); } else { console.log("邮箱验证码已重发"); // 显示提示信息 document.getElementById("resendStatus").innerText = "验证码已重新发送到您的邮箱"; } ``` ```typescript let countdown = 0; let countdownInterval; function startCountdown(seconds) { countdown = seconds; const resendBtn = document.getElementById("resendBtn"); resendBtn.disabled = true; resendBtn.innerText = `${countdown}秒后可重发`; countdownInterval = setInterval(() => { countdown--; resendBtn.innerText = `${countdown}秒后可重发`; if (countdown <= 0) { clearInterval(countdownInterval); resendBtn.disabled = false; resendBtn.innerText = "重新发送验证码"; } }, 1000); } async function resendVerificationCode() { const email = document.getElementById("email").value; if (!email) { alert("请输入邮箱地址"); return; } const { data, error } = await auth.resend({ email: email, type: "signup", }); if (error) { alert("重发失败: " + error.message); } else { alert("验证码已重新发送到您的邮箱"); startCountdown(60); // 60秒倒计时 } } // 重发按钮点击事件 document .getElementById("resendBtn") .addEventListener("click", resendVerificationCode); ``` ```typescript try { const { data, error } = await auth.resend({ phone: "13800138000", type: "signup", }); if (error) { switch (error.code) { case "resource_exhausted": console.error("发送频率过高,请稍后再试"); document.getElementById("resendBtn").disabled = true; setTimeout(() => { document.getElementById("resendBtn").disabled = false; }, 60000); // 1分钟后重试 break; case "invalid_argument": console.error("参数格式错误,请检查邮箱或手机号格式"); break; default: console.error("重发失败:", error.message); } } else { console.log("重发成功"); } } catch (error) { console.error("网络错误:", error); } ``` ## 其他工具 ## onAuthStateChange ```typescript onAuthStateChange(callback: OnAuthStateChangeCallback): OnAuthStateChangeResult ``` 监听认证状态变化,实时响应登录、登出、令牌刷新等事件。 - 监听用户认证状态的变化事件 - 支持多种事件类型:登录、登出、令牌刷新、用户信息更新、身份源绑定等 - 返回订阅对象,用于取消监听 - 适用于构建响应式 UI 和状态管理 void", required: true, description: "取消订阅方法", }, ] }, ] }], }] }} > ```typescript const { data } = auth.onAuthStateChange((event, session, info) => { console.log("认证状态变化:", event, session, info); switch (event) { case "INITIAL_SESSION": console.log("初始会话已建立"); if (session) { console.log("用户已登录:", session.user?.id); } else { console.log("用户未登录"); } break; case "SIGNED_IN": console.log("用户登录成功:", session.user?.id); // 更新UI显示 document.getElementById("loginBtn").style.display = "none"; document.getElementById("userInfo").style.display = "block"; document.getElementById("userId").innerText = session.user?.id || ""; break; case "SIGNED_OUT": console.log("用户已登出"); // 更新UI显示 document.getElementById("loginBtn").style.display = "block"; document.getElementById("userInfo").style.display = "none"; break; case "PASSWORD_RECOVERY": console.log("密码已重置"); // 显示密码重置界面 document.getElementById("passwordResetForm").style.display = "block"; break; case "TOKEN_REFRESHED": console.log("令牌已刷新"); break; case "USER_UPDATED": console.log("用户信息已更新"); // 更新用户信息显示 if (session) { document.getElementById("userId").innerText = session.user?.id || ""; document.getElementById("userAvatar").src = session.user?.user_metadata?.avatarUrl || ""; } break; case "BIND_IDENTITY": console.log("身份源绑定结果"); if (!!info.error) { console.log("身份源绑定失败"); } else { console.log("身份源已绑定"); } break; } }); // 取消监听(在组件卸载时调用) // data.subscription.unsubscribe(); ``` ```typescript import { useEffect, useState } from "react"; function AuthStatus() { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const { data } = auth.onAuthStateChange((event, session, info) => { setLoading(false); if (event === "SIGNED_IN" || event === "INITIAL_SESSION") { setUser(session?.user || null); } else if (event === "SIGNED_OUT") { setUser(null); } }); // 清理函数 return () => { data.subscription.unsubscribe(); }; }, []); if (loading) { return
加载中...
; } return (
{user ? (

欢迎,{user.email}

) : (

请先登录

)}
); } ```
```typescript // 路由守卫组件 function RouteGuard({ children }) { const [authenticated, setAuthenticated] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { const { data } = auth.onAuthStateChange((event, session, info) => { setLoading(false); if (event === "SIGNED_IN" || event === "INITIAL_SESSION") { if (session) { setAuthenticated(true); } else { setAuthenticated(false); } } else if (event === "SIGNED_OUT") { setAuthenticated(false); } }); return () => { data.subscription.unsubscribe(); }; }, []); if (loading) { return
检查登录状态...
; } if (!authenticated) { // 未登录,重定向到登录页 window.location.href = "/login"; return
重定向到登录页...
; } return children; } // 使用路由守卫 function App() { return (
); } ```
```typescript // 全局认证状态管理 class AuthManager { constructor() { this.currentUser = null; this.listeners = []; this.setupAuthListener(); } setupAuthListener() { const { data } = auth.onAuthStateChange((event, session, info) => { switch (event) { case "SIGNED_IN": case "INITIAL_SESSION": this.currentUser = session?.user || null; this.notifyListeners(); break; case "SIGNED_OUT": this.currentUser = null; this.notifyListeners(); break; } }); this.unsubscribe = data.subscription.unsubscribe; } addListener(listener) { this.listeners.push(listener); } removeListener(listener) { this.listeners = this.listeners.filter((l) => l !== listener); } notifyListeners() { this.listeners.forEach((listener) => listener(this.currentUser)); } destroy() { if (this.unsubscribe) { this.unsubscribe(); } } } // 创建全局认证管理器 const authManager = new AuthManager(); // 在不同页面中使用 function PageA() { const [user, setUser] = useState(null); useEffect(() => { authManager.addListener(setUser); return () => authManager.removeListener(setUser); }, []); return
{user ? `欢迎,${user.email}` : "请登录"}
; } ```
--- ## getClaims ```typescript async getClaims(): Promise ``` 获取当前访问令牌中的声明信息,用于调试和权限检查。 - 解析当前访问令牌的 JWT 声明信息 - 返回令牌的头部、声明和签名部分 - 用于调试令牌信息、检查权限和验证令牌状态 - 需要用户已登录状态才能获取令牌信息 ", required: true, description: "令牌声明信息", defaultExpand: false, children: [ { name: "iss", type: "string", required: true, description: "issuer", }, { name: "sub", type: "string", required: true, description: "subject", }, { name: "aud", type: "string", required: true, description: "audience", }, { name: "exp", type: "number", required: true, description: "expiration time", }, { name: "iat", type: "number", required: true, description: "issued at", }, { name: "at_hash", type: "string", required: true, description: "access token hash", }, { name: "name", type: "string", required: true, description: "名称", }, { name: "picture", type: "string", required: false, description: "头像URL", }, { name: "email", type: "string", required: false, description: "邮箱", }, { name: "phone_number", type: "string", required: false, description: "手机号", }, { name: "scope", type: "string", required: true, description: "授权范围", }, { name: "project_id", type: "string", required: true, description: "项目ID", }, { name: "provider", type: "string", required: false, description: "第三方平台标识", }, { name: "provider_type", type: "string", required: false, description: "第三方平台类型", }, { name: "groups", type: "string[]", required: false, description: "用户组", }, { name: "meta", type: "Record", required: false, description: "", children: [ { name: "wxOpenId", type: "string", required: false, description: "", }, { name: "wxUnionId", type: "string", required: false, description: "", }, ], }, { name: "user_id", type: "string", required: true, description: "用户ID", }, { name: "roles", type: "string[]", required: true, description: "角色", }, { name: "user_type", type: "string", required: true, description: "用户类型", }, { name: "client_type", type: "string", required: true, description: "客户端类型", }, { name: "is_system_admin", type: "boolean", required: true, description: "是否系统管理员", }, ], }, { name: "header", type: "Record", required: true, description: "令牌头部信息", defaultExpand: false, children: [ { name: "alg", type: "string", required: true, description: "加密算法", }, { name: "kid", type: "string", required: true, description: "令牌ID", }, ], }, { name: "signature", type: "string", required: true, description: "令牌签名", }, ], }, { name: "error", type: "AuthError | null", required: true, description: "错误信息,成功时为 null", }] }] }} > ```typescript const { data, error } = await auth.getClaims(); if (error) { console.error("获取声明失败:", error.message); } else { console.log("令牌声明信息:", data.claims); console.log("令牌头部信息:", data.header); console.log("令牌签名:", data.signature); // 解析用户信息 if (data.claims) { console.log("用户ID:", data.claims.sub); console.log("过期时间:", new Date(data.claims.exp * 1000).toLocaleString()); console.log("签发时间:", new Date(data.claims.iat * 1000).toLocaleString()); console.log("用户角色:", data.claims.role); } } ``` ```typescript async function checkTokenPermissions() { const { data, error } = await auth.getClaims(); if (error) { console.error("获取令牌信息失败:", error.message); return false; } if (data.claims) { const claims = data.claims; // 检查令牌是否过期 const now = Math.floor(Date.now() / 1000); if (claims.exp && claims.exp < now) { console.error("令牌已过期"); return false; } // 检查用户角色 if (claims.role === "admin") { console.log("管理员权限"); return true; } else if (claims.role === "user") { console.log("普通用户权限"); return true; } else { console.error("未知用户角色"); return false; } } return false; } // 检查权限并执行操作 if (await checkTokenPermissions()) { console.log("有权限,继续执行..."); } else { console.log("无权限,操作被拒绝"); } ``` ```typescript async function checkTokenExpiration() { const { data, error } = await auth.getClaims(); if (error) { console.error("检查令牌失败:", error.message); return false; } if (data.claims) { const claims = data.claims; const now = Math.floor(Date.now() / 1000); const expiresAt = claims.exp; if (expiresAt) { const timeLeft = expiresAt - now; const minutesLeft = Math.floor(timeLeft / 60); const secondsLeft = timeLeft % 60; if (timeLeft <= 0) { console.error("令牌已过期,请重新登录"); return false; } else if (timeLeft < 300) { console.warn( `令牌将在 ${minutesLeft}分${secondsLeft}秒后过期,建议刷新令牌` ); return true; } else { console.log(`令牌有效,剩余时间: ${minutesLeft}分${secondsLeft}秒`); return true; } } } return false; } // 定期检查令牌状态 setInterval(async () => { const isValid = await checkTokenExpiration(); if (!isValid) { console.log("令牌已过期,自动刷新..."); await auth.refreshSession(); } }, 60000); // 每分钟检查一次 ``` ## toDefaultLoginPage ```typescript async toDefaultLoginPage(params: authModels.ToDefaultLoginPage): Promise ``` 跳转系统默认登录页,兼容 web 和微信小程序端。 - 支持跳转到系统默认登录页面 - 兼容 Web 端和微信小程序端 - 可配置登录页面的版本和重定向地址 - 支持携带自定义参数跳转
本地调试 如果开发 Web 端应用,由于 \_\_auth 登录页面静态资源存放于[静态网站托管](https://tcb.cloud.tencent.com/dev?envId=#/static-hosting)中,所以在本地调试时需要配置代理才能成功跳转 \_\_auth 登录页面。 推荐使用 [Whistle](https://wproxy.org/docs/getting-started.html) 工具进行代理配置。 \_\_auth 登录页面的访问域名可在[静态网站托管-配置信息-默认域名](https://tcb.cloud.tencent.com/dev?envId=#/static-hosting)中查看。 假设本地启动应用的访问地址为 `http://localhost:3000`,\_\_auth 登录页面的访问域名为`lowcode-xxx-xxx.tcloudbaseapp.com`,则 Whistle 代理的 Rules 配置如下: ``` https://lowcode-xxx-xxx.tcloudbaseapp.com http://localhost:3000 # 应用其他路径代理配置 ``` 运行应用后,通过 `https://lowcode-xxx-xxx.tcloudbaseapp.com` 即可访问应用
```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); await auth.toDefaultLoginPage({ config_version: "env", app_id: "app-xxx", redirect_uri: "xxx", }); ``` ## 错误码 {#error-code} ### 登录错误 | 错误码 | 说明 | | ------------------ | ---------------------------------------------------- | | not_found | 用户不存在 | | password_not_set | 当前用户未设置密码,请使用验证码登录或第三方登录方式 | | invalid_password | 密码不正确 | | user_pending | 该用户未激活 | | user_blocked | 该用户被停用 | | invalid_status | 您已经超过了密码最大重试次数, 请稍后重试 | | invalid_two_factor | 二次验证码不匹配或已过时 | ### 注册错误 | 错误码 | 说明 | | ------------------- | -------------------------------------------- | | failed_precondition | 你输入的手机号或邮箱已被注册,请使用其他号码 | ### 验证码相关错误 | 错误码 | 说明 | | ------------------- | -------------------------------------- | | failed_precondition | 从第三方获取用户信息失败 | | resource_exhausted | 你尝试过于频繁,请稍后重试 | | invalid_argument | 您输入的验证码不正确或已过期 | | aborted | 你尝试的次数过多,请返回首页,稍后重试 | | permission_denied | 您当前的会话已过期,请返回重试 | | captcha_required | 需要输入验证码, 需根据反机器人服务接入 | | captcha_invalid | 验证码不正确, 需根据反机器人服务接入 | ### 其他错误 | 错误码 | 说明 | | ----------- | -------------------------------------- | | unreachable | 网络错误,请检查您的网络连接,稍后重试 | ### 错误描述 | 错误码 | 错误描述 | 说明 | | ----------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | permission_denied | cors permission denied,please check if \{url\} in your client \{env\} domains | 请在”[云开发平台/环境配置/安全来源/安全域名](https://tcb.cloud.tencent.com/dev?envId=#/env/safety-source)”中检查对应\{env\}环境下是否已经配置了安全域名\{url\},配置后 10 分钟生效 | --- ## 完整类型定义 {#complete-type-definition} ### SignInAnonymouslyReq ```typescript // 匿名登录参数 interface SignInAnonymouslyReq { provider_token?: string; // 提供令牌(可选) } ``` ### User ```typescript interface User { id: any; // 用户ID aud: string; // 受众 role: string[]; // 用户角色 email: any; // 邮箱 email_confirmed_at: string; // 邮箱确认时间 phone: any; // 手机号 phone_confirmed_at: string; // 手机号确认时间 confirmed_at: string; // 确认时间 last_sign_in_at: string; // 最后登录时间 app_metadata: { // 应用元数据 provider: any; // 提供商 providers: any[]; // 提供商列表 }; user_metadata: { // 用户元数据 name: any; // 姓名 picture: any; // 头像 username: any; // 用户名 gender: any; // 性别 locale: any; // 地区 uid: any; // 用户ID nickName: any; // 昵称 avatarUrl: any; // 头像URL location: any; // 位置 hasPassword: any; // 是否有密码 }; identities: any; // 身份信息 created_at: string; // 创建时间 updated_at: string; // 更新时间 is_anonymous: boolean; // 是否匿名用户 // 新增字段 recovery_sent_at?: string; // 密码重置发送时间 email_change_sent_at?: string; // 邮箱变更发送时间 phone_change_sent_at?: string; // 手机号变更发送时间 new_email?: string; // 新邮箱地址 new_phone?: string; // 新手机号 } ``` ### Session ```typescript interface Session { access_token: string; // 访问令牌 refresh_token: string; // 刷新令牌 expires_in: number; // 过期时间(秒) token_type: string; // 令牌类型 user: User; // 用户信息 // 新增字段 provider_token?: string; // 第三方平台令牌 provider_refresh_token?: string; // 第三方平台刷新令牌 expires_at?: number; // 过期时间戳 issued_at?: number; // 签发时间戳 scope?: string; // 授权范围 token_id?: string; // 令牌ID } ``` ### AuthError ```typescript // 认证错误类型 interface AuthError extends Error { code: (string & {}) | undefined; // 错误代码 category: AuthErrorCategory; // 错误分类,用于业务逻辑判断 requestId?: string; // 请求ID status: number | undefined; // HTTP状态码 helpMessage?: string; // 帮助信息,提供错误处理建议 retryAfter?: number; // 频率限制时的重试等待秒数 // 常见错误代码 // 400: 客户端错误 // - invalid_argument: 参数格式错误(邮箱或手机号格式不正确) // - invalid_password: 密码格式错误 // - user_not_found: 用户不存在 // - already_exists: 邮箱已存在 // - phone_already_exists: 手机号已存在 // - username_already_exists: 用户名已存在 // - invalid_code: 验证码错误 // - code_expired: 验证码已过期 // - max_attempts_exceeded: 验证次数过多 // - password_too_weak: 密码强度不足 // - invalid_token: 令牌无效 // - token_expired: 令牌已过期 // - state_mismatch: 状态参数不匹配 // - provider_not_supported: 不支持的第三方平台 // - provider_mismatch: 第三方平台标识不匹配 // - failed_precondition: 前置条件失败 // - permission_denied: 权限不足 // - resource_exhausted: 资源耗尽 // - unreachable: 网络连接失败 // - invalid_argument: 参数错误(验证码不正确或已过期) // - not_found: 用户不存在(verify 阶段) // - unauthenticated: Token 校验失败 // - unauthorized_client: client 校验失败 // 500: 服务器错误 // - internal_error: 内部服务器错误 // - service_unavailable: 服务不可用 } // 错误分类枚举 enum AuthErrorCategory { RATE_LIMITED = "RATE_LIMITED", // 频率限制 CAPTCHA_REQUIRED = "CAPTCHA_REQUIRED", // 需要滑块验证码 CAPTCHA_INVALID = "CAPTCHA_INVALID", // 滑块验证码校验失败 INVALID_CREDENTIALS = "INVALID_CREDENTIALS", // 凭据无效(密码错误、Token 失效等) USER_NOT_FOUND = "USER_NOT_FOUND", // 用户不存在 INVALID_PARAMS = "INVALID_PARAMS", // 参数格式错误 PROVIDER_NOT_ENABLED = "PROVIDER_NOT_ENABLED", // 登录方式未开启 MFA_REQUIRED = "MFA_REQUIRED", // 需要多因素认证 SERVICE_ERROR = "SERVICE_ERROR", // 服务端错误 NETWORK_ERROR = "NETWORK_ERROR", // 网络不可达 PRECONDITION_FAILED = "PRECONDITION_FAILED", // 前置条件不满足(账号已绑定等) VERIFICATION_FAILED = "VERIFICATION_FAILED", // 验证码校验失败(过期/不正确/不匹配) UNKNOWN = "UNKNOWN", // 其他未分类错误 } ``` ### CommonRes ```typescript // 通用响应参数 interface CommonRes { data: {}; // 成功时为空对象 error: AuthError | null; // 错误信息,成功时为null } ``` ### SignUpReq ```typescript // 用户注册参数(四步验证流程) interface SignUpReq { email?: string; // 邮箱(可选,与手机号二选一) phone?: string; // 手机号(可选,与邮箱二选一) password: string; // 密码(必填) username?: string; // 用户名称(可选),长度 5-24 位,支持字符中英文、数字、特殊字符(仅支持_-),不支持中文 nickname?: string; // 昵称(可选) avatar_url?: string; // 头像URL(可选) gender?: "MALE" | "FEMALE"; // 性别(可选) } ``` ### SignOutReq ```typescript // 用户登出参数 interface SignOutReq { options?: { // 配置选项(可选) redirectTo?: string; // 登出后的重定向地址 clearStorage?: boolean; // 是否清除本地存储(默认true) }; } ``` ### SignInWithPasswordReq ```typescript // 密码登录参数 interface SignInWithPasswordReq { username?: string; // 用户名称(可选,与邮箱、手机号三选一),长度 5-24 位,支持字符中英文、数字、特殊字符(仅支持_-),不支持中文 email?: string; // 邮箱(可选,与用户名、手机号三选一) phone?: string; // 手机号(可选,与用户名、邮箱三选一) password: string; // 密码(必填) is_encrypt?: boolean; // 是否加密,默认为false } ``` ### SignInWithIdTokenReq ```typescript // ID令牌登录参数 interface SignInWithIdTokenReq { provider?: string; // 第三方平台标识(可选) token: string; // 第三方平台的身份令牌(必填) // 新增字段 provider?: string; // 第三方平台标识(可选,与provider参数重复,保留兼容性) } ``` ### SignInWithOtpReq ```typescript // OTP登录参数 interface SignInWithOtpReq { email?: string; // 邮箱(可选,与手机号二选一) phone?: string; // 手机号(可选,与邮箱二选一) options?: { shouldCreateUser?: boolean; // 如果用户不存在是否创建用户,默认为true }; } ``` ### SignInWithOAuthReq ```typescript // OAuth授权参数 interface SignInWithOAuthReq { provider: string; // 第三方平台标识(必填) options?: { // 配置选项(可选) redirectTo?: string; // 回调地址,默认为当前页面 skipBrowserRedirect?: boolean; // 是否跳转至授权页面,默认为false state?: string; // 状态参数,用于安全验证,默认为随机字符串(格式:prd-{provider}-{随机字符串}) queryParams?: Record; // 额外的查询参数,将合并到授权 URI 中 type?: "sign_in" | "bind_identity"; // 类型(可选),默认为'sign_in', sign_in: 登录,bind_identity: 绑定身份 }; } ``` ### SetSessionReq ```typescript // 会话设置参数 interface SetSessionReq { access_token: string; // 访问令牌(必填) refresh_token: string; // 刷新令牌(必填) } ``` ### VerifyOAuthReq ```typescript // OAuth验证参数 interface VerifyOAuthReq { code?: string; // 授权码(可选,默认从URL参数获取) state?: string; // 状态参数(可选,默认从URL参数获取) provider?: string; // 第三方平台标识(可选,默认从session获取) } ``` ### VerifyOtpReq ```typescript // OTP验证参数 interface VerifyOtpReq { type?: | "sms" | "phone_change" | "signup" | "invite" | "magiclink" | "recovery" | "email_change" | "email"; // 验证类型(可选) email?: string; // 邮箱(可选,与手机号二选一) phone?: string; // 手机号(可选,与邮箱二选一) token: string; // 验证码(必填) messageId: string; // 验证码对应ID(必填) } ``` ### UpdateUserReq ```typescript // 用户信息更新参数 interface UpdateUserReq { email?: string; // 邮箱地址 phone?: string; // 手机号 username?: string; // 用户名称(可选),长度 5-24 位,支持中英文、数字、特殊字符(仅支持_-),不支持中文 description?: string; // 描述(可选) avatar_url?: string; // 头像URL(可选) nickname?: string; // 昵称(可选) gender?: "MALE" | "FEMALE"; // 性别(可选) } ``` ### LinkIdentityReq ```typescript // 身份源绑定参数 interface LinkIdentityReq { provider: string; // 身份源标识(必填) } ``` ### UnlinkIdentityReq ```typescript // 身份源解绑参数 interface UnlinkIdentityReq { provider: string; // 身份源标识(必填) } ``` ### ReauthenticateReq ```typescript // 重新认证参数 interface ReauthenticateReq { // 无入参,使用当前登录用户的信息 } ``` ### ResendReq ```typescript // 重发验证码参数 interface ResendReq { email?: string; // 邮箱(可选,与手机号二选一) phone?: string; // 手机号(可选,与邮箱二选一) type?: "signup" | "email_change" | "phone_change" | "sms"; // 类型(可选) } ``` ### ResendRes ```typescript // 重发验证码响应参数 interface ResendRes { data: { messageId?: string; // 消息ID(验证码ID) }; error: AuthError | null; // 错误信息,成功时为null } ``` ### SignInWithOtpRes ```typescript // OTP登录响应参数 interface SignInWithOtpRes { data: { verifyOtp?: (params: { token: string; messageId?: string; }) => Promise; // 验证码回调函数,支持messageId参数 }; error: AuthError | null; // 错误信息,成功时为null } ``` ### SignUpRes ```typescript // 用户注册响应参数 interface SignUpRes { data: { verifyOtp?: (params: { token: string; messageId?: string; }) => Promise; // 验证码回调函数,支持messageId参数 }; error: AuthError | null; } ``` ### SignInOAuthRes ```typescript // OAuth授权响应参数 interface SignInOAuthRes { data: { url?: string; // 授权页面URL provider?: string; // 第三方平台标识 scopes?: string; // 授权范围 }; error: AuthError | null; // 错误信息,成功时为null } ``` ### LinkIdentityRes ```typescript // 身份源绑定响应参数 interface LinkIdentityRes { data: { provider?: string; // 绑定的身份源标识 type?: "sign_in" | "bind_identity" | ""; // 类型(可选),默认为'sign_in', sign_in: 登录,bind_identity: 绑定身份 }; error: AuthError | null; // 错误信息,成功时为null } ``` ### Identity ```typescript interface Identity { id: string; // 身份源标识 name: string; // 身份源名称 picture: string; // 头像URL } ``` ### GetUserIdentitiesRes ```typescript // 用户身份源获取响应参数 interface GetUserIdentitiesRes { data: { identities?: Array<{ id: string; // 身份源标识 name: string; // 身份源名称 picture: string; // 头像URL }>; }; error: AuthError | null; // 错误信息,成功时为null } ``` ### GetClaimsRes ```typescript // 声明信息获取响应参数 interface GetClaimsRes { data: { // 令牌声明信息 claims?: { iss: string; // issuer sub: string; // subject aud: string; // audience exp: number; // expiration time iat: number; // issued at at_hash: string; // access token hash name: string; // 名称 picture?: string; // 头像URL email?: string; // 邮箱 phone_number?: string; // 手机号 scope: string; // 授权范围 project_id: string; // 项目ID provider?: string; // 第三方平台标识 provider_type?: string; // 第三方平台类型 groups?: string[]; // 用户组 meta?: { wxOpenId?: string; wxUnionId?: string; }; user_id: string; // 用户ID roles: string[]; // 角色 user_type: string; // 用户类型 client_type: string; // 客户端类型 is_system_admin: boolean; // 是否系统管理员 }; // 令牌头部信息 header?: { alg: string; // 加密算法 kid: string; // 令牌ID }; signature?: string; // 令牌签名 }; error: AuthError | null; // 错误信息,成功时为null } ``` ### ResetPasswordForOldReq ```typescript // 通过旧密码重置密码参数 interface ResetPasswordForOldReq { new_password: string; // 新密码(必填) old_password: string; // 旧密码(必填) } ``` ### DeleteMeReq ```typescript // 删除当前用户参数 interface DeleteMeReq { password: string; // 用户密码(必填) } ``` ### SignInRes ```typescript // 登录响应参数 interface SignInRes { data: { user?: User; // 用户信息 session?: Session; // 会话信息 }; error: AuthError | null; // 错误信息,成功时为null } ``` ### GetUserRes ```typescript // 用户信息获取响应参数 interface GetUserRes { data: { user?: User; // 用户详细信息 }; error: AuthError | null; // 错误信息,成功时为null } ``` ### OnAuthStateChangeEvent ```typescript // 认证状态变化事件类型 type OnAuthStateChangeEvent = | "SIGNED_OUT" // 用户已登出 | "SIGNED_IN" // 用户登录成功 | "INITIAL_SESSION" // 初始会话已建立 | "PASSWORD_RECOVERY" // 密码已重置 | "TOKEN_REFRESHED" // 令牌已刷新 | "USER_UPDATED" // 用户信息已更新 | "BIND_IDENTITY"; // 身份源绑定结果 ``` ### OnAuthStateChangeCallback ```typescript // 认证状态变化回调函数类型 type OnAuthStateChangeCallback = ( event: OnAuthStateChangeEvent, session: Session ) => void; ``` ### ResetPasswordForEmailRes ```typescript // 邮箱重置密码响应参数 interface ResetPasswordForEmailRes { data: { updateUser?: (attributes: UpdateUserAttributes) => Promise; // 验证码回调函数,支持新密码参数 }; error: AuthError | null; // 错误信息,成功时为null } ``` ### UpdateUserAttributes ```typescript // 用户属性更新参数 interface UpdateUserAttributes { nonce: string; // 验证码 password: string; // 新密码 } ``` ### ReauthenticateRes ```typescript // 重新认证响应参数 interface ReauthenticateRes { data: { updateUser?: (attributes: UpdateUserAttributes) => Promise; // 验证码回调函数,支持新密码参数 }; error: AuthError | null; // 错误信息,成功时为null } ``` --- # JS SDK(V2)/云存储/未命名文档 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/storage --- hide_table_of_contents: true --- import TabItem from '@theme/TabItem'; import ApiIntro from '../components/ApiIntro'; import ParameterTable from '../components/ApiContainer'; import Tabs from '@theme/Tabs'; # 概述 ## 快速开始 {#quick-start} ```typescript import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", }); // 使用 Supabase 风格的 API const storage = app.storage.from(); // 上传文件 const { data, error } = await storage.upload("images/photo.jpg", file); // 下载文件 const { data: blob } = await storage.download( "cloud://envId.xxx/images/photo.jpg" ); // 创建签名 URL const { data: { signedUrl }, } = await storage.createSignedUrl( "[images/photo.jpg](cloud://envId.xxx/images/photo.jpg)", 3600 ); // 删除文件 await storage.remove([ "[images/photo.jpg](cloud://envId.xxx/images/photo.jpg)", ]); ``` ## API 方法 {#api-method} ### 链式调用 - [from()](#from) - 指定存储桶 - [throwOnError()](#throwerror) - 错误时抛出异常 ### 文件操作 - [upload()](#upload) - 上传文件 - [update()](#update) - 更新文件 - [download()](#download) - 下载文件 - [remove()](#remove) - 删除文件 - [move()](#move) - 移动文件 - [copy()](#copy) - 复制文件 ### URL 管理 - [createSignedUrl()](#createsignedurl) - 创建签名 URL - [createSignedUrls()](#createsignedurls) - 批量创建签名 URL - [getPublicUrl()](#getpublicurl) - 获取公开 URL - [createSignedUploadUrl()](#createsigneduploadurl) - 创建上传签名 URL ### 文件信息 - [info()](#info) - 获取文件信息 - [exists()](#exists) - 检查文件是否存在 --- ## from ```typescript from(bucket?: string): this ``` 指定要操作的存储桶,默认选中环境云存储桶。 **示例:** ```typescript const storage = app.storage.from(); ``` --- ## throwOnError ```typescript throwOnError(): this ``` 设置在发生错误时抛出异常,而不是返回错误对象。 **示例:** ```typescript const storage = app.storage.from().throwOnError(); try { const { data } = await storage.upload("file.txt", file); console.log("上传成功:", data); } catch (error) { console.error("上传失败:", error); } ``` --- ## upload ```typescript upload( path: string, fileBody: FileBody, fileOptions?: FileOptions ): Promise<{ data: { id: string; path: string; fullPath: string }; error: null } | { data: null; error: StorageError }> ``` 上传文件到云存储。 ```typescript // 上传文件 const { data, error } = await app.storage .from() .upload("images/photo.jpg", file); if (error) { console.error("上传失败:", error); } else { console.log("上传成功:", data); console.log("文件 ID:", data.id); console.log("文件路径:", data.path); } ``` ```typescript // 上传文件并设置选项 const { data, error } = await app.storage .from() .upload("images/photo.jpg", file, { cacheControl: "max-age=3600", contentType: "image/jpeg", metadata: { author: "John Doe", uploadedAt: new Date().toISOString(), }, }); if (error) { console.error("上传失败:", error); } else { console.log("上传成功:", data); } ``` --- ## update ```typescript update( path: string, fileBody: FileBody, fileOptions?: FileOptions ): Promise<{ data: { id: string; path: string; fullPath: string }; error: null } | { data: null; error: StorageError }> ``` 更新已存在的文件。 ```typescript // 更新文件内容 const { data, error } = await app.storage .from() .update("images/photo.jpg", newFile); if (error) { console.error("更新失败:", error); } else { console.log("更新成功:", data); } ``` ```typescript // 更新文件并修改元数据 const { data, error } = await app.storage .from() .update("images/photo.jpg", newFile, { cacheControl: "max-age=7200", metadata: { updatedAt: new Date().toISOString(), version: "2.0", }, }); if (error) { console.error("更新失败:", error); } else { console.log("文件已更新:", data.path); } ``` --- ## download ```typescript download( fileId: string, options?: TransformOptions ): Promise<{ data: Blob; error: null } | { data: null; error: StorageError }> ``` 下载文件并返回 Blob 对象。支持图片转换功能。 ```typescript // 下载原始文件 const { data, error } = await app.storage .from() .download("cloud://envId.xxx/images/photo.jpg"); if (data) { // 创建下载链接 const url = URL.createObjectURL(data); const a = document.createElement("a"); a.href = url; a.download = "photo.jpg"; a.click(); } ``` ```typescript // 下载并转换图片为缩略图 const { data: thumbnail, error } = await app.storage .from() .download("cloud://envId.xxx/images/photo.jpg", { width: 300, height: 200, quality: 80, }); if (thumbnail) { // 显示缩略图 const url = URL.createObjectURL(thumbnail); document.getElementById("thumbnail").src = url; } ``` ```typescript // 下载并转换图片格式 const { data: webpImage, error } = await app.storage .from() .download("cloud://envId.xxx/images/photo.jpg", { format: "webp", quality: 90, }); if (webpImage) { console.log("已转换为 WebP 格式"); } ``` --- ## remove ```typescript remove( fileIds: string[] ): Promise<{ data: { id: string }[]; error: null } | { data: null; error: StorageError }> ``` 删除一个或多个文件。 ```typescript // 删除单个文件 const { data, error } = await app.storage .from() .remove(["cloud://envId.xxx/images/photo.jpg"]); if (error) { console.error("删除失败:", error); } else { console.log("删除成功:", data); } ``` ```typescript // 批量删除多个文件 const { data, error } = await app.storage .from() .remove([ "cloud://envId.xxx/images/photo1.jpg", "cloud://envId.xxx/images/photo2.jpg", "cloud://envId.xxx/documents/file.pdf", ]); if (error) { console.error("删除失败:", error); } else { console.log(`成功删除 ${data.length} 个文件`); } ``` --- ## move ```typescript move( fromPath: string, toPath: string ): Promise<{ data: { message: string }; error: null } | { data: null; error: StorageError }> ``` 移动文件到新位置。 ```typescript // 移动文件到新位置 const { data, error } = await app.storage .from() .move("images/old-photo.jpg", "images/archive/photo.jpg"); if (error) { console.error("移动失败:", error); } else { console.log("移动成功:", data.message); } ``` ```typescript // 重命名文件(移动到同一目录下的新名称) const { data, error } = await app.storage .from() .move("images/photo.jpg", "images/new-photo.jpg"); if (error) { console.error("重命名失败:", error); } else { console.log("重命名成功"); } ``` --- ## copy ```typescript copy( fromPath: string, toPath: string ): Promise<{ data: { path: string }; error: null } | { data: null; error: StorageError }> ``` 复制文件到新位置。 ```typescript // 复制文件到新位置 const { data, error } = await app.storage .from() .copy("images/photo.jpg", "images/backup/photo.jpg"); if (error) { console.error("复制失败:", error); } else { console.log("复制成功,新文件路径:", data.path); } ``` ```typescript // 在同一目录下创建副本 const { data, error } = await app.storage .from() .copy("images/photo.jpg", "images/photo-copy.jpg"); if (error) { console.error("创建副本失败:", error); } else { console.log("副本已创建:", data.path); } ``` --- ## createSignedUrl ```typescript createSignedUrl( fileId: string, expiresIn: number, options?: TransformOptions ): Promise<{ data: { signedUrl: string }; error: null } | { data: null; error: StorageError }> ``` 创建一个带签名的临时访问 URL。 ```typescript // 创建 1 小时有效的临时链接 const { data, error } = await app.storage .from() .createSignedUrl("cloud://envId.xxx/images/photo.jpg", 3600); if (error) { console.error("创建失败:", error); } else { console.log("临时链接:", data.signedUrl); // 可以直接使用这个链接访问文件 } ``` ```typescript // 创建缩略图的临时链接 const { data, error } = await app.storage .from() .createSignedUrl("cloud://envId.xxx/images/photo.jpg", 3600, { width: 300, height: 200, quality: 80, }); if (error) { console.error("创建失败:", error); } else { // 使用缩略图链接 document.getElementById("thumbnail").src = data.signedUrl; } ``` ```typescript // 创建 5 分钟有效的短期分享链接 const { data, error } = await app.storage .from() .createSignedUrl("cloud://envId.xxx/images/photo.jpg", 300); if (error) { console.error("创建失败:", error); } else { // 分享这个链接,5 分钟后自动失效 navigator.clipboard.writeText(data.signedUrl); alert("分享链接已复制到剪贴板"); } ``` --- ## createSignedUrls ```typescript createSignedUrls( fileIds: string[], expiresIn: number, ): Promise<{ data: Array<{ path: string; signedUrl: string }>; error: null } | { data: null; error: StorageError }> ``` 批量创建带签名的临时访问 URL。 ```typescript // 批量创建多个文件的临时链接 const { data, error } = await app.storage .from() .createSignedUrls( [ "cloud://envId.xxx/images/photo1.jpg", "cloud://envId.xxx/images/photo2.jpg", "cloud://envId.xxx/images/photo3.jpg", ], 3600 ); if (error) { console.error("创建失败:", error); } else { data.forEach((item) => { console.log(`${item.path}: ${item.signedUrl}`); }); } ``` ```typescript // 批量创建缩略图链接 const { data, error } = await app.storage .from() .createSignedUrls( [ "cloud://envId.xxx/images/photo1.jpg", "cloud://envId.xxx/images/photo2.jpg", "cloud://envId.xxx/images/photo3.jpg", ], 3600 ); if (error) { console.error("创建失败:", error); } else { // 显示所有缩略图 const gallery = document.getElementById("gallery"); data.forEach((item) => { const img = document.createElement("img"); img.src = item.signedUrl; gallery.appendChild(img); }); } ``` --- ## getPublicUrl ```typescript getPublicUrl( fileId: string, options?: TransformOptions ): { data: { publicUrl: string } } ``` 获取文件的公开访问 URL(不带签名)。 ```typescript // 获取文件的公开访问链接 const { data } = await app.storage .from() .getPublicUrl("cloud://envId.xxx/images/photo.jpg"); console.log("公开链接:", data.publicUrl); // 可以直接使用这个链接(如果文件设置为公开访问) ``` ```typescript // 获取缩略图的公开链接 const { data } = await app.storage .from() .getPublicUrl("cloud://envId.xxx/images/photo.jpg", { width: 300, height: 200, quality: 80, }); // 在 img 标签中使用 document.getElementById("thumbnail").src = data.publicUrl; ``` --- ## info ```typescript info( fileId: string ): Promise<{ data: FileInfo; error: null } | { data: null; error: StorageError }> ``` 获取文件的详细信息。 ```typescript // 获取文件详细信息 const { data, error } = await app.storage .from() .info("cloud://envId.xxx/images/photo.jpg"); if (error) { console.error("获取失败:", error); } else { console.log("文件名:", data.name); console.log("文件大小:", data.size, "字节"); console.log("创建时间:", data.created_at); console.log("更新时间:", data.updated_at); console.log("元数据:", data.metadata); } ``` ```typescript // 在界面上显示文件详情 const { data, error } = await app.storage .from() .info("cloud://envId.xxx/documents/report.pdf"); if (data) { const sizeInMB = (data.size / 1024 / 1024).toFixed(2); document.getElementById("fileName").textContent = data.name; document.getElementById("fileSize").textContent = `${sizeInMB} MB`; document.getElementById("createdAt").textContent = new Date( data.created_at ).toLocaleString(); } ``` --- ## exists ```typescript exists( fileId: string ): Promise<{ data: boolean; error: null } | { data: null; error: StorageError }> ``` 检查文件是否存在。 ```typescript // 检查文件是否存在 const { data: exists, error } = await app.storage .from() .exists("cloud://envId.xxx/images/photo.jpg"); if (error) { console.error("检查失败:", error); } else if (exists) { console.log("文件存在"); } else { console.log("文件不存在"); } ``` ```typescript // 上传前检查文件是否已存在 const { data: exists } = await app.storage .from() .exists("cloud://envId.xxx/images/photo.jpg"); if (exists) { // 文件已存在,询问是否覆盖 const shouldOverwrite = confirm("文件已存在,是否覆盖?"); if (shouldOverwrite) { await app.storage.from().update("images/photo.jpg", file); } } else { // 文件不存在,直接上传 await app.storage.from().upload("images/photo.jpg", file); } ``` --- ## createSignedUploadUrl ```typescript createSignedUploadUrl( fileId: string, options?: { upsert?: boolean } ): Promise<{ data: { signedUrl: string; path: string; token: string }; error: null } | { data: null; error: StorageError }> ``` 创建一个用于上传的签名 URL,允许客户端直接上传文件到云存储。 ```typescript // 创建用于上传的签名 URL const { data, error } = await app.storage .from() .createSignedUploadUrl("cloud://envId.xxx/images/photo.jpg"); if (error) { console.error("创建失败:", error); } else { console.log("上传 URL:", data.signedUrl); console.log("上传令牌:", data.token); // 使用这个 URL 直接上传文件 const formData = new FormData(); formData.append("file", file); await fetch(data.signedUrl, { method: "PUT", body: file, headers: { "Content-Type": file.type, }, }); } ``` ```typescript // 在客户端实现直传功能 async function uploadFileDirectly(file) { // 1. 获取签名上传 URL const { data, error } = await app.storage .from() .createSignedUploadUrl(`cloud://envId.xxx/uploads/${file.name}`); if (error) { console.error("获取上传 URL 失败:", error); return; } // 2. 直接上传到云存储 const uploadResponse = await fetch(data.signedUrl, { method: "PUT", body: file, headers: { "Content-Type": file.type, }, }); if (uploadResponse.ok) { console.log("上传成功"); } else { console.error("上传失败"); } } ``` --- ## 类型定义 {#type-definition} ## FileBody ```typescript type FileBody = File | Blob | ArrayBuffer | Buffer | ReadableStream | string; ``` 支持的文件内容类型。 ## FileOptions ```typescript interface FileOptions { cacheControl?: string; // 缓存控制,如 'max-age=3600' contentType?: string; // 文件 MIME 类型,如 'image/jpeg' metadata?: Record; // 自定义元数据 upsert?: boolean; // 是否覆盖已存在的文件 } ``` 文件上传选项。 ## TransformOptions ```typescript interface TransformOptions { width?: number; // 目标宽度 height?: number; // 目标高度 quality?: number; // 图片质量 (1-100) format?: "jpg" | "png" | "webp"; // 输出格式 } ``` 图片转换选项(仅对图片有效)。 ## FileInfo ```typescript interface FileInfo { name: string; // 文件名 id: string; // 文件 ID size: number; // 文件大小(字节) created_at: string; // 创建时间 updated_at: string; // 更新时间 metadata?: Record; // 自定义元数据 } ``` 文件信息对象。 ## StorageError ```typescript interface StorageError { message: string; // 错误消息 statusCode?: number; // HTTP 状态码 } ``` 存储错误对象。 --- ## 迁移指南 {#migration-guide} 如果你正在使用传统的 CloudBase 云存储 API,建议迁移到 Supabase 风格的 API。以下是迁移对照表: ## API 对照表 | 传统 API | Supabase 风格 API | 说明 | | ---------------------- | -------------------------------------- | ------------ | | `app.uploadFile()` | `app.storage.from().upload()` | 上传文件 | | `app.getTempFileURL()` | `app.storage.from().createSignedUrl()` | 获取临时链接 | | `app.deleteFile()` | `app.storage.from().remove()` | 删除文件 | | `app.downloadFile()` | `app.storage.from().download()` | 下载文件 | ## 迁移示例 ### 上传文件 ```typescript // 传统方式 const result = await app.uploadFile({ cloudPath: "images/photo.jpg", filePath: file, }); console.log("文件 ID:", result.fileID); ``` ```typescript // Supabase 风格 const { data, error } = await app.storage .from() .upload("images/photo.jpg", file); if (error) { console.error("上传失败:", error); } else { console.log("文件 ID:", data.id); } ``` ### 获取临时链接 ```typescript // 传统方式 const res = await app.getTempFileURL({ fileList: [ { fileID: "cloud://envId.xxx/images/photo.jpg", maxAge: 3600, }, ], }); const url = res.fileList[0].tempFileURL; ``` ```typescript // Supabase 风格 const { data, error } = await app.storage .from() .createSignedUrl("cloud://envId.xxx/images/photo.jpg", 3600); if (data) { const url = data.signedUrl; } ``` ### 删除文件 ```typescript // 传统方式 const result = await app.deleteFile({ fileList: ["cloud://xxx.jpg"], }); ``` ```typescript // Supabase 风格 const { data, error } = await app.storage .from() .remove(["cloud://envId.xxx/images/photo.jpg"]); ``` ### 下载文件 ```typescript // 传统方式 const result = await app.downloadFile({ fileID: "cloud://xxx.jpg", }); ``` ```typescript // Supabase 风格 const { data: blob, error } = await app.storage .from() .download("cloud://envId.xxx/images/photo.jpg"); if (blob) { // 使用 Blob 对象 const url = URL.createObjectURL(blob); } ``` --- # 最佳实践 ## 1. 错误处理 始终检查错误对象: ```typescript const { data, error } = await app.storage.from().upload("file.txt", file); if (error) { console.error("操作失败:", error.message); // 处理错误 return; } // 使用 data console.log("操作成功:", data); ``` 或者使用 `throwOnError()` 方法: ```typescript try { const { data } = await app.storage .from() .throwOnError() .upload("file.txt", file); console.log("操作成功:", data); } catch (error) { console.error("操作失败:", error); } ``` ## 2. 文件路径规范 - 使用 `/` 分隔目录 - 不要以 `/` 开头 - 使用有意义的目录结构 ```typescript // ✅ 推荐 "images/avatars/user-123.jpg"; "documents/reports/2024/report.pdf"; "uploads/temp/file-abc.txt"; // ❌ 不推荐 "/images/photo.jpg"; // 不要以 / 开头 "photo.jpg"; // 缺少目录结构 ``` ## 3. 图片优化 使用图片转换选项优化加载速度: ```typescript // 获取缩略图 const { data } = await app.storage .from() .createSignedUrl("images/large-photo.jpg", 3600, { width: 300, height: 300, quality: 80, format: "webp", // 使用 WebP 格式减小文件大小 }); ``` ## 4. 批量操作 对于多个文件的操作,使用批量方法提高效率: ```typescript // ✅ 推荐:批量创建签名 URL const { data } = await app.storage .from() .createSignedUrls(["image1.jpg", "image2.jpg", "image3.jpg"], 3600); // ❌ 不推荐:逐个创建 for (const path of paths) { await app.storage.from().createSignedUrl(path, 3600); } ``` ## 5. 缓存控制 合理设置缓存控制头: ```typescript // 静态资源:长期缓存 await app.storage.from().upload("images/logo.png", file, { cacheControl: "max-age=31536000", // 1 年 }); // 动态内容:短期缓存 await app.storage.from().upload("data/report.json", file, { cacheControl: "max-age=300", // 5 分钟 }); // 不缓存 await app.storage.from().upload("temp/data.txt", file, { cacheControl: "no-cache", }); ``` --- # 常见问题 ## 1. 如何处理大文件上传? 对于大文件,建议使用 `createSignedUploadUrl()` 实现客户端直传: ```typescript const { data } = await app.storage .from() .createSignedUploadUrl("large-file.zip"); // 使用 fetch 上传大文件 await fetch(data.signedUrl, { method: "PUT", body: largeFile, }); ``` ## 2. 如何实现断点续传? CloudBase 云存储暂不直接支持断点续传,建议将大文件分片上传: ```typescript async function uploadLargeFile(file, chunkSize = 5 * 1024 * 1024) { const chunks = Math.ceil(file.size / chunkSize); for (let i = 0; i < chunks; i++) { const start = i * chunkSize; const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end); await app.storage.from().upload(`temp/file-${i}`, chunk); } // 合并分片(需要云函数支持) } ``` ## 3. 如何设置文件访问权限? 文件访问权限需要在云开发控制台的"权限控制/策略管理"中配置。 ## 4. 支持哪些图片格式转换? 支持 JPG、PNG、WebP 格式之间的相互转换。 ## 5. 临时链接的最长有效期是多久? 建议不超过 7 天(604800 秒)。 --- # 相关资源 - [云存储 HTTP API 文档](/http-api/storage/云存储) - [云开发控制台](https://tcb.cloud.tencent.com/dev) - [Supabase Storage 文档](https://supabase.com/docs/guides/storage) --- # JS SDK(V2)/云函数 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/functions ## callFunction ```typescript callFunction(object: Object): Promise ``` ### 1. 接口描述 接口功能:执行云函数 ### 2. 输入参数
函数型云托管 额外可以传参数,传入 `type:'cloudrun'` 参数后,将调用函数型云托管 服务 ", description: "HTTP 请求头", }, { name: "data", type: "object", description: "HTTP 请求体", }, ] } ]} /> ### 3. 输出参数 ### 4. 示例代码 ```javascript import cloudbase from "@cloudbase/js-sdk"; //初始化SDK实例 const app = cloudbase.init({ env: "xxxx-yyy", }); app .callFunction({ name: "test", data: { a: 1 }, }) .then((res) => { const result = res.result; //云函数执行结果 }); ``` ```ts import cloudbase from "@cloudbase/js-sdk"; //初始化SDK实例 const app = cloudbase.init({ env: "xxxx-yyy", }); app .callFunction({ name: "test", // 函数型云托管 参数 type: "cloudrun", method: "POST", path: "/abc", data: { key1: "test value 1", key2: "test value 2", }, }) .then((res) => { const result = res.result; //云函数执行结果 }); ``` --- # JS SDK(V2)/云托管 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/cloudrun ## callContainer ```typescript callContainer(object: Object): Promise ``` ### 1. 接口描述 接口功能:调用云托管服务 ### 2. 输入参数 ", description: "HTTP 请求头", }, { name: "data", type: "object", description: "HTTP 请求体", }, ] } ]} /> ### 3. 输出参数 > 服务执行报错,将通过异常抛出 ### 4. 示例代码 ```javascript import cloudbase from "@cloudbase/js-sdk"; //初始化SDK实例 const app = cloudbase.init({ env: "xxxx-yyy", }); app .callContainer({ name: "helloworld", method: "POST", path: "/abc", header: { "Content-Type": "application/json; charset=utf-8", }, data: { key1: "test value 1", key2: "test value 2", }, }) .then((res) => { console.log(res); }); ``` --- # JS SDK(V2)/未命名文档 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/apis --- hide_table_of_contents: true --- import TabItem from '@theme/TabItem'; import ApiIntro from '../components/ApiIntro'; import ParameterTable from '../components/ApiContainer'; import Tabs from '@theme/Tabs'; # APIs APIs 提供了一种灵活的方式来调用 [V2 版本的 APIs 连接器](https://tcb.cloud.tencent.com/dev?envId=#/apis/datasource/connector/detail)。V2 版本的 APIs 创建方式请参考[说明文档](https://cloud.tencent.com/document/product/1301/126222#790fb2d9-c2d4-41d5-a3f5-afdb71cdd63c) :::info 提示 v3 版本在调用通用 APIs 时,使用的是 [`HTTP API`](/http-api//basic/overview) 的开放能力。 ::: --- ## APIs 使用参考 ```typescript interface apis { [apisLabel: string]: { [ method: "request" | "post" | "get" | "head" | "patch" | "delete" | "put" ]: ( callApiOptions: ICallApiOptions, opts?: IRequestOptions ) => Promise; }; } ``` APIs 模块支持以下 HTTP 方法: | 方法名 | 描述 | 示例 | | ------- | -------------- | ----------------------------------------- | | get | GET 请求 | apis["apisLabel"].get(callApiOptions) | | post | POST 请求 | apis["apisLabel"].post(callApiOptions) | | put | PUT 请求 | apis["apisLabel"].put(callApiOptions) | | delete | DELETE 请求 | apis["apisLabel"].delete(callApiOptions) | | head | HEAD 请求 | apis["apisLabel"].head(callApiOptions) | | patch | PATCH 请求 | apis["apisLabel"].patch(callApiOptions) | | request | 自定义方法请求 | apis["apisLabel"].request(callApiOptions) | :::tip 提示 apisLabel 是 APIs 标识,在创建 V2 版本的 [APIs 连接器](<(https://tcb.cloud.tencent.com/dev?envId=#/apis/datasource/connector/detail)>)时生成,可以在连接器的基本信息中查看 ::: ', description: '自定义请求头', }, { name: 'body', type: 'Record | string', description: '自定义请求体', }, { name: 'token', type: 'string', description: '访问令牌,默认为当前已登录用户的令牌', }, ], }, { name: 'opts', type: 'IRequestOptions', description: '请求对象参数,一般不用传入,包含url、method、headers、body,作为定制化需求的补充,会覆盖 callApiOptions 中的同名参数', } ], output: [ { name: 'res', type: "ResponseObject['data']", description: 'HTTP 接口返回的 data 内容', }, ], }} > ```typescript import cloudbase from "@cloudbase/js-sdk"; // 初始化 CloudBase 应用 const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境ID region: "ap-shanghai", // 地域,默认为上海 accessKey: "", // 填入生成的 Publishable Key, }); // 获取 apis 实例 const apis = app.apis; ``` ```typescript // 调用名为 "apisLabel" APIs 连接器 的 methodLabel 方法,使用 POST 调用 async function getUserProfile(userId: string) { try { const result = await apis["apisLabel"].post({ path: "methodLabel", body: { userId }, }); console.log("调用成功:", result); return result; } catch (error) { console.error("调用失败:", error); throw error; } } // 调用名为 "apisLabel" APIs 连接器 的 methodLabel 方法,使用 GET 调用 async function getProductList(category: string) { try { const result = await apis["apisLabel"].get({ path: "methodLabel", body: { category }, }); return result; } catch (error) { console.error("调用失败:", error); throw error; } } ``` ```typescript // 自定义请求配置 async function customApiCall() { const result = await apis["apisLabel"].request({ method: "PUT", // 指定 HTTP 方法 path: "methodLabel", // API 路径 headers: { "X-Custom-Header": "value", }, body: { data: "custom data", }, }); return result; } // 使用不同的 HTTP 方法 async function multiMethodExample() { // POST 请求 const createResult = await apis["apisLabel"].post({ path: "methodLabel", body: { name: "New Item" }, }); // GET 请求 const getResult = await apis["apisLabel"].get({ path: "methodLabel", }); // DELETE 请求 const deleteResult = await apis["apisLabel"].delete({ path: "methodLabel", }); } ``` ```typescript async function safeApiCall() { try { const result = await apis["apisLabel"].post({ path: 'methodLabel' body: { data: "test" }, }); return result; } catch (error) { // 处理 API 调用错误 if (error.code === "INVALID_PARAMS") { console.error("参数错误:", error.msg); } else { console.error("API 调用失败:", error); } throw error; } } ``` --- --- # JS SDK(V2)/AI/未命名文档 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/ai --- hide_table_of_contents: true --- import TabItem from '@theme/TabItem'; import ApiIntro from '../components/ApiIntro'; import ParameterTable from '../components/ApiContainer'; import Tabs from '@theme/Tabs'; # 概述 提供云开发 AI 接入能力,快速接入大模型和 Agent。 --- ## 基础使用示例 {#basic-usage-examples} `Publishable Key` 可前往 [云开发平台/API Key 配置](https://tcb.cloud.tencent.com/dev#/env/apikey) 中生成 #### 类型声明 ```ts function ai(): AI; ``` #### 返回值 返回新创建的 [AI](#ai) 实例。 ```typescript import cloudbase from "@cloudbase/js-sdk"; // 初始化 const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境ID region: "ap-shanghai", // 地域,默认为上海 accessKey: "", // 填入生成的 Publishable Key }); // 如果填入了 accessKey,则不需要此步骤 await app.auth.signInAnonymously(); const ai = app.ai(); ``` ```typescript // 基础文本生成示例 async function generateText() { const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", messages: [{ role: "user", content: "你好,请你介绍一下李白" }], }); console.log("生成的文本:", result.text); console.log("消耗的token:", result.usage); } ``` ```typescript // 流式文本生成示例 async function streamText() { const model = ai.createModel("cloudbase"); const result = await model.streamText({ model: "hy3", messages: [{ role: "user", content: "1+1等于多少" }], }); for await (let chunk of result.textStream) { console.log("收到文本块:", chunk); } } ``` ```typescript // Agent 对话示例 async function chatWithAgent() { const res = await ai.bot.sendMessage({ botId: "botId-xxx", msg: "你好,请介绍一下你自己", history: [], }); for await (let text of res.textStream) { console.log("Agent 回复:", text); } } ``` --- ## AI 用于创建 AI 模型的类。 ### createModel ```typescript function createModel(model: string): ChatModel; ``` 创建指定的 AI 模型。 - 创建一个新的 AI 模型实例 - 返回一个实现了 ChatModel 抽象类的模型实例 - 该实例提供 AI 生成文本相关能力 ```typescript const model = ai.createModel("cloudbase"); ``` --- ### bot 挂载了 Bot 类的实例,上面集合了一系列与 Agent 交互的方法。具体可参考 [Bot 类](#bot-1) 的详细文档。 #### 使用示例 ```ts const agentList = await ai.bot.list({ pageNumber: 1, pageSize: 10 }); ``` --- ### registerFunctionTool ```typescript function registerFunctionTool(functionTool: FunctionTool): void; ``` 注册函数工具。在进行大模型调用时,可以告知大模型可用的函数工具,当大模型的响应被解析为工具调用时,会自动调用对应的函数工具。 ", description: "必填参数列表", required: true } ] } ] } ], output: [ { name: "void", type: "undefined", description: "无返回值", required: true } ] }} > ```typescript // 定义获取天气的工具 const getWeatherTool = { name: "get_weather", description: "返回某个城市的天气信息。调用示例:get_weather({city: '北京'})", fn: ({ city }) => `${city}的天气是:秋高气爽!!!`, parameters: { type: "object", properties: { city: { type: "string", description: "要查询的城市", }, }, required: ["city"], }, }; // 注册工具 ai.registerFunctionTool(getWeatherTool); // 使用工具进行对话 const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", tools: [getWeatherTool], messages: [ { role: "user", content: "请告诉我北京的天气状况", }, ], }); console.log(result.text); ``` ```typescript // 多工具集成系统 class ToolIntegrationSystem { private registeredTools: Map = new Map(); // 注册多个工具 registerTools(tools: FunctionTool[]): void { tools.forEach((tool) => { ai.registerFunctionTool(tool); this.registeredTools.set(tool.name, tool); console.log(`✓ 工具注册成功: ${tool.name}`); }); } // 获取所有可用工具 getAvailableTools(): FunctionTool[] { return Array.from(this.registeredTools.values()); } // 智能对话助手 async smartAssistant(question: string): Promise { const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", tools: this.getAvailableTools(), messages: [ { role: "user", content: question, }, ], }); return result.text; } // 工具使用统计 getToolUsageStats(): Map { const stats = new Map(); // 在实际应用中,这里可以记录工具调用次数 return stats; } } // 定义多个工具 const tools = [ { name: "get_weather", description: "获取城市天气信息", fn: ({ city }) => `${city}的天气:晴,25℃`, parameters: { type: "object", properties: { city: { type: "string", description: "城市名称" } }, required: ["city"], }, }, { name: "calculate", description: "执行数学计算", fn: ({ expression }) => `计算结果:${eval(expression)}`, parameters: { type: "object", properties: { expression: { type: "string", description: "数学表达式" } }, required: ["expression"], }, }, { name: "search_info", description: "搜索信息", fn: ({ keyword }) => `搜索结果:关于${keyword}的信息`, parameters: { type: "object", properties: { keyword: { type: "string", description: "搜索关键词" } }, required: ["keyword"], }, }, ]; // 使用示例 const toolSystem = new ToolIntegrationSystem(); toolSystem.registerTools(tools); // 智能对话 const answer = await toolSystem.smartAssistant( "计算一下 15*8+20 的结果,并告诉我北京的天气" ); console.log("助手回答:", answer); ``` ```typescript // 动态工具管理系统 class DynamicToolManager { private toolRegistry: Map = new Map(); // 动态注册工具 registerTool(tool: FunctionTool): void { ai.registerFunctionTool(tool); this.toolRegistry.set(tool.name, tool); console.log(`工具注册: ${tool.name}`); } // 动态卸载工具 unregisterTool(toolName: string): boolean { if (this.toolRegistry.has(toolName)) { this.toolRegistry.delete(toolName); console.log(`工具卸载: ${toolName}`); return true; } return false; } // 根据上下文动态选择工具 async contextualAssistant( context: string, question: string ): Promise { const relevantTools = this.selectRelevantTools(context, question); const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", tools: relevantTools, messages: [ { role: "system", content: `当前上下文:${context}`, }, { role: "user", content: question, }, ], }); return result.text; } // 根据上下文选择相关工具 private selectRelevantTools( context: string, question: string ): FunctionTool[] { const tools = Array.from(this.toolRegistry.values()); // 简单的关键词匹配算法 const keywords = [...context.split(" "), ...question.split(" ")]; return tools.filter((tool) => { return keywords.some((keyword) => tool.description.toLowerCase().includes(keyword.toLowerCase()) ); }); } // 热更新工具 updateTool(toolName: string, updatedTool: FunctionTool): boolean { if (this.toolRegistry.has(toolName)) { this.unregisterTool(toolName); this.registerTool(updatedTool); console.log(`工具更新: ${toolName}`); return true; } return false; } // 获取工具列表 listTools(): string[] { return Array.from(this.toolRegistry.keys()); } } // 使用示例 const toolManager = new DynamicToolManager(); // 注册初始工具 toolManager.registerTool({ name: "weather", description: "获取天气信息", fn: ({ city }) => `${city}天气:晴`, parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"], }, }); // 动态添加新工具 toolManager.registerTool({ name: "translate", description: "翻译文本", fn: ({ text, to }) => `${text} 翻译为 ${to}:示例翻译结果`, parameters: { type: "object", properties: { text: { type: "string" }, to: { type: "string" }, }, required: ["text", "to"], }, }); // 上下文感知对话 const answer = await toolManager.contextualAssistant( "天气和翻译相关的问题", "把'你好'翻译成英语,并告诉我上海的天气" ); console.log("智能回答:", answer); // 查看可用工具 console.log("可用工具:", toolManager.listTools()); ``` --- ## ChatModel 这个抽象类描述了 AI 生文模型类提供的接口。 ### generateText ```typescript function generateText(data: BaseChatModelInput): Promise<{ rawResponses: Array; text: string; messages: Array; usage: Usage; error?: unknown; }>; ``` 调用大模型生成文本。 - 向大模型发送消息并获取生成的文本响应 - 支持完整的对话上下文管理 - 返回详细的调用信息和 token 消耗统计 ", description: "消息列表,包含对话上下文", required: true }, { name: "temperature", type: "number", description: "采样温度,控制输出的随机性,范围 0-1", required: false }, { name: "topP", type: "number", description: "温度采样,考虑概率质量为 top_p 的标记的结果", required: false }, { name: "tools", type: "Array", description: "大模型可用的工具列表", required: false }, { name: "toolChoice", type: "string", description: "指定大模型选择工具的方式", required: false }, { name: "maxSteps", type: "number", description: "请求大模型的最大次数", required: false }, { name: "onStepFinish", type: "(prop: IOnStepFinish) => unknown", description: "当对大模型的一次请求完成时,出发的回调函数。", required: false } ] } ], output: [ { name: "Promise", type: "object", description: "大模型生成文本的响应结果", required: true, children: [ { name: "text", type: "string", description: "大模型生成的文本内容", required: true }, { name: "rawResponses", type: "Array", description: "大模型的完整返回值,包含详细数据", required: true }, { name: "messages", type: "Array", description: "本次调用的完整消息列表", required: true }, { name: "usage", type: "Usage", description: "本次调用消耗的 token 统计", required: true }, { name: "error", type: "unknown", description: "调用过程中产生的错误信息", required: false } ] } ] }} > ```typescript const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", messages: [{ role: "user", content: "你好,请你介绍一下李白" }], }); console.log("生成的文本:", result.text); console.log("消耗的token:", result.usage); ``` ```typescript const model = ai.createModel("cloudbase"); // 多轮对话示例 const messages = [ { role: "user", content: "你好,我想了解中国古代文学" }, { role: "assistant", content: "中国古代文学源远流长,从诗经楚辞到唐诗宋词,都有着丰富的文化内涵。", }, { role: "user", content: "那你能介绍一下唐诗的特点吗?" }, ]; const result = await model.generateText({ model: "hy3", messages: messages, temperature: 0.7, topP: 0.9, }); console.log("唐诗介绍:", result.text); ``` ```typescript // 注册天气查询工具 ai.registerFunctionTool({ name: "get_weather", description: "查询城市天气信息", fn: ({ city }) => `${city}的天气是:晴朗,25℃`, parameters: { type: "object", properties: { city: { type: "string", description: "要查询的城市名称", }, }, required: ["city"], }, }); const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", messages: [{ role: "user", content: "请告诉我北京的天气状况" }], tools: [getWeatherTool], toolChoice: "auto", }); console.log("大模型响应:", result.text); ``` --- ### streamText ```typescript function streamText(data: BaseChatModelInput): Promise; ``` 以流式调用大模型生成文本。 - 流式调用时,生成的文本及其他响应数据会通过 SSE 返回,该接口的返回值对 SSE 做了不同程度的封装,开发者能根据实际需求获取到文本流和完整数据流。 - 以流式方式调用大模型生成文本,支持实时获取增量内容 ", description: "消息列表,包含对话上下文", required: true }, { name: "temperature", type: "number", description: "采样温度,控制输出的随机性,范围 0-1", required: false }, { name: "topP", type: "number", description: "温度采样,考虑概率质量为 top_p 的标记的结果", required: false }, { name: "tools", type: "Array", description: "大模型可用的工具列表", required: false }, { name: "toolChoice", type: "string", description: "指定大模型选择工具的方式", required: false }, { name: "maxSteps", type: "number", description: "请求大模型的最大次数", required: false }, { name: "onStepFinish", type: "(prop: IOnStepFinish) => unknown", description: "当对大模型的一次请求完成时,出发的回调函数。", required: false } ] } ], output: [ { name: "Promise", type: "StreamTextResult", description: "流式文本生成的结果,包含文本流和数据流", required: true, children: [ { name: "textStream", type: "ReadableStream", description: "以流式返回的大模型生成文本,可获取到生成的增量文本", required: true }, { name: "dataStream", type: "ReadableStream", description: "以流式返回的大模型响应数据,可获取到生成的增量数据", required: true, children: [ { name: "DataChunk", type: "object", description: "流式调用返回的数据块", required: true, defaultExpand: false, children: [ { name: "choices", type: "Array", description: "模型选择结果", required: true, children: [ { name: "finish_reason", type: "string", description: "模型终止推断的原因", required: true }, { name: "delta", type: "ChatModelMessage", description: "本次请求的消息增量", required: true } ] }, { name: "usage", type: "Usage", description: "本次请求消耗的 token", required: true }, { name: "rawResponse", type: "unknown", description: "大模型返回的原始回复", required: true } ] } ] }, { name: "messages", type: "Promise", description: "本次调用的完整消息列表", required: true }, { name: "usage", type: "Promise", description: "本次调用消耗的 token", required: true }, { name: "error", type: "unknown", description: "调用过程中产生的错误", required: false } ] } ] }} > ```typescript const model = ai.createModel("cloudbase"); const result = await model.streamText({ model: "hy3", messages: [{ role: "user", content: "你好,请你介绍一下李白" }], }); // 获取文本流 for await (let chunk of result.textStream) { console.log("收到文本块:", chunk); } // 1 // 加 // 1 // 的结果 // 是 // 2 // 。 // 获取数据流 for await (let data of result.dataStream) { console.log("收到数据块:", data); } // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} ``` ```typescript // 在网页中实时显示流式内容 async function displayStreamingText(question: string) { const model = ai.createModel("cloudbase"); const result = await model.streamText({ model: "hy3", messages: [{ role: "user", content: question }], }); const outputElement = document.getElementById("output"); outputElement.innerHTML = ""; for await (let chunk of result.textStream) { outputElement.innerHTML += chunk; // 滚动到最新内容 outputElement.scrollTop = outputElement.scrollHeight; } } // 使用示例 displayStreamingText("请详细介绍一下唐诗的发展历程"); ``` ```typescript // 监控流式生成进度 async function monitorStreamProgress(question: string) { const model = ai.createModel("cloudbase"); const result = await model.streamText({ model: "hy3", messages: [{ role: "user", content: question }], }); let totalChunks = 0; let startTime = Date.now(); for await (let chunk of result.textStream) { totalChunks++; console.log(`第${totalChunks}个文本块: ${chunk}`); } const endTime = Date.now(); console.log( `生成完成,共${totalChunks}个文本块,耗时${endTime - startTime}ms` ); // 获取完整消息和token统计 const messages = await result.messages; const usage = await result.usage; console.log("完整对话:", messages); console.log("Token消耗:", usage); } ``` --- ## Bot 用于与 Agent 交互的类。 ### get ```typescript function get(props: { botId: string }): Promise; ``` 获取某个 Agent 的信息。 - 根据 Agent ID 获取详细的 Agent 信息 - 返回 Agent 的基本配置、欢迎语、头像等完整信息 ```typescript const res = await ai.bot.get({ botId: "botId-xxx" }); console.log("Agent 信息:", res); ``` ```typescript // 应用启动时加载 Agent 配置 async function initializeAgent(botId: string) { try { const agentInfo = await ai.bot.get({ botId }); // 设置应用界面 document.title = `${agentInfo.name} - AI助手`; // 显示欢迎信息 const welcomeElement = document.getElementById("welcome"); welcomeElement.innerHTML = `
${agentInfo.name}

${agentInfo.name}

${agentInfo.introduction}

`; // 设置聊天背景 if (agentInfo.background) { document.body.style.backgroundImage = `url(${agentInfo.background})`; } return agentInfo; } catch (error) { console.error("加载 Agent 配置失败:", error); throw error; } } // 使用示例 initializeAgent("bot-27973647"); ```
```typescript // 管理多个 Agent 的配置 class AgentManager { private agents: Map = new Map(); async loadAgent(botId: string) { if (this.agents.has(botId)) { return this.agents.get(botId); } const agentInfo = await ai.bot.get({ botId }); this.agents.set(botId, agentInfo); return agentInfo; } getAgentList() { return Array.from(this.agents.values()); } async switchAgent(botId: string) { const agentInfo = await this.loadAgent(botId); // 更新界面显示 this.updateUI(agentInfo); return agentInfo; } private updateUI(agentInfo: BotInfo) { // 更新界面元素 console.log("切换到 Agent:", agentInfo.name); } } // 使用示例 const agentManager = new AgentManager(); await agentManager.loadAgent("bot-27973647"); await agentManager.loadAgent("bot-27973648"); ```
--- ### list ```typescript function list(props: { name: string; introduction: string; information: string; enable: boolean; pageSize: number; pageNumber: number; }): Promise; ``` 批量获取多个 Agent 的信息。 - 查询和筛选可用的 Agent 列表 - 支持分页查询和条件筛选 - 返回 Agent 的基本信息和配置详情 - 适用于构建 Agent 选择器、Agent 管理界面等应用 ", description: "Agent 列表", required: true, children: [ { name: "botId", type: "string", description: "Agent ID", required: true }, { name: "name", type: "string", description: "Agent 名称", required: true }, { name: "introduction", type: "string", description: "Agent 简介", required: true }, { name: "welcomeMessage", type: "string", description: "Agent 欢迎语", required: true }, { name: "avatar", type: "string", description: "Agent 头像链接", required: true }, { name: "background", type: "string", description: "Agent 聊天背景图链接", required: true }, { name: "isNeedRecommend", type: "boolean", description: "Agent 回答后是否推荐问题", required: true }, { name: "type", type: "string", description: "Agent 类型", required: true } ] } ] } ] }} > ```typescript const agentList = await ai.bot.list({ pageNumber: 1, pageSize: 10, name: "", enable: true, information: "", introduction: "", }); console.log("Agent 总数:", agentList.total); console.log("Agent 列表:", agentList.botList); ``` ```typescript // 根据条件筛选 Agent async function searchAgents(keyword: string, page: number = 1) { const result = await ai.bot.list({ pageNumber: page, pageSize: 20, name: keyword, enable: true, information: keyword, introduction: keyword, }); console.log(`找到 ${result.total} 个匹配的 Agent`); result.botList.forEach((agent) => { console.log(`- ${agent.name}: ${agent.introduction}`); }); return result; } // 使用示例 await searchAgents("翻译"); // 搜索翻译相关的 Agent await searchAgents("客服", 2); // 搜索客服相关的 Agent,第二页 ``` ```typescript // 构建 Agent 选择器界面 class AgentSelector { private currentPage: number = 1; private pageSize: number = 12; async loadAgents(keyword: string = "") { const result = await ai.bot.list({ pageNumber: this.currentPage, pageSize: this.pageSize, name: keyword, enable: true, information: keyword, introduction: keyword, }); this.displayAgents(result.botList); this.updatePagination(result.total); return result; } private displayAgents(agents: any[]) { const container = document.getElementById("agent-list"); container.innerHTML = ""; agents.forEach((agent) => { const agentCard = document.createElement("div"); agentCard.className = "agent-card"; agentCard.innerHTML = ` ${agent.name}

${agent.name}

${agent.introduction}

`; container.appendChild(agentCard); }); } private updatePagination(total: number) { const pagination = document.getElementById("pagination"); const totalPages = Math.ceil(total / this.pageSize); pagination.innerHTML = ` 第 ${this.currentPage} 页,共 ${totalPages} 页 `; } async nextPage() { this.currentPage++; await this.loadAgents(); } async prevPage() { if (this.currentPage > 1) { this.currentPage--; await this.loadAgents(); } } selectAgent(botId: string) { console.log("选择的 Agent ID:", botId); // 开始与选中的 Agent 对话 this.startChat(botId); } private startChat(botId: string) { // 实现与 Agent 开始对话的逻辑 console.log("开始与 Agent 对话:", botId); } } // 使用示例 const selector = new AgentSelector(); await selector.loadAgents(); // 加载第一页 Agent // 搜索特定类型的 Agent await selector.loadAgents("写作助手"); ```
--- ### sendMessage ```typescript function sendMessage(props: { botId: string; msg: string; history: Array<{ role: string; content: string; }>; }): Promise; ``` 与 Agent 进行对话。 - 响应会通过 SSE 返回,该接口的返回值对 SSE 做了不同程度的封装,开发者能根据实际需求获取到文本流和完整数据流。 - 支持多轮对话上下文管理 - 返回流式响应,支持实时获取 Agent 回复 - 适用于构建聊天机器人、智能助手等应用 ", description: "在此次对话前发生的聊天记录", required: true, children: [ { name: "object", type: "Object", description: "聊天记录", required: true, children: [{ name: "role", type: "string", description: "本聊天信息的发送角色(user/assistant)", required: true }, { name: "content", type: "string", description: "本聊天信息的内容", required: true }] } ] } ] } ], output: [ { name: "Promise", type: "StreamResult", description: "流式对话结果,包含文本流和数据流", required: true, children: [ { name: "textStream", type: "AsyncIterable", description: "以流式返回的 Agent 生成文本,可获取到生成的增量文本", required: true }, { name: "dataStream", type: "AsyncIterable", description: "以流式返回的 Agent 生成数据,可获取到生成的增量数据", required: true, children: [ { name: "AgentStreamChunk", type: "object", description: "Agent 流式对话返回的数据块", required: true, defaultExpand: false, children: [ { name: "created", type: "number", description: "对话时间戳", required: true }, { name: "record_id", type: "string", description: "对话记录ID", required: true }, { name: "model", type: "string", description: "大模型类型", required: true }, { name: "version", type: "string", description: "大模型版本", required: true }, { name: "type", type: "string", description: "回复类型:text(主要回答内容)、thinking(思考过程)、search(查询结果)、knowledge(知识库)", required: true }, { name: "role", type: "string", description: "对话角色,响应中固定为 assistant", required: true }, { name: "content", type: "string", description: "对话内容", required: true }, { name: "finish_reason", type: "string", description: "对话结束标志,continue(对话未结束)、stop(对话结束)", required: true }, { name: "reasoning_content", type: "string", description: "深度思考内容(仅deepseek-r1不为空字符串)", required: true }, { name: "usage", type: "object", description: "token使用量", required: true, children: [ { name: "prompt_tokens", type: "number", description: "表示prompt的tokens数,多次返回中保持不变", required: true }, { name: "completion_tokens", type: "number", description: "回答的token总数,在流式返回中表示到目前为止所有completion的tokens总数,多次返回中持续累加", required: true }, { name: "total_tokens", type: "number", description: "表示prompt_tokens和completion_tokens之和", required: true } ] }, { name: "knowledge_base", type: "string[]", description: "对话中使用到的知识库", required: true }, { name: "search_info", type: "object", description: "搜索结果信息,需要开启联网查询", required: true, children: [ { name: "search_results", type: "object[]", description: "搜索引文信息", required: true, children: [ { name: "index", type: "string", description: "搜索引文序号", required: true }, { name: "title", type: "string", description: "搜索引文标题", required: true }, { name: "url", type: "string", description: "搜索引文链接", required: true } ] } ] } ] } ] } ] } ] }} > ```typescript const res = await ai.bot.sendMessage({ botId: "botId-xxx", history: [{ content: "你是李白。", role: "user" }], msg: "你好", }); // 获取文本流 for await (let text of res.textStream) { console.log("Agent 回复:", text); } // 获取数据流 for await (let data of res.dataStream) { console.log("详细数据:", data); } ``` ```typescript // 多轮对话示例 class ChatSession { private history: Array<{ role: string; content: string }> = []; async sendMessage(botId: string, message: string) { const res = await ai.bot.sendMessage({ botId: botId, history: this.history, msg: message, }); let fullResponse = ""; // 实时显示回复 for await (let chunk of res.textStream) { fullResponse += chunk; this.updateDisplay(fullResponse); } // 记录对话历史 this.history.push({ role: "user", content: message }); this.history.push({ role: "assistant", content: fullResponse }); return fullResponse; } private updateDisplay(text: string) { const chatArea = document.getElementById("chat-area"); chatArea.innerHTML = text; chatArea.scrollTop = chatArea.scrollHeight; } clearHistory() { this.history = []; } getHistory() { return this.history; } } // 使用示例 const chatSession = new ChatSession(); await chatSession.sendMessage("botId-xxx", "你好,请介绍一下你自己"); await chatSession.sendMessage("botId-xxx", "你能帮我写一首诗吗?"); ``` ```typescript // 构建实时聊天应用 class ChatApp { private botId: string; private conversationId: string; constructor(botId: string) { this.botId = botId; this.setupEventListeners(); } private setupEventListeners() { const sendBtn = document.getElementById("send-btn"); const input = document.getElementById("message-input") as HTMLInputElement; sendBtn.addEventListener("click", () => { this.sendMessage(input.value); input.value = ""; }); input.addEventListener("keypress", (e) => { if (e.key === "Enter") { this.sendMessage(input.value); input.value = ""; } }); } async sendMessage(message: string) { if (!message.trim()) return; // 显示用户消息 this.displayMessage("user", message); try { const res = await ai.bot.sendMessage({ botId: this.botId, history: this.getChatHistory(), msg: message, }); // 显示流式回复 const assistantDiv = this.displayMessage("assistant", ""); for await (let chunk of res.textStream) { assistantDiv.innerHTML += chunk; assistantDiv.scrollIntoView({ behavior: "smooth" }); } } catch (error) { this.displayMessage("error", "发送消息失败: " + error.message); } } private displayMessage(role: string, content: string): HTMLDivElement { const chatContainer = document.getElementById("chat-container"); const messageDiv = document.createElement("div"); messageDiv.className = `message ${role}`; messageDiv.innerHTML = content; chatContainer.appendChild(messageDiv); chatContainer.scrollTop = chatContainer.scrollHeight; return messageDiv; } private getChatHistory() { const messages = document.querySelectorAll(".message"); const history = []; for (let message of messages) { const role = message.classList.contains("user") ? "user" : "assistant"; const content = message.innerHTML; history.push({ role, content }); } return history; } } // 使用示例 const chatApp = new ChatApp("botId-xxx"); ``` --- ### getChatRecords ```typescript function getChatRecords(props: { botId: string; sort: string; pageSize: number; pageNumber: number; }): Promise; ``` 获取聊天记录。 - 获取指定 Agent 的历史聊天记录 - 支持分页查询和排序 - 返回完整的对话历史信息 - 适用于构建聊天历史查看功能 ", description: "对话记录列表", required: true, children: [ { name: 'record', type: 'Record', description: '对话记录', required: true, defaultExpand: false, children: [ { name: "botId", type: "string", description: "Agent ID", required: true }, { name: "recordId", type: "string", description: "对话ID,由系统生成", required: true }, { name: "role", type: "string", description: "对话中的角色(user/assistant)", required: true }, { name: "content", type: "string", description: "对话内容", required: true }, { name: "conversation", type: "string", description: "用户标识", required: true }, { name: "type", type: "string", description: "对话数据类型", required: true }, { name: "image", type: "string", description: "对话生成的图片链接", required: true }, { name: "triggerSrc", type: "string", description: "对话发起来源", required: true }, { name: "replyTo", type: "string", description: "对话回复的记录ID", required: true }, { name: "createTime", type: "string", description: "对话时间", required: true } ] } ] } ] } ] }} > ```typescript const records = await ai.bot.getChatRecords({ botId: "botId-xxx", pageNumber: 1, pageSize: 10, sort: "asc", }); console.log("总记录数:", records.total); console.log("记录列表:", records.recordList); ``` ```typescript // 分页查询聊天记录 class ChatHistoryManager { private botId: string; private currentPage: number = 1; private pageSize: number = 20; constructor(botId: string) { this.botId = botId; } async loadPage(pageNumber: number) { const records = await ai.bot.getChatRecords({ botId: this.botId, pageNumber: pageNumber, pageSize: this.pageSize, sort: "desc", // 按时间倒序排列 }); this.currentPage = pageNumber; return records; } async nextPage() { return this.loadPage(this.currentPage + 1); } async previousPage() { if (this.currentPage > 1) { return this.loadPage(this.currentPage - 1); } return null; } getTotalPages(totalRecords: number) { return Math.ceil(totalRecords / this.pageSize); } } // 使用示例 const historyManager = new ChatHistoryManager("botId-xxx"); const firstPage = await historyManager.loadPage(1); console.log("第1页记录:", firstPage.recordList); const secondPage = await historyManager.nextPage(); console.log("第2页记录:", secondPage.recordList); ``` ```typescript // 构建聊天历史查看界面 class ChatHistoryViewer { private botId: string; constructor(botId: string) { this.botId = botId; this.setupUI(); } private setupUI() { const historyContainer = document.getElementById("history-container"); const prevBtn = document.getElementById("prev-btn"); const nextBtn = document.getElementById("next-btn"); const pageInfo = document.getElementById("page-info"); prevBtn.addEventListener("click", () => this.previousPage()); nextBtn.addEventListener("click", () => this.nextPage()); } async loadHistory(pageNumber: number = 1) { const records = await ai.bot.getChatRecords({ botId: this.botId, pageNumber: pageNumber, pageSize: 10, sort: "desc", }); this.displayRecords(records.recordList); this.updatePageInfo(pageNumber, records.total); } private displayRecords(records: any[]) { const container = document.getElementById("records-container"); container.innerHTML = ""; records.forEach((record) => { const recordDiv = document.createElement("div"); recordDiv.className = "chat-record"; recordDiv.innerHTML = `
${record.role} ${record.createTime}
${record.content}
`; container.appendChild(recordDiv); }); } private updatePageInfo(currentPage: number, total: number) { const pageInfo = document.getElementById("page-info"); const totalPages = Math.ceil(total / 10); pageInfo.textContent = `第${currentPage}页,共${totalPages}页,总计${total}条记录`; } async nextPage() { const currentPage = parseInt( document.getElementById("page-info").textContent.match(/第(\d+)页/)[1] ); await this.loadHistory(currentPage + 1); } async previousPage() { const currentPage = parseInt( document.getElementById("page-info").textContent.match(/第(\d+)页/)[1] ); if (currentPage > 1) { await this.loadHistory(currentPage - 1); } } } // 使用示例 const historyViewer = new ChatHistoryViewer("botId-xxx"); await historyViewer.loadHistory(1); ```
--- ### sendFeedback ```typescript function sendFeedback(props: { userFeedback: IUserFeedback; botId: string; }): Promise; ``` 发送对某条聊天记录的反馈信息。 - 对指定的聊天记录进行评价和反馈 - 支持评分、评论、标签等多种反馈方式 - 帮助改进 Agent 的回答质量 - 适用于构建用户反馈系统 ```typescript const res = await ai.bot.sendFeedback({ userFeedback: { botId: "botId-xxx", recordId: "recordId-xxx", comment: "非常棒", rating: 5, tags: ["优美"], aiAnswer: "落英缤纷", input: "来个成语", type: "upvote", }, botId: "botId-xxx", }); console.log("反馈发送成功"); ``` ```typescript // 构建用户反馈系统 class FeedbackSystem { private botId: string; constructor(botId: string) { this.botId = botId; this.setupFeedbackUI(); } private setupFeedbackUI() { const feedbackButtons = document.querySelectorAll(".feedback-btn"); feedbackButtons.forEach((button) => { button.addEventListener("click", (e) => { const recordId = e.target.dataset.recordId; const type = e.target.dataset.type; // upvote 或 downvote this.showFeedbackDialog(recordId, type); }); }); } private showFeedbackDialog(recordId: string, type: string) { const dialog = document.getElementById("feedback-dialog"); const submitBtn = document.getElementById("submit-feedback"); const ratingInput = document.getElementById("rating") as HTMLInputElement; const commentInput = document.getElementById( "comment" ) as HTMLTextAreaElement; const tagsInput = document.getElementById("tags") as HTMLInputElement; dialog.style.display = "block"; submitBtn.onclick = async () => { await this.submitFeedback(recordId, type, { rating: parseInt(ratingInput.value), comment: commentInput.value, tags: tagsInput.value.split(",").map((tag) => tag.trim()), }); dialog.style.display = "none"; }; } async submitFeedback( recordId: string, type: string, feedback: { rating: number; comment: string; tags: string[]; } ) { try { await ai.bot.sendFeedback({ userFeedback: { botId: this.botId, recordId: recordId, comment: feedback.comment, rating: feedback.rating, tags: feedback.tags, aiAnswer: this.getCurrentAnswer(), input: this.getCurrentQuestion(), type: type, }, botId: this.botId, }); this.showSuccessMessage("感谢您的反馈!"); } catch (error) { this.showErrorMessage("反馈发送失败: " + error.message); } } private getCurrentAnswer(): string { // 获取当前对话的回答内容 const answerElement = document.querySelector( ".assistant-message:last-child" ); return answerElement?.textContent || ""; } private getCurrentQuestion(): string { // 获取当前对话的问题内容 const questionElement = document.querySelector(".user-message:last-child"); return questionElement?.textContent || ""; } private showSuccessMessage(message: string) { const notification = document.createElement("div"); notification.className = "notification success"; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => notification.remove(), 3000); } private showErrorMessage(message: string) { const notification = document.createElement("div"); notification.className = "notification error"; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => notification.remove(), 5000); } } // 使用示例 const feedbackSystem = new FeedbackSystem("botId-xxx"); ``` ```typescript // 批量处理用户反馈 class BatchFeedbackProcessor { private botId: string; private feedbackQueue: Array<{ recordId: string; type: string; rating: number; comment: string; tags: string[]; }> = []; constructor(botId: string) { this.botId = botId; this.startProcessing(); } addFeedback( recordId: string, type: string, rating: number, comment: string, tags: string[] ) { this.feedbackQueue.push({ recordId, type, rating, comment, tags }); } private async startProcessing() { setInterval(async () => { if (this.feedbackQueue.length > 0) { await this.processBatch(); } }, 5000); // 每5秒处理一次 } private async processBatch() { const batch = this.feedbackQueue.splice(0, 10); // 每次处理最多10个反馈 const promises = batch.map((feedback) => { return ai.bot.sendFeedback({ userFeedback: { botId: this.botId, recordId: feedback.recordId, comment: feedback.comment, rating: feedback.rating, tags: feedback.tags, aiAnswer: "", // 需要根据实际情况获取 input: "", // 需要根据实际情况获取 type: feedback.type, }, botId: this.botId, }); }); try { await Promise.all(promises); console.log(`成功处理 ${batch.length} 个反馈`); } catch (error) { console.error("批量处理反馈失败:", error); // 将失败的反馈重新加入队列 this.feedbackQueue.unshift(...batch); } } getQueueSize() { return this.feedbackQueue.length; } } // 使用示例 const feedbackProcessor = new BatchFeedbackProcessor("botId-xxx"); // 添加多个反馈 feedbackProcessor.addFeedback("record-1", "upvote", 5, "回答很棒", [ "准确", "详细", ]); feedbackProcessor.addFeedback("record-2", "downvote", 2, "回答不够准确", [ "不准确", ]); console.log("当前队列大小:", feedbackProcessor.getQueueSize()); ``` --- ### getFeedback ```typescript function getFeedback(props: { botId: string; type: string; sender: string; senderFilter: string; minRating: number; maxRating: number; from: number; to: number; pageSize: number; pageNumber: number; }): Promise; ``` 获取已存在的反馈信息。 - 查询指定 Agent 的用户反馈记录 - 支持多种过滤条件:时间范围、评分范围、用户过滤等 - 返回分页的反馈结果和统计信息 - 适用于构建反馈分析和管理系统 ", description: "反馈查询结果列表", required: true, children: [ { name: 'feedback', type: 'Feedback', description: '反馈查询结果', require: true, children: [ { name: "recordId", type: "string", description: "对话记录ID", required: true }, { name: "type", type: "string", description: "用户反馈类型,upvote(点赞)或 downvote(点踩)", required: true }, { name: "botId", type: "string", description: "Agent ID", required: true }, { name: "comment", type: "string", description: "用户评论", required: true }, { name: "rating", type: "number", description: "用户评分", required: true }, { name: "tags", type: "string[]", description: "用户反馈的标签数组", required: true }, { name: "input", type: "string", description: "用户输入的问题", required: true }, { name: "aiAnswer", type: "string", description: "Agent 的回答", required: true } ] } ] }, { name: "total", type: "number", description: "反馈总数", required: true } ] } ] }} > ```typescript const res = await ai.bot.getFeedback({ botId: "botId-xxx", from: 0, to: 0, maxRating: 4, minRating: 3, pageNumber: 1, pageSize: 10, sender: "user-a", senderFilter: "include", type: "upvote", }); console.log("反馈总数:", res.total); console.log("反馈列表:", res.feedbackList); ``` ```typescript // 构建反馈分析系统 class FeedbackAnalyzer { private botId: string; constructor(botId: string) { this.botId = botId; } async analyzeFeedback(timeRange: { from: number; to: number }) { const feedback = await ai.bot.getFeedback({ botId: this.botId, from: timeRange.from, to: timeRange.to, minRating: 1, maxRating: 5, pageNumber: 1, pageSize: 1000, // 获取足够多的数据进行分析 sender: "", senderFilter: "include", type: "", }); return this.generateReport(feedback.feedbackList); } private generateReport(feedbackList: any[]) { const report = { total: feedbackList.length, upvotes: feedbackList.filter((f) => f.type === "upvote").length, downvotes: feedbackList.filter((f) => f.type === "downvote").length, averageRating: this.calculateAverageRating(feedbackList), commonTags: this.getCommonTags(feedbackList), ratingDistribution: this.getRatingDistribution(feedbackList), topComments: this.getTopComments(feedbackList), }; return report; } private calculateAverageRating(feedbackList: any[]): number { const ratings = feedbackList.map((f) => f.rating).filter((r) => r > 0); return ratings.length > 0 ? ratings.reduce((a, b) => a + b, 0) / ratings.length : 0; } private getCommonTags(feedbackList: any[]): string[] { const tagCounts = new Map(); feedbackList.forEach((feedback) => { feedback.tags?.forEach((tag) => { tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1); }); }); return Array.from(tagCounts.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map((entry) => entry[0]); } private getRatingDistribution(feedbackList: any[]): Record { const distribution = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }; feedbackList.forEach((feedback) => { if (feedback.rating >= 1 && feedback.rating <= 5) { distribution[feedback.rating]++; } }); return distribution; } private getTopComments(feedbackList: any[]): any[] { return feedbackList .filter((f) => f.comment && f.comment.length > 0) .sort((a, b) => b.rating - a.rating) .slice(0, 5); } } // 使用示例 const analyzer = new FeedbackAnalyzer("botId-xxx"); const report = await analyzer.analyzeFeedback({ from: Date.now() - 7 * 24 * 60 * 60 * 1000, // 最近7天 to: Date.now(), }); console.log("反馈分析报告:", report); ``` ```typescript // 构建反馈管理界面 class FeedbackManager { private botId: string; constructor(botId: string) { this.botId = botId; this.setupUI(); } private setupUI() { const filterForm = document.getElementById("feedback-filter-form"); const resultsContainer = document.getElementById("feedback-results"); const exportBtn = document.getElementById("export-feedback"); filterForm.addEventListener("submit", (e) => { e.preventDefault(); this.loadFeedback(this.getFilterValues()); }); exportBtn.addEventListener("click", () => { this.exportFeedback(); }); } private getFilterValues() { const formData = new FormData( document.getElementById("feedback-filter-form") ); return { type: formData.get("type") as string, minRating: parseInt(formData.get("minRating") as string), maxRating: parseInt(formData.get("maxRating") as string), from: this.parseDate(formData.get("fromDate") as string), to: this.parseDate(formData.get("toDate") as string), pageNumber: 1, pageSize: 50, }; } private parseDate(dateString: string): number { return dateString ? new Date(dateString).getTime() : 0; } async loadFeedback(filters: any) { try { const res = await ai.bot.getFeedback({ botId: this.botId, ...filters, sender: "", senderFilter: "include", }); this.displayResults(res.feedbackList, res.total); } catch (error) { this.showError("加载反馈失败: " + error.message); } } private displayResults(feedbackList: any[], total: number) { const container = document.getElementById("feedback-results"); container.innerHTML = ""; const summary = document.createElement("div"); summary.className = "feedback-summary"; summary.innerHTML = `共找到 ${total} 条反馈记录`; container.appendChild(summary); feedbackList.forEach((feedback) => { const feedbackCard = this.createFeedbackCard(feedback); container.appendChild(feedbackCard); }); } private createFeedbackCard(feedback: any): HTMLDivElement { const card = document.createElement("div"); card.className = `feedback-card ${feedback.type}`; card.innerHTML = ` `; return card; } private async exportFeedback() { const res = await ai.bot.getFeedback({ botId: this.botId, from: 0, to: Date.now(), minRating: 1, maxRating: 5, pageNumber: 1, pageSize: 10000, sender: "", senderFilter: "include", type: "", }); const csv = this.convertToCSV(res.feedbackList); this.downloadCSV(csv, `feedback_${this.botId}_${Date.now()}.csv`); } private convertToCSV(feedbackList: any[]): string { const headers = ["类型", "评分", "问题", "回答", "评论", "标签", "时间"]; const rows = feedbackList.map((f) => [ f.type, f.rating, f.input, f.aiAnswer, f.comment, f.tags?.join(";"), new Date(f.createTime).toLocaleString(), ]); return [headers, ...rows] .map((row) => row.map((cell) => `"${cell}"`).join(",")) .join("\n"); } private downloadCSV(csv: string, filename: string) { const blob = new Blob([csv], { type: "text/csv" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } private showError(message: string) { const errorDiv = document.createElement("div"); errorDiv.className = "error-message"; errorDiv.textContent = message; document.getElementById("feedback-results").appendChild(errorDiv); } } // 使用示例 const feedbackManager = new FeedbackManager("botId-xxx"); ``` --- ### uploadFiles ```typescript function uploadFiles(props: { botId: string; fileList: Array<{ fileId: string; fileName: string; type: "file"; }>; }): Promise; ``` 将云存储中的文件上传至 Agent,用于进行文档聊天。 - 支持将云存储中的文件上传到指定的 Agent - 上传后的文件可用于文档聊天功能 - 支持批量上传多个文件 - 适用于构建文档问答、文件分析等应用场景 ", description: "要上传的文件列表", required: true, children: [ { name: "fileId", type: "string", description: "云存储文件 ID,格式如 cloud://xxx.docx", required: true }, { name: "fileName", type: "string", description: "文件名,用于显示和识别", required: true }, { name: "type", type: "string", description: "文件类型,目前只支持 'file'", required: true } ] } ] } ], output: [ { name: "Promise", type: "void", description: "无返回值", required: true } ] }} > ```typescript // 上传单个文件到指定 Agent await ai.bot.uploadFiles({ botId: "botId-xxx", fileList: [ { fileId: "cloud://document.docx", fileName: "document.docx", type: "file", }, ], }); console.log("文件上传成功"); ``` ```typescript // 批量上传多个文件 async function uploadAndChat( botId: string, files: Array<{ fileId: string; fileName: string }> ) { // 上传文件 await ai.bot.uploadFiles({ botId: botId, fileList: files.map((file) => ({ ...file, type: "file" as const, })), }); console.log("文件上传完成,开始文档聊天"); // 基于上传的文件进行聊天 const res = await ai.bot.sendMessage({ botId: botId, msg: "请分析这些文档的主要内容", files: files.map((f) => f.fileId), }); // 流式输出回答 for await (let text of res.textStream) { console.log(text); } } // 使用示例 await uploadAndChat("your-bot-id", [ { fileId: "cloud://report.pdf", fileName: "季度报告.pdf" }, { fileId: "cloud://data.xlsx", fileName: "数据表格.xlsx" }, ]); ``` ```typescript // 构建文档问答系统 class DocumentQASystem { private botId: string; private uploadedFiles: string[] = []; constructor(botId: string) { this.botId = botId; } async uploadDocuments(files: Array<{ fileId: string; fileName: string }>) { await ai.bot.uploadFiles({ botId: this.botId, fileList: files.map((file) => ({ ...file, type: "file" as const, })), }); this.uploadedFiles.push(...files.map((f) => f.fileId)); console.log(`已上传 ${files.length} 个文件`); } async askQuestion(question: string): Promise { const res = await ai.bot.sendMessage({ botId: this.botId, msg: question, files: this.uploadedFiles, }); let fullAnswer = ""; for await (let text of res.textStream) { fullAnswer += text; } return fullAnswer; } async interactiveChat() { console.log("文档问答系统已启动,输入 'exit' 退出"); while (true) { const question = await this.getUserInput("请输入问题: "); if (question.toLowerCase() === "exit") { console.log("再见!"); break; } console.log("思考中..."); const answer = await this.askQuestion(question); console.log("回答:", answer); console.log("---"); } } private getUserInput(prompt: string): Promise { return new Promise((resolve) => { // 在实际应用中,这里可以是浏览器输入框或命令行输入 const input = prompt; resolve(input); }); } } // 使用示例 const qaSystem = new DocumentQASystem("doc-bot-id"); // 上传文档 await qaSystem.uploadDocuments([ { fileId: "cloud://manual.pdf", fileName: "产品手册.pdf" }, { fileId: "cloud://faq.docx", fileName: "常见问题.docx" }, ]); // 开始交互式问答 await qaSystem.interactiveChat(); ``` --- ### getRecommendQuestions ```typescript function getRecommendQuestions(props: { botId: string; name: string; introduction: string; agentSetting: string; msg: string; history: Array<{ role: string; content: string; }>; }): Promise; ``` 获取推荐的问题,基于对话上下文智能生成相关问题建议。 - 根据当前对话内容和历史记录智能推荐相关问题 - 支持流式返回,实时获取推荐问题 - 适用于构建智能对话助手、聊天机器人等应用 - 帮助用户发现更多相关话题,提升对话体验 ", description: "历史对话记录", required: true, children: [ { name: "role", type: "string", description: "对话角色,user 或 assistant", required: true }, { name: "content", type: "string", description: "对话内容", required: true } ] } ] } ], output: [ { name: "Promise", type: "StreamResult", description: "流式返回的推荐问题结果", required: true, children: [ { name: "textStream", type: "AsyncIterable", description: "以流式返回的推荐问题文本", required: true }, { name: "dataStream", type: "AsyncIterable", description: "以流式返回的详细对话数据", required: true, children: [ { name: 'AgentStreamChunk', type: 'object', description: 'Agent 流式对话返回的数据块', required: true, defaultExpand: false, children: [ { name: 'created', type: 'number', description: '对话时间戳', required: true, }, { name: 'record_id', type: 'string', description: '对话记录ID', required: true, }, { name: 'model', type: 'string', description: '大模型类型', required: true, }, { name: 'version', type: 'string', description: '大模型版本', required: true, }, { name: 'type', type: 'string', description: '回复类型:text(主要回答内容)、thinking(思考过程)、search(查询结果)、knowledge(知识库)', required: true, }, { name: 'role', type: 'string', description: '对话角色,响应中固定为 assistant', required: true, }, { name: 'content', type: 'string', description: '对话内容', required: true, }, { name: 'finish_reason', type: 'string', description: '对话结束标志,continue(对话未结束)、stop(对话结束)', required: true, }, { name: 'reasoning_content', type: 'string', description: '深度思考内容(仅deepseek-r1不为空字符串)', required: true, }, { name: 'usage', type: 'object', description: 'token使用量', required: true, children: [ { name: 'prompt_tokens', type: 'number', description: '表示prompt的tokens数,多次返回中保持不变', required: true, }, { name: 'completion_tokens', type: 'number', description: '回答的token总数,在流式返回中表示到目前为止所有completion的tokens总数,多次返回中持续累加', required: true, }, { name: 'total_tokens', type: 'number', description: '表示prompt_tokens和completion_tokens之和', required: true, }, ], }, { name: 'knowledge_base', type: 'string[]', description: '对话中使用到的知识库', required: true, }, { name: 'search_info', type: 'object', description: '搜索结果信息,需要开启联网查询', required: true, children: [ { name: 'search_results', type: 'object[]', description: '搜索引文信息', required: true, children: [ { name: 'index', type: 'string', description: '搜索引文序号', required: true, }, { name: 'title', type: 'string', description: '搜索引文标题', required: true, }, { name: 'url', type: 'string', description: '搜索引文链接', required: true, }, ], }, ], }, ], }, ], } ] } ] }} > ```typescript // 获取推荐问题 const res = await ai.bot.getRecommendQuestions({ botId: "botId-xxx", name: "智能助手", introduction: "我是一个智能对话助手", agentSetting: "专业、友好的AI助手", msg: "你好,我想了解人工智能", history: [ { content: "你是谁啊", role: "user" }, { content: "我是一个智能助手,可以帮助你解答各种问题", role: "assistant" }, ], }); // 流式获取推荐问题 for await (let question of res.textStream) { console.log("推荐问题:", question); } ``` ```typescript // 构建智能对话助手,自动推荐相关问题 class SmartChatAssistant { private botId: string; private conversationHistory: Array<{ role: string; content: string }> = []; constructor(botId: string) { this.botId = botId; } async sendMessage(message: string): Promise { // 添加用户消息到历史记录 this.conversationHistory.push({ role: "user", content: message }); // 发送消息并获取回答 const response = await ai.bot.sendMessage({ botId: this.botId, msg: message, history: this.conversationHistory, }); let assistantReply = ""; for await (let text of response.textStream) { assistantReply += text; } // 添加助手回答到历史记录 this.conversationHistory.push({ role: "assistant", content: assistantReply, }); return assistantReply; } async getRecommendedQuestions(): Promise { if (this.conversationHistory.length === 0) { return []; } const latestMessage = this.conversationHistory[this.conversationHistory.length - 1].content; const res = await ai.bot.getRecommendQuestions({ botId: this.botId, name: "智能助手", introduction: "智能对话助手", agentSetting: "", msg: latestMessage, history: this.conversationHistory, }); const questions: string[] = []; for await (let question of res.textStream) { questions.push(question); } return questions; } async interactiveChat() { console.log( "智能助手已启动,输入 'exit' 退出,输入 'suggest' 获取推荐问题" ); while (true) { const userInput = await this.getUserInput("请输入: "); if (userInput.toLowerCase() === "exit") { console.log("再见!"); break; } if (userInput.toLowerCase() === "suggest") { console.log("正在为您推荐相关问题..."); const questions = await this.getRecommendedQuestions(); console.log("推荐问题:"); questions.forEach((q, i) => console.log(`${i + 1}. ${q}`)); continue; } console.log("思考中..."); const reply = await this.sendMessage(userInput); console.log("助手回答:", reply); // 自动推荐相关问题 console.log("\n您可能还想了解:"); const questions = await this.getRecommendedQuestions(); questions.slice(0, 3).forEach((q, i) => console.log(`${i + 1}. ${q}`)); console.log("---"); } } private getUserInput(prompt: string): Promise { return new Promise((resolve) => { // 实际应用中这里可以是浏览器输入框 const input = prompt; resolve(input); }); } } // 使用示例 const assistant = new SmartChatAssistant("smart-bot-id"); await assistant.interactiveChat(); ``` ```typescript // 电商客服场景下的推荐问题应用 class EcommerceCustomerService { private botId: string; constructor(botId: string) { this.botId = botId; } async handleCustomerQuery( query: string ): Promise<{ answer: string; suggestions: string[] }> { const response = await ai.bot.sendMessage({ botId: this.botId, msg: query, history: [], }); let answer = ""; for await (let text of response.textStream) { answer += text; } // 获取相关推荐问题 const recommendRes = await ai.bot.getRecommendQuestions({ botId: this.botId, name: "电商客服", introduction: "专业的电商客服助手", agentSetting: "专注于解答电商相关问题", msg: query, history: [ { role: "user", content: query }, { role: "assistant", content: answer }, ], }); const suggestions: string[] = []; for await (let suggestion of recommendRes.textStream) { suggestions.push(suggestion); } return { answer, suggestions }; } // 处理常见电商场景 async handleProductInquiry(productName: string) { const query = `我想了解 ${productName} 这个产品`; const result = await this.handleCustomerQuery(query); console.log("客服回答:", result.answer); console.log("\n您可能还想了解:"); result.suggestions.forEach((suggestion, index) => { console.log(`${index + 1}. ${suggestion}`); }); return result; } async handleOrderInquiry(orderId: string) { const query = `查询订单 ${orderId} 的状态`; const result = await this.handleCustomerQuery(query); console.log("订单状态:", result.answer); console.log("\n相关服务:"); result.suggestions.forEach((suggestion, index) => { console.log(`${index + 1}. ${suggestion}`); }); return result; } } // 使用示例 const customerService = new EcommerceCustomerService("ecommerce-bot-id"); // 处理产品咨询 await customerService.handleProductInquiry("iPhone 15"); // 处理订单查询 await customerService.handleOrderInquiry("ORD20231226001"); ``` --- ### createConversation ```typescript function createConversation(props: { botId: string; title?: string; }): Promise; ``` 创建与 Agent 的新对话,建立独立的对话会话。 - 为指定的 Agent 创建新的对话会话 - 支持自定义对话标题,便于管理和识别 - 每个对话会话独立存储对话历史 - 适用于多用户、多场景的对话管理需求 ```typescript // 创建新的对话会话 const conversation = await ai.bot.createConversation({ botId: "botId-xxx", title: "技术咨询对话", }); console.log("对话创建成功:", conversation.conversationId); console.log("对话标题:", conversation.title); ``` ```typescript // 多用户对话管理系统 class ConversationManager { private botId: string; private userConversations: Map = new Map(); // userId -> conversationId constructor(botId: string) { this.botId = botId; } async createUserConversation( userId: string, title?: string ): Promise { const conversation = await ai.bot.createConversation({ botId: this.botId, title: title || `${userId}的对话`, }); this.userConversations.set(userId, conversation.conversationId); console.log(`为用户 ${userId} 创建对话: ${conversation.conversationId}`); return conversation.conversationId; } async getUserConversation(userId: string): Promise { return this.userConversations.get(userId) || null; } async sendMessageToUser(userId: string, message: string): Promise { let conversationId = await this.getUserConversation(userId); if (!conversationId) { conversationId = await this.createUserConversation(userId); } const response = await ai.bot.sendMessage({ botId: this.botId, msg: message, conversationId: conversationId, }); let reply = ""; for await (let text of response.textStream) { reply += text; } return reply; } async getConversationHistory(userId: string) { const conversationId = await this.getUserConversation(userId); if (!conversationId) { return []; } const conversation = await ai.bot.getConversation({ botId: this.botId, conversationId: conversationId, }); return conversation.history || []; } } // 使用示例 const manager = new ConversationManager("support-bot-id"); // 为用户创建对话 await manager.createUserConversation("user123", "技术支持对话"); // 发送消息 const reply = await manager.sendMessageToUser("user123", "我的账户有问题"); console.log("助手回复:", reply); // 获取对话历史 const history = await manager.getConversationHistory("user123"); console.log("对话历史:", history); ``` ```typescript // 多主题对话管理系统 class MultiTopicConversationManager { private botId: string; private topicConversations: Map = new Map(); // topic -> conversationId constructor(botId: string) { this.botId = botId; } async createTopicConversation(topic: string): Promise { const conversation = await ai.bot.createConversation({ botId: this.botId, title: `关于${topic}的讨论`, }); this.topicConversations.set(topic, conversation.conversationId); console.log(`创建主题对话: ${topic} -> ${conversation.conversationId}`); return conversation.conversationId; } async switchTopic(topic: string, message: string): Promise { let conversationId = this.topicConversations.get(topic); if (!conversationId) { conversationId = await this.createTopicConversation(topic); } const response = await ai.bot.sendMessage({ botId: this.botId, msg: message, conversationId: conversationId, }); let reply = ""; for await (let text of response.textStream) { reply += text; } return reply; } async getTopicSummary(topic: string): Promise { const conversationId = this.topicConversations.get(topic); if (!conversationId) { return "该主题暂无对话记录"; } const conversation = await ai.bot.getConversation({ botId: this.botId, conversationId: conversationId, }); const history = conversation.history || []; const messageCount = history.length; const lastMessage = history.length > 0 ? history[history.length - 1].content : "无"; return `主题: ${topic}\n对话数量: ${messageCount}\n最后消息: ${lastMessage}`; } listTopics(): string[] { return Array.from(this.topicConversations.keys()); } } // 使用示例 const topicManager = new MultiTopicConversationManager("multi-topic-bot-id"); // 在不同主题间切换对话 const techReply = await topicManager.switchTopic( "技术问题", "如何解决这个bug?" ); console.log("技术问题回复:", techReply); const businessReply = await topicManager.switchTopic( "商务合作", "我们想谈合作" ); console.log("商务合作回复:", businessReply); // 回到技术问题继续对话 const techReply2 = await topicManager.switchTopic( "技术问题", "还有其他解决方案吗?" ); console.log("技术问题继续:", techReply2); // 查看主题摘要 console.log("所有主题:", topicManager.listTopics()); const summary = await topicManager.getTopicSummary("技术问题"); console.log("技术问题摘要:\n", summary); ``` --- ### getConversation ```typescript function getConversation(props: { botId: string; pageSize?: number; pageNumber?: number; isDefault?: boolean; }): Promise; ``` 获取 Agent 的对话列表,支持分页查询和条件筛选。 - 查询指定 Agent 的所有对话记录 - 支持分页查询,便于管理大量对话 - 可筛选默认对话或所有对话 - 适用于对话管理、历史记录查看等场景 ", description: "对话列表", required: true, children: [ { name: "conversationId", type: "string", description: "对话 ID", required: true }, { name: "title", type: "string", description: "对话标题", required: true }, { name: "createTime", type: "string", description: "对话创建时间", required: true }, { name: "updateTime", type: "string", description: "对话最后更新时间", required: true }, { name: "messageCount", type: "number", description: "对话中的消息数量", required: true } ] } ] } ] }} > ```typescript // 获取对话列表 const result = await ai.bot.getConversation({ botId: "botId-xxx", pageSize: 10, pageNumber: 1, isDefault: false, }); console.log(`共有 ${result.total} 个对话`); result.conversations.forEach((conversation, index) => { console.log( `${index + 1}. ${conversation.title} (${conversation.messageCount} 条消息)` ); }); ``` ```typescript // 对话管理系统 class ConversationManagementSystem { private botId: string; constructor(botId: string) { this.botId = botId; } async getAllConversations(): Promise { let allConversations: IConversation[] = []; let pageNumber = 1; const pageSize = 50; while (true) { const result = await ai.bot.getConversation({ botId: this.botId, pageSize: pageSize, pageNumber: pageNumber, isDefault: false, }); allConversations = allConversations.concat(result.conversations); if (result.conversations.length < pageSize) { break; } pageNumber++; } return allConversations; } async searchConversations(keyword: string): Promise { const allConversations = await this.getAllConversations(); return allConversations.filter((conversation) => conversation.title.toLowerCase().includes(keyword.toLowerCase()) ); } async getRecentConversations(days: number = 7): Promise { const allConversations = await this.getAllConversations(); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - days); return allConversations.filter((conversation) => { const updateTime = new Date(conversation.updateTime); return updateTime >= cutoffDate; }); } async getConversationStats(): Promise<{ total: number; recent: number; averageMessages: number; }> { const allConversations = await this.getAllConversations(); const recentConversations = await this.getRecentConversations(7); const totalMessages = allConversations.reduce( (sum, conv) => sum + conv.messageCount, 0 ); const averageMessages = allConversations.length > 0 ? totalMessages / allConversations.length : 0; return { total: allConversations.length, recent: recentConversations.length, averageMessages: Math.round(averageMessages), }; } async exportConversationList(): Promise { const allConversations = await this.getAllConversations(); const csv = [ "对话ID,标题,创建时间,最后更新时间,消息数量", ...allConversations.map( (conv) => `${conv.conversationId},"${conv.title}",${conv.createTime},${conv.updateTime},${conv.messageCount}` ), ].join("\n"); return csv; } } // 使用示例 const manager = new ConversationManagementSystem("management-bot-id"); // 获取对话统计 const stats = await manager.getConversationStats(); console.log(`总对话数: ${stats.total}`); console.log(`最近7天对话数: ${stats.recent}`); console.log(`平均消息数: ${stats.averageMessages}`); // 搜索对话 const searchResults = await manager.searchConversations("技术"); console.log(`找到 ${searchResults.length} 个技术相关对话`); // 导出对话列表 const csvData = await manager.exportConversationList(); console.log("对话列表导出完成"); ``` ```typescript // 批量对话分析工具 class ConversationAnalyzer { private botId: string; constructor(botId: string) { this.botId = botId; } async analyzeConversationPatterns(): Promise<{ timeDistribution: { hour: number; count: number }[]; messageLengthStats: { avgLength: number; maxLength: number }; topicDistribution: Map; }> { const allConversations = await this.getAllConversations(); // 时间分布分析 const timeDistribution = Array.from({ length: 24 }, (_, hour) => ({ hour, count: allConversations.filter((conv) => { const hour = new Date(conv.createTime).getHours(); return hour === hour; }).length, })); // 消息长度统计 const messageLengths = allConversations.map((conv) => conv.messageCount); const avgLength = messageLengths.reduce((sum, len) => sum + len, 0) / messageLengths.length; const maxLength = Math.max(...messageLengths); // 主题分布分析(基于标题关键词) const topicDistribution = new Map(); const topics = ["技术", "商务", "客服", "产品", "其他"]; topics.forEach((topic) => { const count = allConversations.filter((conv) => conv.title.includes(topic) ).length; topicDistribution.set(topic, count); }); return { timeDistribution, messageLengthStats: { avgLength, maxLength }, topicDistribution, }; } async getConversationTrends(): Promise<{ dailyTrends: { date: string; count: number }[]; weeklyTrends: { week: string; count: number }[]; }> { const allConversations = await this.getAllConversations(); // 按日期分组 const dailyCounts = new Map(); allConversations.forEach((conv) => { const date = new Date(conv.createTime).toISOString().split("T")[0]; dailyCounts.set(date, (dailyCounts.get(date) || 0) + 1); }); const dailyTrends = Array.from(dailyCounts.entries()) .map(([date, count]) => ({ date, count, })) .sort((a, b) => a.date.localeCompare(b.date)); // 按周分组 const weeklyCounts = new Map(); allConversations.forEach((conv) => { const date = new Date(conv.createTime); const weekStart = new Date(date); weekStart.setDate(date.getDate() - date.getDay()); const weekKey = weekStart.toISOString().split("T")[0]; weeklyCounts.set(weekKey, (weeklyCounts.get(weekKey) || 0) + 1); }); const weeklyTrends = Array.from(weeklyCounts.entries()) .map(([week, count]) => ({ week, count, })) .sort((a, b) => a.week.localeCompare(b.week)); return { dailyTrends, weeklyTrends }; } private async getAllConversations(): Promise { let allConversations: IConversation[] = []; let pageNumber = 1; const pageSize = 100; while (true) { const result = await ai.bot.getConversation({ botId: this.botId, pageSize: pageSize, pageNumber: pageNumber, isDefault: false, }); allConversations = allConversations.concat(result.conversations); if (result.conversations.length < pageSize) { break; } pageNumber++; } return allConversations; } } // 使用示例 const analyzer = new ConversationAnalyzer("analytics-bot-id"); // 分析对话模式 const patterns = await analyzer.analyzeConversationPatterns(); console.log("对话时间分布:", patterns.timeDistribution); console.log("消息长度统计:", patterns.messageLengthStats); console.log("主题分布:", patterns.topicDistribution); // 获取对话趋势 const trends = await analyzer.getConversationTrends(); console.log("每日趋势:", trends.dailyTrends); console.log("每周趋势:", trends.weeklyTrends); ``` --- ### deleteConversation ```typescript function deleteConversation(props: { botId: string; conversationId: string; }): Promise; ``` 删除指定的对话会话,清理对话历史记录。 - 永久删除指定的对话会话 - 清理对话中的所有消息记录 - 释放存储空间,优化系统性能 - 适用于对话管理、数据清理等场景 ```typescript // 删除指定的对话 await ai.bot.deleteConversation({ botId: "botId-xxx", conversationId: "conv-123", }); console.log("对话删除成功"); ``` ```typescript // 批量对话清理工具 class ConversationCleaner { private botId: string; constructor(botId: string) { this.botId = botId; } async deleteOldConversations(daysThreshold: number = 30): Promise { const allConversations = await this.getAllConversations(); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysThreshold); const oldConversations = allConversations.filter((conversation) => { const updateTime = new Date(conversation.updateTime); return updateTime < cutoffDate; }); console.log( `找到 ${oldConversations.length} 个超过 ${daysThreshold} 天的旧对话` ); // 批量删除旧对话 let deletedCount = 0; for (const conversation of oldConversations) { try { await ai.bot.deleteConversation({ botId: this.botId, conversationId: conversation.conversationId, }); deletedCount++; console.log(`已删除对话: ${conversation.title}`); } catch (error) { console.error(`删除对话失败: ${conversation.title}`, error); } } console.log(`成功删除 ${deletedCount} 个旧对话`); return deletedCount; } async deleteEmptyConversations(): Promise { const allConversations = await this.getAllConversations(); const emptyConversations = allConversations.filter( (conversation) => conversation.messageCount === 0 ); console.log(`找到 ${emptyConversations.length} 个空对话`); let deletedCount = 0; for (const conversation of emptyConversations) { try { await ai.bot.deleteConversation({ botId: this.botId, conversationId: conversation.conversationId, }); deletedCount++; } catch (error) { console.error(`删除空对话失败: ${conversation.title}`, error); } } console.log(`成功删除 ${deletedCount} 个空对话`); return deletedCount; } async deleteConversationsByTitle(keyword: string): Promise { const allConversations = await this.getAllConversations(); const matchingConversations = allConversations.filter((conversation) => conversation.title.toLowerCase().includes(keyword.toLowerCase()) ); console.log( `找到 ${matchingConversations.length} 个包含"${keyword}"的对话` ); let deletedCount = 0; for (const conversation of matchingConversations) { try { await ai.bot.deleteConversation({ botId: this.botId, conversationId: conversation.conversationId, }); deletedCount++; console.log(`已删除: ${conversation.title}`); } catch (error) { console.error(`删除对话失败: ${conversation.title}`, error); } } return deletedCount; } private async getAllConversations(): Promise { let allConversations: IConversation[] = []; let pageNumber = 1; const pageSize = 100; while (true) { const result = await ai.bot.getConversation({ botId: this.botId, pageSize: pageSize, pageNumber: pageNumber, isDefault: false, }); allConversations = allConversations.concat(result.conversations); if (result.conversations.length < pageSize) { break; } pageNumber++; } return allConversations; } } // 使用示例 const cleaner = new ConversationCleaner("cleanup-bot-id"); // 删除30天前的旧对话 const oldDeleted = await cleaner.deleteOldConversations(30); console.log(`清理了 ${oldDeleted} 个旧对话`); // 删除空对话 const emptyDeleted = await cleaner.deleteEmptyConversations(); console.log(`清理了 ${emptyDeleted} 个空对话`); // 删除包含特定关键词的对话 const keywordDeleted = await cleaner.deleteConversationsByTitle("测试"); console.log(`清理了 ${keywordDeleted} 个测试对话`); ``` ```typescript // 用户数据管理系统 class UserDataManager { private botId: string; private userConversations: Map = new Map(); // userId -> conversationIds constructor(botId: string) { this.botId = botId; } async deleteUserConversations(userId: string): Promise { const userConversationIds = this.userConversations.get(userId) || []; console.log(`为用户 ${userId} 删除 ${userConversationIds.length} 个对话`); let deletedCount = 0; for (const conversationId of userConversationIds) { try { await ai.bot.deleteConversation({ botId: this.botId, conversationId: conversationId, }); deletedCount++; console.log(`已删除对话: ${conversationId}`); } catch (error) { console.error(`删除对话失败: ${conversationId}`, error); } } // 从内存中移除用户对话记录 this.userConversations.delete(userId); console.log(`用户 ${userId} 的数据清理完成,删除了 ${deletedCount} 个对话`); return deletedCount; } async deleteAllUserData(): Promise { const allUserIds = Array.from(this.userConversations.keys()); let totalDeleted = 0; console.log(`开始清理 ${allUserIds.length} 个用户的数据`); for (const userId of allUserIds) { const deletedCount = await this.deleteUserConversations(userId); totalDeleted += deletedCount; } console.log(`总共清理了 ${totalDeleted} 个对话`); return totalDeleted; } async deleteUserDataByCondition( condition: (userId: string, conversationIds: string[]) => boolean ): Promise { const usersToDelete: string[] = []; for (const [userId, conversationIds] of this.userConversations.entries()) { if (condition(userId, conversationIds)) { usersToDelete.push(userId); } } console.log(`找到 ${usersToDelete.length} 个符合条件的用户`); let totalDeleted = 0; for (const userId of usersToDelete) { const deletedCount = await this.deleteUserConversations(userId); totalDeleted += deletedCount; } return totalDeleted; } // 添加用户对话记录 addUserConversation(userId: string, conversationId: string) { if (!this.userConversations.has(userId)) { this.userConversations.set(userId, []); } this.userConversations.get(userId)!.push(conversationId); } // 获取用户对话统计 getUserStats(): { totalUsers: number; totalConversations: number } { let totalConversations = 0; for (const conversationIds of this.userConversations.values()) { totalConversations += conversationIds.length; } return { totalUsers: this.userConversations.size, totalConversations: totalConversations, }; } } // 使用示例 const dataManager = new UserDataManager("data-management-bot-id"); // 添加用户对话记录 dataManager.addUserConversation("user1", "conv-123"); dataManager.addUserConversation("user1", "conv-456"); dataManager.addUserConversation("user2", "conv-789"); // 查看统计 const stats = dataManager.getUserStats(); console.log(`用户数: ${stats.totalUsers}, 对话数: ${stats.totalConversations}`); // 删除特定用户数据 const deletedCount = await dataManager.deleteUserConversations("user1"); console.log(`删除了用户1的 ${deletedCount} 个对话`); // 条件删除:删除对话数超过阈值的用户 const conditionDeleted = await dataManager.deleteUserDataByCondition( (userId, conversationIds) => conversationIds.length > 5 ); console.log(`删除了 ${conditionDeleted} 个对话数超过5的用户数据`); ``` --- ### speechToText ```typescript function speechToText(props: { botId: string; engSerViceType: string; voiceFormat: string; url: string; isPreview?: boolean; }): Promise; ``` 将语音文件转换为文字,支持多种音频格式和语言识别。 - 支持多种音频格式:MP3、WAV、AAC 等 - 支持多种语言识别引擎 - 可处理本地或远程音频文件 - 适用于语音助手、会议记录、语音笔记等场景 ```typescript // 将语音文件转换为文字 const result = await ai.bot.speechToText({ botId: "speech-bot-id", engSerViceType: "16k_zh", voiceFormat: "mp3", url: "https://example.com/audio.mp3", }); console.log("识别结果:", result.text); console.log("置信度:", result.confidence); console.log("音频时长:", result.duration, "秒"); console.log("识别语言:", result.language); ``` ```typescript // 语音助手应用 class VoiceAssistant { private botId: string; constructor(botId: string) { this.botId = botId; } async processVoiceCommand(audioUrl: string): Promise<{ text: string; response: string; action?: string; }> { // 语音转文字 const speechResult = await ai.bot.speechToText({ botId: this.botId, engSerViceType: "16k_zh", voiceFormat: "mp3", url: audioUrl, }); console.log("语音识别结果:", speechResult.text); // 根据识别结果生成回复 const response = await ai.bot.sendMessage({ botId: this.botId, msg: speechResult.text, }); let assistantReply = ""; for await (let text of response.textStream) { assistantReply += text; } // 分析是否需要执行特定操作 const action = this.analyzeCommand(speechResult.text); return { text: speechResult.text, response: assistantReply, action: action, }; } async createVoiceNote(audioUrl: string, title?: string): Promise { const result = await ai.bot.speechToText({ botId: this.botId, engSerViceType: "16k_zh", voiceFormat: "mp3", url: audioUrl, }); const noteContent = ` 语音笔记 - ${title || new Date().toLocaleString()} 音频时长: ${result.duration}秒 识别置信度: ${Math.round(result.confidence * 100)}% 内容: ${result.text} `; console.log("语音笔记创建成功:"); console.log(noteContent); return noteContent; } async batchTranscribe( audioFiles: Array<{ url: string; name: string }> ): Promise> { const results = new Map(); console.log(`开始批量转换 ${audioFiles.length} 个音频文件`); for (const file of audioFiles) { try { const result = await ai.bot.speechToText({ botId: this.botId, engSerViceType: "16k_zh", voiceFormat: this.getFileFormat(file.url), url: file.url, }); results.set(file.name, result.text); console.log(`✓ ${file.name}: 转换成功`); } catch (error) { console.error(`✗ ${file.name}: 转换失败`, error); results.set(file.name, "转换失败"); } } console.log("批量转换完成"); return results; } private analyzeCommand(text: string): string | undefined { const commands = { 天气: "weather", 时间: "time", 播放: "play", 停止: "stop", 搜索: "search", }; for (const [keyword, action] of Object.entries(commands)) { if (text.includes(keyword)) { return action; } } return undefined; } private getFileFormat(url: string): string { const extension = url.split(".").pop()?.toLowerCase(); return extension === "wav" ? "wav" : "mp3"; } } // 使用示例 const voiceAssistant = new VoiceAssistant("voice-bot-id"); // 处理语音命令 const commandResult = await voiceAssistant.processVoiceCommand( "voice-command.mp3" ); console.log("语音命令:", commandResult.text); console.log("助手回复:", commandResult.response); if (commandResult.action) { console.log("执行操作:", commandResult.action); } // 创建语音笔记 const note = await voiceAssistant.createVoiceNote( "meeting-recording.mp3", "会议记录" ); // 批量转换音频文件 const audioFiles = [ { url: "audio1.mp3", name: "采访录音1" }, { url: "audio2.wav", name: "会议录音2" }, { url: "audio3.mp3", name: "讲座录音3" }, ]; const transcriptions = await voiceAssistant.batchTranscribe(audioFiles); ``` ```typescript // 智能会议记录系统 class MeetingTranscriptionSystem { private botId: string; constructor(botId: string) { this.botId = botId; } async transcribeMeeting( audioUrl: string, participants: string[] = [] ): Promise<{ transcript: string; summary: string; actionItems: string[]; speakerAnalysis: Map; }> { // 语音转文字 const speechResult = await ai.bot.speechToText({ botId: this.botId, engSerViceType: "16k_zh", voiceFormat: "mp3", url: audioUrl, }); console.log("会议录音转换完成,时长:", speechResult.duration, "秒"); // 生成会议摘要 const summaryPrompt = `请为以下会议内容生成摘要,提取关键决策和行动项:\n\n${speechResult.text}`; const summaryResponse = await ai.bot.sendMessage({ botId: this.botId, msg: summaryPrompt, }); let summary = ""; for await (let text of summaryResponse.textStream) { summary += text; } // 提取行动项 const actionPrompt = `从以下会议内容中提取具体的行动项和负责人:\n\n${speechResult.text}`; const actionResponse = await ai.bot.sendMessage({ botId: this.botId, msg: actionPrompt, }); let actionText = ""; for await (let text of actionResponse.textStream) { actionText += text; } const actionItems = actionText .split("\n") .filter((item) => item.trim().length > 0); // 分析发言分布(简单版本) const speakerAnalysis = this.analyzeSpeakerDistribution( speechResult.text, participants ); return { transcript: speechResult.text, summary: summary, actionItems: actionItems, speakerAnalysis: speakerAnalysis, }; } async generateMeetingMinutes( audioUrl: string, meetingTitle: string, participants: string[] ): Promise { const meetingData = await this.transcribeMeeting(audioUrl, participants); const minutes = ` 会议纪要 ======== 会议标题: ${meetingTitle} 会议时长: ${ meetingData.transcript.length > 0 ? "约" + Math.ceil(meetingData.transcript.length / 200) + "分钟" : "未知" } 参会人员: ${participants.join(", ")} 会议摘要 -------- ${meetingData.summary} 会议记录 -------- ${meetingData.transcript} 行动项 ------ ${meetingData.actionItems .map((item, index) => `${index + 1}. ${item}`) .join("\n")} 发言统计 -------- ${Array.from(meetingData.speakerAnalysis.entries()) .map(([speaker, count]) => `${speaker}: ${count} 次发言`) .join("\n")} `; console.log("会议纪要生成完成"); return minutes; } private analyzeSpeakerDistribution( transcript: string, participants: string[] ): Map { const distribution = new Map(); // 为每个参与者初始化计数 participants.forEach((participant) => { distribution.set(participant, 0); }); // 简单分析:统计参与者名字出现的次数 participants.forEach((participant) => { const regex = new RegExp(participant, "gi"); const count = (transcript.match(regex) || []).length; distribution.set(participant, count); }); return distribution; } } // 使用示例 const meetingSystem = new MeetingTranscriptionSystem("meeting-bot-id"); // 生成会议纪要 const minutes = await meetingSystem.generateMeetingMinutes( "meeting-recording.mp3", "季度规划会议", ["张三", "李四", "王五"] ); console.log(minutes); // 直接转录会议 const meetingData = await meetingSystem.transcribeMeeting("team-meeting.mp3"); console.log("会议摘要:", meetingData.summary); console.log("行动项:", meetingData.actionItems); ``` --- ### textToSpeech ```typescript function textToSpeech(props: { botId: string; voiceType: number; text: string; isPreview?: boolean; }): Promise; ``` 将文本转换为语音,支持多种语音类型和音色选择。 - 支持多种语音类型和音色选择 - 可调节语速、音调等参数 - 支持长文本分段转换 - 适用于语音播报、有声读物、语音助手等场景 ```typescript // 将文本转换为语音 const result = await ai.bot.textToSpeech({ botId: "tts-bot-id", voiceType: 1, // 标准女声 text: "你好,我是AI语音助手,很高兴为您服务。", }); console.log("语音合成完成:"); console.log("音频地址:", result.audioUrl); console.log("音频时长:", result.duration, "秒"); console.log("文件格式:", result.format); console.log("文件大小:", result.size, "字节"); ``` ```typescript // 智能语音助手 class VoiceAssistant { private botId: string; private voiceType: number; constructor(botId: string, voiceType: number = 1) { this.botId = botId; this.voiceType = voiceType; } async speakText(text: string): Promise { const result = await ai.bot.textToSpeech({ botId: this.botId, voiceType: this.voiceType, text: text, }); console.log(`语音合成完成: ${result.duration}秒`); // 在实际应用中,这里可以播放音频 this.playAudio(result.audioUrl); return result.audioUrl; } async createAudioBook( chapters: Array<{ title: string; content: string }> ): Promise> { const audioFiles = new Map(); console.log(`开始制作有声读物,共 ${chapters.length} 章`); for (const [index, chapter] of chapters.entries()) { console.log(`正在合成第 ${index + 1} 章: ${chapter.title}`); try { const result = await ai.bot.textToSpeech({ botId: this.botId, voiceType: this.voiceType, text: chapter.content, }); audioFiles.set(chapter.title, result.audioUrl); console.log(`✓ 第 ${index + 1} 章合成完成,时长: ${result.duration}秒`); } catch (error) { console.error(`✗ 第 ${index + 1} 章合成失败`, error); } } console.log("有声读物制作完成"); return audioFiles; } async generateInteractiveStory( storyTitle: string, storyContent: string[] ): Promise<{ audioFiles: string[]; totalDuration: number; }> { const audioFiles: string[] = []; let totalDuration = 0; console.log(`开始生成交互式故事: ${storyTitle}`); for (const [index, paragraph] of storyContent.entries()) { console.log(`合成第 ${index + 1} 段内容`); const result = await ai.bot.textToSpeech({ botId: this.botId, voiceType: this.voiceType, text: paragraph, }); audioFiles.push(result.audioUrl); totalDuration += result.duration; console.log(`✓ 第 ${index + 1} 段合成完成,时长: ${result.duration}秒`); } console.log(`故事生成完成,总时长: ${totalDuration}秒`); return { audioFiles, totalDuration }; } async createVoiceAnnouncement( message: string, repeat: number = 1 ): Promise { const announcements: string[] = []; console.log(`生成语音播报,重复 ${repeat} 次`); for (let i = 0; i < repeat; i++) { const result = await ai.bot.textToSpeech({ botId: this.botId, voiceType: this.voiceType, text: message, }); announcements.push(result.audioUrl); console.log(`✓ 第 ${i + 1} 次播报生成完成`); } return announcements; } private playAudio(audioUrl: string): void { // 在实际应用中,这里可以实现音频播放逻辑 console.log("播放音频:", audioUrl); // 示例:使用HTML5 Audio API播放 // const audio = new Audio(audioUrl); // audio.play(); } // 设置语音类型 setVoiceType(voiceType: number): void { this.voiceType = voiceType; console.log(`语音类型已设置为: ${voiceType}`); } // 获取支持的语音类型 getSupportedVoices(): Array<{ id: number; name: string }> { return [ { id: 1, name: "标准女声" }, { id: 2, name: "标准男声" }, { id: 3, name: "情感女声" }, { id: 4, name: "情感男声" }, { id: 5, name: "儿童声音" }, { id: 6, name: "老年声音" }, ]; } } // 使用示例 const voiceAssistant = new VoiceAssistant("voice-bot-id", 1); // 简单语音播报 const audioUrl = await voiceAssistant.speakText("欢迎使用语音助手系统!"); // 制作有声读物 const chapters = [ { title: "第一章", content: "这是一个故事的开头..." }, { title: "第二章", content: "故事继续发展..." }, { title: "第三章", content: "故事的高潮部分..." }, ]; const audioBook = await voiceAssistant.createAudioBook(chapters); // 生成交互式故事 const storyContent = [ "从前有一个小村庄...", "村庄里住着一位聪明的老人...", "老人教会了村民很多知识...", ]; const story = await voiceAssistant.generateInteractiveStory( "智慧老人", storyContent ); // 创建语音播报 const announcements = await voiceAssistant.createVoiceAnnouncement( "请注意,系统即将维护", 3 ); ``` ```typescript // 多语言语音合成系统 class MultiLanguageVoiceSystem { private botId: string; private languageVoices: Map = new Map(); constructor(botId: string) { this.botId = botId; this.initializeLanguageVoices(); } private initializeLanguageVoices(): void { this.languageVoices.set("zh-CN", 1); // 中文-标准女声 this.languageVoices.set("en-US", 2); // 英文-标准男声 this.languageVoices.set("ja-JP", 3); // 日语-情感女声 this.languageVoices.set("ko-KR", 4); // 韩语-情感男声 this.languageVoices.set("fr-FR", 1); // 法语-标准女声 this.languageVoices.set("de-DE", 2); // 德语-标准男声 } async synthesizeMultilingualText( texts: Map ): Promise> { const results = new Map(); console.log(`开始合成多语言文本,共 ${texts.size} 种语言`); for (const [language, text] of texts.entries()) { const voiceType = this.languageVoices.get(language) || 1; console.log(`合成 ${language} 语言文本`); try { const result = await ai.bot.textToSpeech({ botId: this.botId, voiceType: voiceType, text: text, }); results.set(language, result.audioUrl); console.log(`✓ ${language} 语言合成完成,时长: ${result.duration}秒`); } catch (error) { console.error(`✗ ${language} 语言合成失败`, error); results.set(language, "合成失败"); } } return results; } async createInternationalAnnouncement( message: string ): Promise> { const translations = await this.translateMessage(message); return this.synthesizeMultilingualText(translations); } async generateLanguageLearningAudio( language: string, phrases: string[] ): Promise> { const learningAudios: Array<{ phrase: string; audioUrl: string }> = []; const voiceType = this.languageVoices.get(language) || 1; console.log(`生成 ${language} 语言学习音频,共 ${phrases.length} 个短语`); for (const phrase of phrases) { try { const result = await ai.bot.textToSpeech({ botId: this.botId, voiceType: voiceType, text: phrase, }); learningAudios.push({ phrase: phrase, audioUrl: result.audioUrl, }); console.log(`✓ 短语 "${phrase}" 音频生成完成`); } catch (error) { console.error(`✗ 短语 "${phrase}" 音频生成失败`, error); } } return learningAudios; } async createTourGuideAudio( language: string, attractions: Array<{ name: string; description: string }> ): Promise> { const guideAudios = new Map(); const voiceType = this.languageVoices.get(language) || 1; console.log( `生成 ${language} 语言导游音频,共 ${attractions.length} 个景点` ); for (const attraction of attractions) { const description = `${attraction.name}:${attraction.description}`; try { const result = await ai.bot.textToSpeech({ botId: this.botId, voiceType: voiceType, text: description, }); guideAudios.set(attraction.name, result.audioUrl); console.log(`✓ 景点 "${attraction.name}" 导游音频生成完成`); } catch (error) { console.error(`✗ 景点 "${attraction.name}" 导游音频生成失败`, error); } } return guideAudios; } private async translateMessage( message: string ): Promise> { // 在实际应用中,这里可以调用翻译API const translations = new Map(); translations.set("zh-CN", message); // 中文 translations.set("en-US", "Hello, this is an announcement."); // 英文 translations.set("ja-JP", "こんにちは、これはアナウンスです。"); // 日语 translations.set("ko-KR", "안녕하세요, 공지사항입니다."); // 韩语 return translations; } // 添加新的语言支持 addLanguageSupport(languageCode: string, voiceType: number): void { this.languageVoices.set(languageCode, voiceType); console.log(`添加语言支持: ${languageCode} -> 语音类型 ${voiceType}`); } // 获取支持的语言列表 getSupportedLanguages(): string[] { return Array.from(this.languageVoices.keys()); } } // 使用示例 const multiLangSystem = new MultiLanguageVoiceSystem("multilang-bot-id"); // 多语言文本合成 const multilingualTexts = new Map([ ["zh-CN", "欢迎使用多语言语音系统"], ["en-US", "Welcome to the multilingual voice system"], ["ja-JP", "多言語音声システムへようこそ"], ]); const audioResults = await multiLangSystem.synthesizeMultilingualText( multilingualTexts ); // 国际公告播报 const announcements = await multiLangSystem.createInternationalAnnouncement( "系统维护通知" ); // 语言学习音频 const englishPhrases = ["Hello", "How are you?", "Thank you", "Goodbye"]; const learningAudios = await multiLangSystem.generateLanguageLearningAudio( "en-US", englishPhrases ); // 导游音频 const attractions = [ { name: "故宫", description: "中国古代宫殿建筑群,世界文化遗产" }, { name: "长城", description: "中国古代军事防御工程,世界奇迹" }, ]; const guideAudios = await multiLangSystem.createTourGuideAudio( "zh-CN", attractions ); ``` --- ### getTextToSpeechResult ```typescript function getTextToSpeechResult(props: { botId: string; taskId: string; isPreview?: boolean; }): Promise; ``` 获取文字转语音任务的结果,查询语音合成任务的完成状态和生成的音频文件。 - 查询异步语音合成任务的状态和结果 - 获取生成的音频文件信息 - 支持预览模式和正式模式 - 适用于批量语音合成、长文本处理等异步场景 ```typescript // 查询语音合成任务结果 const result = await ai.bot.getTextToSpeechResult({ botId: "tts-bot-id", taskId: "task-123456", }); console.log("任务状态:", result.status); console.log("任务进度:", result.progress, "%"); if (result.status === "completed") { console.log("音频地址:", result.audioUrl); console.log("音频时长:", result.duration, "秒"); console.log("文件格式:", result.format); console.log("文件大小:", result.size, "字节"); } else if (result.status === "processing") { console.log("任务仍在处理中,请稍后重试"); } else if (result.status === "failed") { console.log("任务处理失败"); } ``` ```typescript // 批量语音合成任务监控系统 class BatchTTSMonitor { private botId: string; private taskQueue: Map = new Map(); constructor(botId: string) { this.botId = botId; } // 添加任务到队列 addTask(taskId: string, text: string): void { this.taskQueue.set(taskId, { text: text, createTime: new Date(), }); console.log(`任务 ${taskId} 已添加到队列`); } // 监控任务进度 async monitorTaskProgress( taskId: string, maxWaitTime: number = 300 ): Promise { const startTime = Date.now(); const checkInterval = 5000; // 5秒检查一次 console.log(`开始监控任务 ${taskId} 进度`); while (true) { const result = await ai.bot.getTextToSpeechResult({ botId: this.botId, taskId: taskId, }); console.log( `任务 ${taskId} 状态: ${result.status}, 进度: ${result.progress}%` ); if (result.status === "completed") { console.log(`任务 ${taskId} 已完成,音频时长: ${result.duration}秒`); this.taskQueue.delete(taskId); return result; } if (result.status === "failed") { console.error(`任务 ${taskId} 处理失败`); this.taskQueue.delete(taskId); throw new Error(`语音合成任务失败: ${taskId}`); } // 检查是否超时 if (Date.now() - startTime > maxWaitTime * 1000) { console.error(`任务 ${taskId} 监控超时`); throw new Error(`语音合成任务超时: ${taskId}`); } // 等待一段时间后重试 await this.sleep(checkInterval); } } // 批量监控所有任务 async monitorAllTasks(): Promise> { const results = new Map(); const taskIds = Array.from(this.taskQueue.keys()); console.log(`开始监控 ${taskIds.length} 个任务`); for (const taskId of taskIds) { try { const result = await this.monitorTaskProgress(taskId); results.set(taskId, result); } catch (error) { console.error(`任务 ${taskId} 监控失败:`, error); } } console.log(`任务监控完成,成功: ${results.size}/${taskIds.length}`); return results; } // 获取任务统计信息 getTaskStats(): { total: number; completed: number; processing: number; failed: number; } { const stats = { total: this.taskQueue.size, completed: 0, processing: 0, failed: 0, }; // 在实际应用中,这里可以根据任务状态进行统计 return stats; } // 清理已完成的任务 cleanupCompletedTasks(): void { const completedTaskIds: string[] = []; // 在实际应用中,这里可以清理已完成的任务记录 console.log(`清理了 ${completedTaskIds.length} 个已完成任务`); } private sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } } // 使用示例 const monitor = new BatchTTSMonitor("batch-tts-bot-id"); // 添加任务到监控队列 monitor.addTask("task-001", "这是第一个语音合成任务"); monitor.addTask("task-002", "这是第二个语音合成任务"); monitor.addTask("task-003", "这是第三个语音合成任务"); // 监控单个任务 const result = await monitor.monitorTaskProgress("task-001"); console.log("任务结果:", result.audioUrl); // 批量监控所有任务 const allResults = await monitor.monitorAllTasks(); // 查看任务统计 const stats = monitor.getTaskStats(); console.log("任务统计:", stats); ``` ```typescript // 长文本语音合成系统 class LongTextTTSProcessor { private botId: string; private voiceType: number; constructor(botId: string, voiceType: number = 1) { this.botId = botId; this.voiceType = voiceType; } // 分段处理长文本 async processLongText( longText: string, maxSegmentLength: number = 500 ): Promise { const segments = this.splitText(longText, maxSegmentLength); const audioUrls: string[] = []; console.log(`开始处理长文本,共 ${segments.length} 段`); for (const [index, segment] of segments.entries()) { console.log(`处理第 ${index + 1}/${segments.length} 段文本`); try { // 提交语音合成任务 const result = await ai.bot.textToSpeech({ botId: this.botId, voiceType: this.voiceType, text: segment, }); // 等待任务完成 const finalResult = await this.waitForTaskCompletion(result.taskId); audioUrls.push(finalResult.audioUrl); console.log( `✓ 第 ${index + 1} 段处理完成,时长: ${finalResult.duration}秒` ); } catch (error) { console.error(`✗ 第 ${index + 1} 段处理失败`, error); } } console.log(`长文本处理完成,生成 ${audioUrls.length} 个音频文件`); return audioUrls; } // 等待任务完成 private async waitForTaskCompletion( taskId: string, maxWaitTime: number = 600 ): Promise { const startTime = Date.now(); const checkInterval = 3000; // 3秒检查一次 while (true) { const result = await ai.bot.getTextToSpeechResult({ botId: this.botId, taskId: taskId, }); if (result.status === "completed") { return result; } if (result.status === "failed") { throw new Error(`语音合成任务失败: ${taskId}`); } // 检查超时 if (Date.now() - startTime > maxWaitTime * 1000) { throw new Error(`语音合成任务超时: ${taskId}`); } console.log(`任务 ${taskId} 进度: ${result.progress}%`); await this.sleep(checkInterval); } } // 文本分段 private splitText(text: string, maxLength: number): string[] { const segments: string[] = []; let currentSegment = ""; const sentences = text.split(/[。!?.!?]/); for (const sentence of sentences) { const trimmedSentence = sentence.trim(); if (!trimmedSentence) continue; if (currentSegment.length + trimmedSentence.length + 1 <= maxLength) { currentSegment += (currentSegment ? "。" : "") + trimmedSentence; } else { if (currentSegment) { segments.push(currentSegment + "。"); } currentSegment = trimmedSentence; } } if (currentSegment) { segments.push(currentSegment + "。"); } return segments; } // 合并音频文件(在实际应用中需要专门的音频处理工具) async mergeAudioFiles(audioUrls: string[]): Promise { console.log(`开始合并 ${audioUrls.length} 个音频文件`); // 在实际应用中,这里可以调用音频合并API // 这里返回第一个音频URL作为示例 return audioUrls[0]; } // 生成有声读物 async createAudioBook( bookTitle: string, chapters: string[] ): Promise> { const bookAudios = new Map(); console.log(`开始制作有声读物: ${bookTitle},共 ${chapters.length} 章`); for (const [index, chapter] of chapters.entries()) { console.log(`处理第 ${index + 1} 章`); const chapterAudios = await this.processLongText(chapter); bookAudios.set(`第${index + 1}章`, chapterAudios); console.log( `✓ 第 ${index + 1} 章处理完成,生成 ${chapterAudios.length} 个音频片段` ); } console.log(`有声读物制作完成: ${bookTitle}`); return bookAudios; } private sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } } // 使用示例 const longTextProcessor = new LongTextTTSProcessor("longtext-bot-id", 1); // 处理长文本 const longText = "这是一段很长的文本内容,需要被分成多个段落进行语音合成..."; const audioSegments = await longTextProcessor.processLongText(longText); // 制作有声读物 const bookChapters = [ "第一章:故事的开端...", "第二章:情节发展...", "第三章:高潮部分...", "第四章:结局...", ]; const audioBook = await longTextProcessor.createAudioBook( "我的故事", bookChapters ); // 合并音频文件 const mergedAudio = await longTextProcessor.mergeAudioFiles(audioSegments); console.log("合并后的音频:", mergedAudio); ``` --- ## 完整类型定义 {#complete-type-definitions} ### IBotCreateConversation ```ts interface IBotCreateConversation { botId: string; title?: string; } ``` --- ### IBotGetConversation ```ts interface IBotGetConversation { botId: string; pageSize?: number; pageNumber?: number; isDefault?: boolean; } ``` --- ### IBotDeleteConversation ```ts interface IBotDeleteConversation { botId: string; conversationId: string; } ``` --- ### IBotSpeechToText ```ts interface IBotSpeechToText { botId: string; engSerViceType: string; voiceFormat: string; url: string; isPreview?: boolean; } ``` --- ### IBotTextToSpeech ```ts interface IBotTextToSpeech { botId: string; voiceType: number; text: string; isPreview?: boolean; } ``` --- ### IBotGetTextToSpeechResult ```ts interface IBotGetTextToSpeechResult { botId: string; taskId: string; isPreview?: boolean; } ``` --- ### BaseChatModelInput ```ts interface BaseChatModelInput { model: string; messages: Array; temperature?: number; topP?: number; tools?: Array; toolChoice?: "none" | "auto" | "custom"; maxSteps?: number; onStepFinish?: (prop: IOnStepFinish) => unknown; } ``` | BaseChatModelInput 属性名 | 类型 | 说明 | | ------------------------- | ---------------------------------- | --------------------------------------------------- | | model | `string` | 模型名称。 | | messages | `Array` | 消息列表。 | | temperature | `number` | 采样温度,控制输出的随机性。 | | topP | `number` | 温度采样,即模型考虑概率质量为 top_p 的标记的结果。 | | tools | `Array` | 大模型可用的工具列表。 | | toolChoice | `string` | 指定大模型选择工具的方式。 | | maxSteps | `number` | 请求大模型的最大次数。 | | onStepFinish | `(prop: IOnStepFinish) => unknown` | 当对大模型的一次请求完成时,出发的回调函数。 | --- ### BotInfo ```ts interface BotInfo { botId: string; name: string; introduction: string; agentSetting: string; welcomeMessage: string; avatar: string; background: string; tags: Array; isNeedRecommend: boolean; knowledgeBase: Array; type: string; initQuestions: Array; enable: true; } ``` --- ### IUserFeedback ```ts interface IUserFeedback { recordId: string; type: string; botId: string; comment: string; rating: number; tags: Array; input: string; aiAnswer: string; } ``` --- ### ChatModelMessage ```ts type ChatModelMessage = | UserMessage | SystemMessage | AssistantMessage | ToolMessage; ``` #### UserMessage ```ts type UserMessage = { role: "user"; content: string; }; ``` #### SystemMessage ```ts type SystemMessage = { role: "system"; content: string; }; ``` #### AssistantMessage ```ts type AssistantMessage = { role: "assistant"; content?: string; tool_calls?: Array; }; ``` #### ToolMessage ```ts type ToolMessage = { role: "tool"; tool_call_id: string; content: string; }; ``` --- ### ToolCall ```ts export type ToolCall = { id: string; type: string; function: { name: string; arguments: string }; }; ``` --- ### FunctionTool 工具定义类型。 ```ts type FunctionTool = { name: string; description: string; fn: CallableFunction; parameters: object; }; ``` | FunctionTool 属性名 | 类型 | 说明 | | ------------------- | ------------------ | -------------------------------------------------------------------------------------------------- | | name | `string` | 工具名称。 | | description | `string` | 工具的描述。清楚的工具描述有助于大模型认识工具的用途。 | | fn | `CallableFunction` | 工具的执行函数。当 AI SDK 解析出大模型的响应需要该工具调用时,会调用此函数,并将结果返回给大模型。 | | parameters | `object` | 工具执行函数的入参。需要使用 JSON Schema 的格式定义入参。 | --- ### IOnStepFinish 大模型响应后出发的回调函数的入参类型。 ```ts interface IOnStepFinish { messages: Array; text?: string; toolCall?: ToolCall; toolResult?: unknown; finishReason?: string; stepUsage?: Usage; totalUsage?: Usage; } ``` | IOnStepFinish 属性名 | 类型 | 说明 | | -------------------- | ------------------------- | -------------------------------- | | messages | `Array` | 到当前步骤为止所有的消息列表。 | | text | `string` | 当前响应的文本。 | | toolCall | `ToolCall` | 当前响应调用的工具。 | | toolResult | `unknown` | 对应的工具调用结果。 | | finishReason | `string` | 大模型推理结束的原因。 | | stepUsage | `Usage` | 当前步骤所花费的 token。 | | totalUsage | `Usage` | 到当前步骤为止所花费的总 token。 | --- ### Usage ```ts type Usage = { completion_tokens: number; prompt_tokens: number; total_tokens: number; }; ``` --- ### IConversation Agent 会话。 ```ts interface IConversation { id: string; envId: string; ownerUin: string; userId: string; conversationId: string; title: string; startTime: string; // date-time format createTime: string; updateTime: string; } ``` --- # JS SDK(V2)/跨端开发/适配器开发指引 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/adapter/adapter @cloudbase/js-sdk 只支持常规 Web 应用(即浏览器环境)的开发,不兼容其他类 Web 平台,比如微信小程序、快应用、Cocos 等。虽然这些平台大多支持 JavaScript 运行环境,但在网络请求、本地存储、平台标识等特性上与浏览器环境有明显差异。针对这些差异特性,@cloudbase/js-sdk 提供一套完整的适配扩展方案,遵循此方案规范可开发对应平台的适配器,然后搭配 @cloudbase/js-sdk 和适配器实现平台的兼容性。 ## 适配规范 开发适配器之前需要安装官方提供的接口声明模块[`@cloudbase/adapter-interface`](https://www.npmjs.com/package/@cloudbase/adapter-interface): ```bash # npm npm i @cloudbase/adapter-interface # yarn yarn add @cloudbase/adapter-interface ``` 适配器模块需要导出一个`adapter`对象: ```js const adapter = { genAdapter, isMatch, // runtime标记平台唯一性 runtime: '平台名称' }; export adapter; export default adapter; ``` 必须包含以下三个字段: - `runtime`: `string`,平台的名称,用于标记平台唯一性; - `isMatch`: `Function`,判断当前运行环境是否为平台,返回`boolean`值; - `genAdapter`: `Function`,创建`adapter`实体。 ### runtime `runtime`用于标记平台的唯一性,建议尽量以平台的英文名称或简写命名,比如百度小程序`baidu_miniapp`、QQ 小程序`qq_miniapp`等等。 ### isMatch `isMatch`函数用于判断当前运行环境是否与适配器匹配,通常是通过判断平台特有的一些全局变量、API 等。比如以下代码是判断运行环境是否为 Cocos 原生平台: ```js function isMatch(): boolean { if (typeof cc === "undefined") { return false; } if (typeof WebSocket === "undefined") { return false; } if (typeof XMLHttpRequest === "undefined") { return false; } if (!cc.game) { return false; } if (typeof cc.game.on !== "function") { return false; } if (!cc.game.EVENT_HIDE) { return false; } if (!cc.game.EVENT_SHOW) { return false; } if (!cc.sys) { return false; } if (!cc.sys.isNative) { return false; } return true; } ``` ### genAdapter `genAdapter`函数返回适配器的实体对象,结构如下: ```typescript interface SDKAdapterInterface { // 全局根变量,浏览器环境为window root: any; // WebSocket类 wsClass: WebSocketContructor; // request类 reqClass: SDKRequestConstructor; // 无localstorage时persistence=local降级为none localStorage?: StorageInterface; // 无sessionStorage时persistence=session降级为none sessionStorage?: StorageInterface; // storage模式首选,优先级高于persistence primaryStorage?: StorageType; // Captcha验证码配置 captchaOptions?: { // 打开网页并通过URL回调获取 CaptchaToken,针对不同的平台,该函数可以自定义实现 openURIWithCallback?: (url: string) => Promise; }; // 获取平台唯一应用标识的api getAppSign?(): string; } ``` ### 示例 ```typescript import { AbstractSDKRequest, IRequestOptions, IUploadRequestOptions, StorageInterface, WebSocketInterface, WebSocketContructor, SDKAdapterInterface, StorageType, formatUrl, } from "@cloudbase/adapter-interface"; // isMatch函数判断当前平台是否匹配 function isMatch(): boolean { // ... return true; } // Request类为平台特有的网络请求,必须实现post/upload/download三个public接口 export class Request extends AbstractSDKRequest { // 实现post接口 public post(options: IRequestOptions) { return new Promise((resolve) => { // ... resolve(); }); } // 实现upload接口 public upload(options: IUploadRequestOptions) { return new Promise((resolve) => { // ... resolve(); }); } // 实现download接口 public download(options: IRequestOptions) { return new Promise((resolve) => { // ... resolve(); }); } } // Storage为平台特有的本地存储,必须实现setItem/getItem/removeItem/clear四个接口 export const Storage: StorageInterface = { setItem(key: string, value: any) { // ... }, getItem(key: string): any { // ... }, removeItem(key: string) { // ... }, clear() { // ... }, }; // WebSocket为平台特有的WebSocket,与HTML5标准规范一致 export class WebSocket { constructor(url: string, options: object = {}) { const socketTask: WebSocketInterface = { set onopen(cb) { // ... }, set onmessage(cb) { // ... }, set onclose(cb) { // ... }, set onerror(cb) { // ... }, send: (data) => { // ... }, close: (code?: number, reason?: string) => { // ... }, get readyState() { // ... return readyState; }, CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3, }; return socketTask; } } // genAdapter函数创建adapter实体 // options 为 cloudbase.useAdapters(adapter, options) 时传入的参数 function genAdapter(options) { const adapter: SDKAdapterInterface = { // root对象为全局根对象,没有则填空对象{} root: window, reqClass: Request, wsClass: WebSocket as WebSocketContructor, localStorage: Storage, // 首先缓存存放策略,建议始终保持localstorage primaryStorage: StorageType.local, // sessionStorage为可选项,如果平台不支持可不填 sessionStorage: sessionStorage, }; return adapter; } // 三者缺一不可 const adapter = { genAdapter, isMatch, // runtime标记平台唯一性 runtime: "平台名称", }; export default adapter; ``` ## 接入流程 ### 第 1 步:安装并引入适配器 安装 @cloudbase/js-sdk 和所需平台的适配器,比如 QQ 小游戏平台: ```bash # 安装 @cloudbase/js-sdk npm i @cloudbase/js-sdk # 安装 QQ 小游戏适配器 npm i cloudbase-adapter-qq_game ``` 然后在业务代码中将引入适配器: ```js import cloudbase from "@cloudbase/js-sdk"; import adapter from "cloudbase-adapter-qq_game"; // options传入后,可以在 adapter 的 genAdapter 中获取到该参数 cloudbase.useAdapters(adapter, options); ``` ### 第 2 步:初始化云开发 在业务代码中初始化云开发 ```js import cloudbase from '@cloudbase/js-sdk'; import adapter from 'cloudbase-adapter-qq_game'; cloudbase.useAdapters(adapter); cloudbase.init({ env: '环境ID', }) ``` ## 一套代码多端适配 :::tip Web 和小程序无需配置 @cloudbase/js-sdk 已经内置了 Web 端和小程序端的 adapter,在这两个平台下无需配置即可使用。 ::: 如果您需要将一套代码兼容多种平台,@cloudbase/js-sdk 可以同时引入多个适配器,在运行时通过各适配器的`isMatch`函数判断平台类型,然后引入对应的兼容逻辑。比如以下代码可以同时兼容 QQ 小游戏、 Cocos 原生和百度小游戏三种平台: ```js import cloudbase from '@cloudbase/js-sdk'; import adapter as adapterOfQQGame from 'cloudbase-adapter-qq_game'; import adapter as adapterOfCocosNative from 'cloudbase-adapter-cocos_native'; import adapter as adapterOfBDGame from 'cloudbase-adapter-bd_game'; cloudbase.useAdapters([ adapterOfQQGame, adapterOfCocosNative, adapterOfBDGame ]); ``` ## 验证码处理指南 ### 1、概述 详细说明了处理验证码的完整流程,包括验证码触发场景、适配器改造、事件处理和用户交互。 ### 2、验证码触发场景 验证码会在以下情况被要求输入: - 用户名密码登录失败 5 次后 - 发送手机号或邮箱验证码时达到频率限制 > 需要验证码时,接口会返回如下错误信息 ```typescript { // ... data: { error: "captcha_required", error_code: 4001, error_description: "captcha_token required" } } ``` ### 3、完整处理流程 #### 适配器(Adapter)改造 ```typescript // options 通过 useAdapters 第二参数传入 // 例如:cloudbase.useAdapters(adapter, options); function genAdapter(options) { const adapter: SDKAdapterInterface = { // 其他适配器配置... captchaOptions: { // 当检测到需要验证码时,自动触发此函数 // url 为包含验证码图片、token、state等参数的字符串 openURIWithCallback: async (url: string) => { // 解析URL中的验证码参数 const { captchaData, state, token } = cloudbase.parseCaptcha(url); // 通过事件总线发送验证码数据,进行前端缓存及展示 options.EVENT_BUS.emit("CAPTCHA_DATA_CHANGE", { captchaData, // Base64编码的验证码图片 state, // 验证码状态标识 token, // 验证码token }); // 监听验证码校验结果 return new Promise((resolve) => { console.log("等待验证码校验结果..."); options.EVENT_BUS.once("RESOLVE_CAPTCHA_DATA", (res) => { // auth.verifyCaptchaData的校验结果 resolve(res); }); }); }, }, }; return adapter; } ``` #### 初始化与适配器配置 ```typescript // 创建事件总线实例 const EVENT_BUS = new EventBus(); // 配置并使用适配器,将 EVENT_BUS 注入给 genAdapter cloudbase.useAdapters(adapter, { EVENT_BUS }); const app = cloudbase.init({ env: '环境ID', appSign: '应用标识', appSecret: { appAccessKeyId: '应用凭证版本号' appAccessKey: '应用凭证' } }); const auth = app.auth(); ``` #### 验证码界面实现 ```typescript // 存储当前验证码状态 let captchaState = { captchaData: "", // Base64编码的验证码图片 state: "", // 验证码状态标识 token: "", // 验证码token }; // 监听验证码数据变化 EVENT_BUS.on("CAPTCHA_DATA_CHANGE", ({ captchaData, state, token }) => { console.log("收到验证码数据", { captchaData, state, token }); // 更新本地验证码状态 captchaState = { captchaData, state, token }; // 在页面中显示验证码图片,例如在web使用img标签展示方式如下 document.getElementById('captcha-image').src = captchaData; }); // 用户点击刷新验证码,触发该函数 const refreshCaptcha = async () => { try { // 获取最新验证码信息 const result = await auth.createCaptchaData({ state: captchaState.state }); // 更新本地验证码状态 captchaState = { ...captchaState captchaData: result.data, token: result.token, }; // 更新显示的验证码,例如在web使用img标签展示方式如下 document.getElementById('captcha-image').src = result.data; } catch (error) { console.error("刷新验证码失败", error); } }; // 用户提交验证码,触发该函数 const verifyCaptchaData = async (userCaptcha) => { try { // 校验验证码 const verifyResult = await auth.verifyCaptchaData({ token: captchaState.token, key: userCaptcha }); // 将校验结果通知适配器 EVENT_BUS.emit("RESOLVE_CAPTCHA_DATA", verifyResult); // 验证成功,继续登录流程 console.log("验证码校验成功"); } catch (error) { console.error("验证码校验失败", error); // 可以选择刷新验证码 await refreshCaptcha() } }; ``` #### 完整流程 1. 用户尝试登录或发送验证码 2. 若触发验证码要求,SDK 抛出 captcha_required 错误 3. 适配器捕获错误并调用 openURIWithCallback 4. 系统解析验证码参数并通过 EVENT_BUS 发送 5. 前端展示验证码图片并等待用户输入 6. 用户输入验证码并提交 7. 系统验证并返回结果 8. 根据验证结果决定是否重试原操作 #### 验证码展示效果 --- # JS SDK(V2)/跨端开发/UniApp 适配器 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/adapter/uniapp-adapter ## 概览 UniApp 适配器是云开发为 UniApp 框架提供的专用适配器,让开发者能够在 UniApp 项目中无缝使用云开发的完整功能。通过该适配器,您可以轻松实现跨平台的云端数据存储、用户认证、文件管理和云函数调用等。 ### 支持平台 目前已适配以下平台: - H5 端 - 微信小程序 - 支付宝小程序 - 抖音小程序 - iOS - Android ## 各平台展示效果 完整示例项目请参考:[CloudBase UniApp 模板](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/universal/cloudbase-uniapp-template) 各平台展示如下: | H5 端 | 微信小程序 | | :-----------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------: | | ![H5 端](https://qcloudimg.tencent-cloud.cn/raw/cec528e3e0d4dddadff11c66a11013cc.png) | ![微信小程序](https://qcloudimg.tencent-cloud.cn/raw/826666e480af55c2c886ffa1a451dea8.png) | | 支付宝小程序 | 抖音小程序 | | :------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------: | | ![支付宝小程序](https://qcloudimg.tencent-cloud.cn/raw/5fa5a46ae73b325bb1199b7e4059e480.png) | ![抖音小程序](https://qcloudimg.tencent-cloud.cn/raw/c4750c695aa81dab6cd3ef2de0aee8f0.png) | | Android 和 iOS | | :--------------------------------------------------------------------------------------------------: | | | ## 安装 使用 npm 安装: ```bash npm install @cloudbase/adapter-uni-app ``` ## 快速开始 ### 适配器配置 如果需要使用登录功能或自定义配置,需要传入 `options` 参数: :::tip 注意 `options` 传入后可以在适配器内部的 `genAdapter` 中接收和使用。详细说明请参考 [跨端开发指南](./adapter#验证码处理指南)。 - `uni` 对象是必需的,用于处理图形验证码功能。 ::: ```javascript import cloudbase from "@cloudbase/js-sdk"; import adapter from "@cloudbase/adapter-uni-app"; // 传入配置选项 const options = { uni: uni, // 传入 uni 对象,用于图形验证码功能 }; cloudbase.useAdapters(adapter, options); const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境 ID // 仅在App端需要配置 appSign: "your-app-sign", appSecret: { appAccessKeyId: 1, appAccessKey: "your-app-access-key", }, }); export default app; ``` :::warning 重要提示 - `adapter` 必须在 `cloudbase.init()` 之前调用 - 如果需要实现登录相关功能,必须传入 UniApp 的 `uni` 对象 - App 端需要额外配置应用标识和凭证信息 ::: ## 安全域名配置 云开发 SDK 在使用过程中,向云开发服务发送的请求都需要验证请求来源的合法性。以下是不同平台的域名配置方法: ### H5 端 开发环境域名: ``` http://localhost:本地端口号 ``` 生产环境需要配置您的实际域名。 ### 微信小程序 在微信小程序管理后台的【开发】→【开发管理】→【开发设置】→【服务器域名】中配置: **request 合法域名:** ``` https://tcb-api.tencentcloudapi.com https://your-env-id..app.tcloudbase.com ``` **uploadFile 合法域名:** ``` https://cos.ap-shanghai.myqcloud.com ``` **downloadFile 合法域名:** ``` https://your-env-id.tcb.qcloud.la https://cos.ap-shanghai.myqcloud.com ``` :::info 提示 请将 `your-env-id` 替换为您的实际环境 ID,地域根据您的云开发环境所在地域调整。 ::: ### 支付宝小程序 开发环境域名: ``` devappid.hybrid.alipay-eco.com ``` ### 抖音小程序 开发环境域名: ``` tmaservice.developer.toutiao.com ``` ### App 端配置 在云开发控制台的【环境配置】→【安全来源】→【移动应用安全来源】中添加应用: - **应用标识**:`your-app-sign` - **应用凭证**:`your-app-access-key` ```typescript const appConfig = { env: "your-env-id", appSign: "your-app-sign", // 应用标识 appSecret: { appAccessKeyId: 1, // 凭证版本 appAccessKey: "your-app-access-key", // 应用凭证 }, }; ``` ### 开发环境启动 ```bash # H5 开发 npm run dev:h5 # 微信小程序开发 npm run dev:mp-weixin # 抖音小程序开发 npm run dev:mp-toutiao # 支付宝小程序开发 npm run dev:mp-alipay # App (iOS/Android) 开发 # 1. 使用 HBuilderX 打开项目 # 2. 在顶部菜单栏选择【运行】->【运行到手机或模拟器】-> 选择您的设备 ``` :::info 平台支持说明 目前稳定支持 APP、H5、微信小程序、抖音小程序和支付宝小程序平台。 ::: ## 图形验证码机制说明 在某些安全敏感的操作场景下(如用户登录),腾讯云开发会要求进行图形验证码验证。UniApp Adapter 通过事件总线机制处理图形验证码交互。 ### 工作流程 1. 触发验证码 - 当云开发检测到需要图形验证码时,自动调用适配器的图形验证码处理函数 2. 解析数据 - 适配器解析图形验证码 URL,提取图片数据、token 和状态信息 3. 前端展示 - 通过事件通知前端页面显示图形验证码 4. 用户输入 - 用户查看验证码图片并输入答案 5. 结果回传 - 前端将用户输入结果通过事件发送回适配器 6. 完成验证 - 适配器接收结果并继续后续流程 ### 适配器内部图形验证码处理实现 在适配器内部,可以访问到传入的 `uni` 对象,通过事件总线发送图形验证码数据: ```javascript function genAdapter(options) { const adapter: SDKAdapterInterface = { // 其他适配器配置... captchaOptions: { // 当检测到需要验证码时,自动触发此函数 // _url 为包含验证码图片、token、state等参数的字符串 openURIWithCallback: (_url: string) => { // 在适配器内部使用传入的 uni 对象 const uni = options.uni // 解析URL中的验证码参数 const { captchaData, state, token } = cloudbase.parseCaptcha(_url) return new Promise((resolve) => { console.log('等待验证码输入...') // 发送验证码数据到前端 uni.$emit('CAPTCHA_DATA_CHANGE', { state, // 验证码状态标识 token, // 验证码token captchaData // Base64编码的验证码图片 }) // 监听验证码验证结果 uni.$once('RESOLVE_CAPTCHA_DATA', (res: { captcha_token: string expires_in: number }) => { resolve(res) }) }) } } } return adapter } ``` ### 前端验证码处理 在您的 UniApp 页面中,需要监听验证码事件并处理用户输入: ```javascript // 在页面中监听验证码事件 uni.$on("CAPTCHA_DATA_CHANGE", ({ state, token, captchaData }) => { console.log("收到验证码数据", { state, token, captchaData }); // 更新本地验证码状态 captchaState = { state, token, captchaData }; // 在页面中显示验证码图片... }); ``` ## 常见问题 ### Q: 为什么要在 init 之前调用 useAdapters? A: 适配器需要在 SDK 初始化之前注册,以确保 SDK 能够正确识别当前运行环境并使用相应的适配器。 ### Q: 如何处理不同平台的差异? A: UniApp Adapter 会自动检测当前运行平台并使用相应的适配逻辑,开发者无需手动处理平台差异。 ### Q: 图形验证码功能如何自定义样式? A: 图形验证码的显示和交互完全由开发者控制,您可以根据需要自定义图形验证码弹窗的样式和交互逻辑。 ### Q: 支持哪些云开发功能? A: 支持云开发的所有核心功能,包括: - 身份验证(匿名登录、自定义登录等) - 数据库(增删改查、实时数据库等) - 文件存储(文件上传、下载、删除等) - 云函数 - 云托管 --- # JS SDK(V2)/跨端开发/微信小程序适配器 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/adapter/weixin-adapter :::tip 提示 当前 **JS SDK** 已默认集成了微信小程序适配器,无需额外安装,如果需要进行适配器扩展,可以按下面步骤进行安装使用。 ::: ## 概览 微信小程序适配器是云开发为微信小程序提供的专用适配器。通过该适配器,开发者可以方便地在小程序中调用云函数、访问数据库、使用文件存储等功能。 ## 安装 ```bash npm install @cloudbase/adapter-wx_mp ``` ## 快速开始 ### 基础使用 ```javascript import cloudbase from '@cloudbase/js-sdk'; import adapter from '@cloudbase/adapter-wx_mp'; // 使用微信小程序适配器 cloudbase.useAdapters(adapter); const app = cloudbase.init({ env: 'your-env-id', // 替换为您的环境ID // 其他配置项 } }); export default app; ``` ### 高级配置 如果需要使用验证码功能或自定义事件总线,可以传入配置参数: ```javascript import cloudbase from '@cloudbase/js-sdk'; import adapter from '@cloudbase/adapter-wx_mp'; // 自定义事件总线(可选) const EventBus = { $emit: (event, data) => { // 处理事件发送逻辑 }, $on: (event, callback) => { // 处理事件监听逻辑 }, $once: (event, callback) => { // 处理一次性事件监听逻辑 }, $off: (event, callback) => { // 处理事件取消监听逻辑 } }; // cloudbase.useAdapters(adapter, {EventBus: uni}); cloudbase.useAdapters(adapter, { EventBus }); const app = cloudbase.init({ env: 'your-env-id', // 替换为您的环境ID // 其他配置项 } }); export default app; ``` :::warning 重要提示 - `adapter` 必须在 `cloudbase.init()` 之前调用 - 如果需要使用验证码功能,建议传入自定义的事件总线对象 ::: ### 配置参数说明 `options` 是一个可选的配置对象,传入后可以在适配器内部的 `genAdapter` 中接收和使用。详细说明请参考 [跨端开发指南](https://docs.cloudbase.net/api-reference/webv2/adapter#%E9%AA%8C%E8%AF%81%E7%A0%81%E5%A4%84%E7%90%86%E6%8C%87%E5%8D%97)。 ## 验证码处理 ### 验证码机制说明 在某些场景下(如登录时),云开发可能需要验证码验证。**微信小程序适配器**通过事件总线机制处理验证码交互。在初始化适配器时,可以传入一个事件总线对象,用于处理验证码相关事件。 ### 适配器验证码处理逻辑 以下是适配器内部的验证码处理逻辑: ```javascript import adapter from '@cloudbase/adapter-wx_mp'; function genAdapter(options) { const adapter: SDKAdapterInterface = { // 其他适配器配置... ...adapter.genAdapter(options), captchaOptions: { // 当检测到需要验证码时,自动触发此函数 // _url 为包含验证码图片、token、state等参数的字符串 openURIWithCallback: (_url: string) => { // 在适配器内部使用传入的 EventBus 对象 const EventBus = options.EventBus; // 解析URL中的验证码参数 const { captchaData, state, token } = cloudbase.parseCaptcha(_url) return new Promise((resolve) => { console.log('等待验证码输入...') // 发送验证码数据到前端 EventBus.$emit('CAPTCHA_DATA_CHANGE', { state, // 验证码状态标识 token, // 验证码token captchaData // Base64编码的验证码图片 }) // 监听验证码验证结果 EventBus.$once('RESOLVE_CAPTCHA_DATA', (res: { captcha_token: string expires_in: number }) => { resolve(res) }) }) } } } return adapter } ``` ### 前端处理验证码 在小程序前端页面中,可以通过事件总线监听验证码数据变化,并处理用户输入的验证码。以下是一个示例: ```javascript // 在页面中监听验证码事件 EventBus.$on("CAPTCHA_DATA_CHANGE", ({ state, token, captchaData }) => { console.log("收到验证码数据", { state, token, captchaData }); // 更新本地验证码状态 captchaState = { state, token, captchaData }; // 在页面中显示验证码图片... }); //... // 发送验证结果 EventBus.$emit("RESOLVE_CAPTCHA_DATA", result); ``` ## 完整示例 弹窗实现可以参考 awesome-cloudbase-examples 的 [uniapp 模版](https://github.com/TencentCloudBase/awesome-cloudbase-examples/blob/master/universal/cloudbase-uniapp-template/src/components/show-captcha.vue). --- # JS SDK(V2)/跨端开发/Node.js 适配器 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/adapter/node-adapter ## 概览 Node.js 适配器是为 Node.js 环境设计的,提供了对云开发服务的访问能力。它允许开发者在 Node.js 应用中使用云开发的功能,如数据库、存储、函数等。 ## 安装 使用 npm 安装 Node.js 适配器: ```bash npm i @cloudbase/adapter-node ``` ## 快速开始 ### 使用 ```javascript const cloudbase = require("@cloudbase/js-sdk"); const adapter = require("@cloudbase/adapter-node"); // 使用 Node.js 适配器 cloudbase.useAdapters(adapter); const app = cloudbase.init({ env: "your-env", // 需替换为实际使用环境 id }); ``` ## 使用示例 ### 云函数调用示例 ```javascript // 云函数调用示例 const result = await app.callFunction({ name: "your-function-name", data: { key: "value" } }); ``` ### 数据库操作示例 ```javascript // 数据库操作示例 const db = app.database(); // 创建测试集合 const testCollection = db.collection('test_collection'); // 插入测试数据 const insertResult = await testCollection.add({ name: "Node.js 测试", timestamp: new Date().toISOString(), }); ``` ### 文件上传示例 ```javascript // 上传文件示例 const fileName = `test-files/app-upload-${Date.now()}.jpg`; const fs = require('fs'); const testFilePath = '/your/local/path/XXX.jpg'; // 替换为你的本地文件路径 // 读取文件内容 const fileBuffer = fs.readFileSync(testFilePath); const uploadResult = await app.uploadFile({ cloudPath: fileName, filePath: fileBuffer, onUploadProgress: function (progressEvent) { console.log("上传进度:", progressEvent); var percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); } }); ``` --- # JS SDK(V2)/跨端开发/React Native 适配器 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/adapter/rn-adapter ## 概览 React Native 适配器是云开发为 React Native / Expo 框架提供的专用适配器,让开发者能够在 React Native 项目中无缝使用云开发的完整功能。通过该适配器,您可以轻松实现移动端的云端数据存储、用户认证、文件管理和云函数调用等。 ### 支持平台 目前已适配以下平台: - iOS 13+ - Android 6+ ### 环境要求 - Node.js >= 18 - React Native 0.76+ - Expo SDK 52+ ## 效果展示 完整示例项目请参考:[CloudBase React Native Demo](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/universal/cloudbase-rn-demo)

## 安装 使用 npm 安装: ```bash npm install @cloudbase/js-sdk @cloudbase/adapter-rn react-native-mmkv ``` :::tip 注意 `react-native-mmkv` 用于高性能持久化存储,建议使用 v3.x 版本,v4.x 需要 NitroModules 配置较复杂。 ::: ### iOS 原生依赖 ```bash cd ios && pod install && cd .. ``` ## 快速开始 ### 适配器配置 ```typescript import cloudbase from "@cloudbase/js-sdk"; import adapter from "@cloudbase/adapter-rn"; // 注册适配器 cloudbase.useAdapters(adapter); const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境 ID region: "ap-shanghai", // 地域 accekey: "your-app-access-key", // Publishable Key }); export default app; ``` :::warning 重要提示 `adapter` 必须在 `cloudbase.init()` 之前调用 ::: ## 图形验证码机制说明 在某些安全敏感的操作场景下(如用户登录),腾讯云开发会要求进行图形验证码验证。适配器内部已集成验证码处理流程: ```typescript captchaOptions: { openURIWithCallback: async (url: string): Promise => { const urlObj = new URL(url); const captchaData = urlObj.searchParams.get("captcha_data") || ""; const state = urlObj.searchParams.get("state") || ""; const token = urlObj.searchParams.get("token") || ""; return new Promise((resolve) => { if (captchaHandler) { captchaHandler({ captchaData, state, token, resolve }); } else { console.warn("No captcha handler registered"); resolve({ error: "no_handler" } as any); } }); }, } ``` 开发者需注册验证码处理器,参考 [图形验证码处理流程](https://docs.cloudbase.net/api-reference/webv2/adapter/#3%E5%AE%8C%E6%95%B4%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B): ```typescript import { setCaptchaHandler } from "@cloudbase/adapter-rn"; setCaptchaHandler(async ({ captchaData, state, token, resolve }) => { // 显示 captchaData (Base64编码的验证码图片) 给用户 // 用户输入验证码后调用 verifyCaptchaData 校验 const captchaToken = await auth.verifyCaptchaData({ token, key: "user-input-captcha-key", }); resolve(captchaToken); }); ``` ## 项目结构 ## 主要依赖 | 依赖 | 版本 | 说明 | | ---------------------------------------------------------------------------- | ------- | ------------------- | | @cloudbase/js-sdk | ^2.24.9 | 腾讯云开发 SDK | | [@cloudbase/adapter-rn](https://www.npmjs.com/package/@cloudbase/adapter-rn) | ^1.0.0 | React Native 适配器 | | react-native-mmkv | ^3.3.3 | 高性能持久化存储 | ## 常见问题 ### Q: 为什么要在 init 之前调用 useAdapters? A: 适配器需要在 SDK 初始化之前注册,以确保 SDK 能够正确识别当前运行环境并使用相应的适配器。 ### Q: 为什么推荐使用 react-native-mmkv v3.x? A: v4.x 版本需要 NitroModules 配置,配置较为复杂。v3.x 版本配置简单,性能稳定。 ### Q: 文件上传为什么需要使用 base64 编码? A: React Native 环境下,文件系统访问与 Web 环境不同,使用 base64 编码是最兼容的方式。上传时需设置 `contentEncoding: 'base64'`。 ## 相关资源 - [CloudBase 官方文档](https://docs.cloudbase.net/) - [@cloudbase/adapter-rn NPM 包](https://www.npmjs.com/package/@cloudbase/adapter-rn) - [React Native 官方文档](https://reactnative.dev/) --- # JS SDK(V2)/跨端开发/Cocos 适配器 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/adapter/cocos-adapter ## 概览 Cocos Native 适配器是云开发为 Cocos Creator 原生平台(iOS/Android)提供的专用适配器,让开发者能够在 Cocos Creator 原生项目中无缝使用云开发的完整功能。通过该适配器,您可以轻松实现移动端的云端数据存储、用户认证、文件管理和云函数调用等。 ### 支持平台 目前已适配以下平台: - Cocos Creator 支持的所有平台 ### 环境要求 - Node.js >= 22 - Cocos Creator 3.x - @cloudbase/js-sdk >= 2.25.1 - @cloudbase/adapter-cocos_native >= 1.0.0 ## 效果展示 完整示例项目请参考:[CloudBase Cocos Demo](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/universal/cloudbase-cocos-demo)

## 安装 使用 npm 安装: ```bash npm install @cloudbase/js-sdk @cloudbase/adapter-cocos_native ``` ## 快速开始 ### 适配器配置 ```typescript import cloudbase from "@cloudbase/js-sdk"; import adapter from "@cloudbase/adapter-cocos_native"; // 注册适配器 cloudbase.useAdapters(adapter); const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境 ID region: "ap-shanghai", // 地域 accessKey: "your-access-key", // Publishable Key }); export default app; ``` :::warning 重要提示 `adapter` 必须在 `cloudbase.init()` 之前调用 ::: ## 特性说明 - **网络请求**:使用 XMLHttpRequest 实现,已针对 Cocos Native 平台进行兼容性优化 - **本地存储**:使用 `cc.sys.localStorage`,原生环境不支持 sessionStorage - **AbortController**:内置 polyfill,支持请求中止,无需额外安装 ## 环境检测 适配器会自动检测当前是否为 Cocos Native 环境,检测条件包括: - `cc` 全局对象存在 - `cc.sys.isNative` 为 `true` - `XMLHttpRequest` 和 `WebSocket` 可用 ## 主要依赖 | 依赖 | 版本 | 说明 | | ------------------------------------------------------------------------------------------------ | -------- | ------------------- | | @cloudbase/js-sdk | >=2.25.1 | 腾讯云开发 SDK | | [@cloudbase/adapter-cocos_native](https://www.npmjs.com/package/@cloudbase/adapter-cocos_native) | ^1.0.0 | Cocos Native 适配器 | ## 常见问题 ### Q: 为什么要在 init 之前调用 useAdapters? A: 适配器需要在 SDK 初始化之前注册,以确保 SDK 能够正确识别当前运行环境并使用相应的适配器。 ### Q: 本地存储有什么限制? A: Cocos Native 环境使用 `cc.sys.localStorage` 进行本地存储,不支持 sessionStorage。 ### Q: 支持哪些版本的 Cocos Creator? A: 适配器支持 Cocos Creator 3.x 版本。 ## 相关资源 - [CloudBase 官方文档](https://docs.cloudbase.net/) - [@cloudbase/adapter-cocos_native NPM 包](https://www.npmjs.com/package/@cloudbase/adapter-cocos_native) - [Cocos Creator 官方文档](https://docs.cocos.com/creator/manual/zh/) --- # JS SDK(V2)/其他参考/Token 刷新与错误处理 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/auth_token ### credentials_error 错误说明 当使用 `@cloudbase/js-sdk` 时,登录态可能会因以下原因失效,触发 `credentials_error` 事件: 1. **refresh_token 过期**: `refresh_token` 超过有效期,无法刷新 `access_token`,需要用户重新登录 2. **Token 被更新**: 账号在其他设备登录,导致当前设备的 token 失效 ### 配置 Token 有效期 可以在 [云开发平台 - 身份认证 - Token 管理](https://tcb.cloud.tencent.com/dev?envId=#/identity/token-management) 中配置 Token 有效期: - **access_token 有效期**: 建议设置为 **7200 秒** (2小时) - **refresh_token 有效期**: 建议设置为 **2592000 秒** (30天) :::tip 提示 配置修改后,仅对新登录的用户生效。已登录用户的 token 有效期不会改变。 ::: ### 处理 credentials_error 事件 #### 基础处理 ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 监听登录态变化,处理 credentials_error auth.onLoginStateChanged((params) => { const { eventType, msg } = params?.data || {}; switch (eventType) { case "credentials_error": // 方式一: refresh_token 过期,需要重新登录 if (msg && msg.includes("refresh_token")) { console.warn("登录已过期,请重新登录"); // 跳转到登录页或调用登录弹窗 auth.toDefaultLoginPage(); } // 方式二: 其他设备登录,token 被更新 if (msg && msg.includes("token updated")) { console.warn("账号在其他设备登录,请重新验证"); // 提示用户重新登录 auth.toDefaultLoginPage(); } break; case "sign_out": // 处理登出逻辑 console.log("用户已登出"); break; case "sign_in": // 处理登录成功逻辑 console.log("用户已登录"); break; } }); ``` #### 小程序中的处理 在微信小程序中,可以结合 UI 提示优化用户体验: ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", }); const auth = app.auth(); auth.onLoginStateChanged((params) => { const { eventType, msg } = params?.data || {}; if (eventType === "credentials_error") { wx.showModal({ title: "登录过期", content: "您的登录已过期,请重新登录", confirmText: "重新登录", success: (res) => { if (res.confirm) { // 跳转到登录页面 wx.navigateTo({ url: "/pages/login/login", }); } }, }); } }); ``` #### 使用 Publishable Key 避免登录前的错误 如果需要在用户登录前查询数据模型(如展示首页文章列表),可以使用 **Publishable Key** + 匿名身份查询,避免 `credentials_error`: ```js import cloudbases from "@cloudbase/js-sdk/app"; import { registerAuth } from "@cloudbase/js-sdk/auth"; import { registerModel } from "@cloudbase/js-sdk/model"; registerAuth(cloudbases); registerModel(cloudbases); const app = cloudbases.init({ env: "your-env-id", accessKey: "", // 从控制台获取 }); // 登录前可以匿名查询 const models = app.models; const articles = await models.article.list(); // 需要鉴权时再登录 await app.auth().signInAnonymously(); ``` **获取 Publishable Key**: 1. 访问 [云开发平台 - API Key 管理](https://tcb.cloud.tencent.com/dev?envId=#/identity/api-key) 2. 创建或复制 **Publishable Key** 3. 在初始化时传入 `accessKey` 参数 ### 最佳实践 1. **全局监听**: 在应用启动时(如 `App.onLaunch`)注册 `onLoginStateChanged` 监听器 2. **用户提示**: 使用 Modal 或 Toast 提示用户登录已过期 3. **自动重定向**: 登录过期后自动跳转到登录页 4. **保存状态**: 记录用户当前页面路径,登录后跳转回原页面 --- # JS SDK(V2)/其他参考/开发环境错误日志提示 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/errlog `@cloudbase/js-sdk` 在开发环境下会将调用 API 的错误信息以更友好的方式打印到控制台: 图中打印的错误是当前的登录类型受到函数的安全规则限制,导致没有调用函数的权限。错误信息分为两部分: 1. 上半部分的黑色字体提示包含了后端 API 返回的错误信息以及基于此报错的解决方案提示 2. 下半部分的红色字体是经优化后的错误堆栈,由于原始错误堆栈层次太深导致 debug 困难,此处打印的错误堆栈第一条直接定位到 SDK 源码,第二条定位到调用报错 API 的业务源码 优化的错误提示仅在开发环境中使用,根据 `process.env.NODE_ENV` 判断是否为开发环境。定位到 SDK 源码需要借助 SourceMap。 以上功能依赖构建工具的部分特性,目前仅支持 Webpack 和 Rollup。 **Webpack 4+ 版本配置:** ```js module.exports = { mode: "development", }; ``` **旧版本 Webpack 配置:** ```js const webpack = require("webpack"); module.exports = { plugins: [ new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development"), }), // ...其他插件 ], // ...其他配置 }; ``` **添加 sourcemap 预处理:** ```js module.exports = { module: { rules: [ { test: /\.js$/, enforce: "pre", use: ["source-map-loader"], }, // ...其他rules ], }, // ...其他配置 }; ``` 配置完成后启动 webpack-dev-server 即可。 Rollup 需要使用两个插件: - [rollup-plugin-replace](https://github.com/rollup/rollup-plugin-replace) 用来注入`process.env.NODE_ENV` - [rollup-plugin-sourcemaps](https://github.com/maxdavidson/rollup-plugin-sourcemaps) 用来加载 sourcemap ```js import sourcemaps from "rollup-plugin-sourcemaps"; import replace from "rollup-plugin-replace"; export default { plugins: [ sourcemaps(), replace({ "process.env.NODE_ENV": JSON.stringify("development"), }), // ...其他插件 ], // ...其他配置 }; ``` 配置完成后使用 [rollup-plugin-serve](https://github.com/thgh/rollup-plugin-serve) 启动 dev server 即可。 --- # JS SDK(V2)/其他参考/从 cloudbase js sdk v1 迁移 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/migration ## 登录认证 从 v1 到 v2,登录认证模块有以下变化: - 登录状态持久化:登录状态只支持 `local` 模式 - v2 版本不再支持监听用户登录状态改变 - **v2 版本不再支持公众号登录方式**,如需使用该方式,请用 [v1 版本](/authentication/method/wechat-login) - 用户字段信息精简化,详见 [User](#user) - 对账户关联使用方式进行了调整,并且不再支持关联自定义登录,详见「[登录认证 v2 |账户关联](../../authentication-v2/auth/account-linking.md)」 - 对登录的使用方式进行了调整,详见「[登录认证 v2 |登录认证](../../authentication-v2/method/anonymous.md)」 ### 接口调整 #### Auth ##### 更新 - 接口命名 typo 修正:不再使用 `Auth.getCurrenUser()`,请使用 [`Auth.getCurrentUser()`](./authentication.md#authgetcurrentuser) ##### 新增 - [Auth.bindEmail](./authentication.md#authbindemail) - [Auth.bindPhoneNumber](./authentication.md#authbindphonenumber) - [Auth.bindWithProvider](./authentication.md#authbindwithprovider) - [Auth.deleteMe](./authentication.md#authdeleteme) - [Auth.genProviderRedirectUri](./authentication.md#authgenproviderredirecturi) - [Auth.getAccessToken](./authentication.md#authgetaccesstoken) - [Auth.getProviders](./authentication.md#authgetproviders) - [Auth.getUserInfo](./authentication.md#authgetuserinfo) - [Auth.getVerification](./authentication.md#authgetverification) - [Auth.grantProviderToken](./authentication.md#authgrantprovidertoken) - [Auth.loginScope](./authentication.md#authloginscope) - [Auth.queryUser](./authentication.md#authqueryuser) - [Auth.resetPassword](./authentication.md#authresetpassword) - [Auth.setCustomSignFunc](./authentication.md#authsetcustomsignfunc) - [Auth.setPassword](./authentication.md#authsetpassword) - [Auth.signIn](./authentication.md#authsignin) - [Auth.signInAnonymously](./authentication.md#authsigninanonymously) - [Auth.signInWithCustomTicket](./authentication.md#authsigninwithcustomticket) - [Auth.signInWithOpenId](./authentication.md#authsigninwithopenid) - [Auth.signInWithPhoneAuth](./authentication.md#authsigninwithphoneauth) - [Auth.signInWithProvider](./authentication.md#authsigninwithprovider) - [Auth.signInWithSms](./authentication.md#authsigninwithsms) - [Auth.signInWithEmail](./authentication.md#signInWithEmail) - [Auth.signInWithUnionId](./authentication.md#authsigninwithunionid) - [Auth.signUp](./authentication.md#authsignup) - [Auth.sudo](./authentication.md#authsudo) - [Auth.unbindProvider](./authentication.md#authunbindprovider) - [Auth.verify](./authentication.md#authverify) ##### 废弃 - Auth.anonymousAuthProvider - Auth.customAuthProvider - Auth.forceResetPwdByPhoneCode - Auth.getAuthHeader - Auth.getAuthHeaderAsync - Auth.onAccessTokenRefreshed - Auth.onAnonymousConverted - Auth.onLoginStateExpired - Auth.onLoginTypeChanged - Auth.sendPasswordResetEmail - Auth.sendPhoneCode - Auth.shouldRefreshAccessToken - Auth.signInWithEmailAndPassword - Auth.signInWithPhoneCodeOrPassword - Auth.signInWithUsernameAndPassword - Auth.signUpWithEmailAndPassword - Auth.signUpWithPhoneCode - Auth.weixinAuthProvider #### LoginState ##### 废弃 - LoginState.isAnonymousAuth - LoginState.isCustomAuth - LoginState.isUsernameAuth - LoginState.isWeixinAuth - LoginState.loginType #### User ##### 废弃 - User.avatarUrl - User.linkWithPhoneNumber - User.nickName - User.updateEmail - User.updatePassword - User.updatePhoneNumber - User.updateUsername --- # JS SDK(V2)/其他参考/常见问题 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/faq ### 查询条件正确但结果为空 在使用数据库查询时,如果返回空结果,通常有以下两种情况: 1. 没有符合查询条件的数据 2. 数据被权限控制过滤 #### 排查方法 1. **确认数据存在性** - 在云开发控制台直接查看集合中是否存在目标数据 - 检查数据的创建时间和字段值是否符合预期 2. **检查权限配置** - 查看集合的基础权限设置是否允许当前用户读取 - 数据库查询时会以 `_openid` 字段作为数据归属判定依据 - 如果使用安全规则,验证规则表达式是否正确 - 确认查询条件是否包含安全规则要求的必要字段 3. **验证查询条件** - 简化查询条件,逐步排查哪个条件导致结果为空 - 检查字段名称、数据类型和查询语法是否正确 --- # JS SDK(V2)/其他参考/Web SDK API V1 和 V2 不兼容问题 > 当前文档链接: https://docs.cloudbase.net/faq/knowledge/web-sdk-v1-v2-compatibility-issue 云开发 Web SDK 的 API V1 和 V2 版本不互通,混用会导致登录错误。 ## 问题现象 当您在项目中同时使用 Web SDK API V1 和 V2 时,可能会出现以下错误: - 控制台显示 `v1 sign in` 错误 - 用户无法正常登录 - 数据库访问失败 ## 问题原因 Web SDK API V1 和 V2 是两个独立的版本,它们的登录态、认证机制和调用方式均不兼容。因此: - 使用 V1 SDK 登录后,无法使用 V2 SDK 调用接口 - 使用 V2 SDK 登录后,无法使用 V1 SDK 调用接口 两个版本的调用方式、登录态管理机制完全不同,不能混用。 ## 解决方案 ### 方案一:统一使用 V2 SDK(推荐) V2 SDK 是最新版本,功能更完善,建议在新项目中使用。 ```javascript // 初始化 V2 SDK import cloudbase from '@cloudbase/js-sdk'; const app = cloudbase.init({ env: 'your-env-id' }); // 登录 const auth = app.auth(); await auth.anonymousAuthProvider().signIn(); // 调用数据库 const db = app.database(); const result = await db.collection('users').get(); ``` ### 方案二:统一使用 V1 SDK 如果已有项目使用 V1 SDK,可以继续使用,但不要混用。 ```javascript // 初始化 V1 SDK import cloudbase from '@cloudbase/js-sdk/dist/cloudbase.js'; const app = cloudbase.init({ env: 'your-env-id' }); // 登录 await app.auth({ persistence: 'local' }); // 调用数据库 const db = app.database(); const result = await db.collection('users').get(); ``` ### 方案三:项目迁移 如果需要从 V1 迁移到 V2,建议参考官方文档进行完整迁移: - [Web SDK V2 文档](/api-reference/webv2/initialization) - [SDK 迁移指南](/api-reference/webv2/migration) ## 注意事项 1. **不要混用**:在同一个项目中不要同时使用 V1 和 V2 SDK 2. **保持一致**:所有模块(数据库、存储、云函数等)使用同一版本 SDK 3. **登录态独立**:V1 和 V2 的登录态是独立的,无法共享 ## 相关文档 - [Web SDK V2 快速开始](https://docs.cloudbase.net/client/sdk/web) - [Web SDK V1 文档](https://docs.cloudbase.net/client/sdk/v1) - [数据库开发指南](https://docs.cloudbase.net/database/introduce) --- # JS SDK(V2)/其他参考/更新日志 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/changelog ## [2.28.4] 2026-05-06 - [Fixed] getUser 返回用户信息支持 open_id 字段 ## [2.28.3] 2026-04-30 - [Added] 文档型数据库支持批量新增数据 ## [2.28.2] 2026-04-29 - [Added] 类型定义 ## [2.28.1] 2026-04-23 - [Added] 海外支持 ## [2.27.5] 2026-04-21 - [Added] database where 条件支持事务 ## [2.27.4] 2026-04-17 - [Fixed] signUp 修复 phone_number 为空时格式化报错的问题 ## [2.27.3] 2026-04-13 - [Added] getUser 支持读缓存和读接口数据可选 ## [2.27.2] 2026-04-02 - [Added] signInWithOtp 支持 emailRedirectTo ## [2.27.1] 2026-03-24 - [Added] 增加帮助信息 ## [2.27.0] 2026-03-23 - [Added] 小程序手机号授权支持服务商类型小程序 ## [2.26.2] 2026-03-13 - [Added] 增强类型声明以及错误提示 ## [2.26.1] 2026-03-12 - [Fixed] getVerification 增加 phone_number 参数格式化 ## [2.26.0] 2026-03-12 - [Added] signInWithOtp 增加可选参数 options.shouldCreateUser 控制是否自动注册用户 ## [2.25.11] 2026-03-11 - [Fixed] verifyOAuth 返回预设重定向地址 ## [2.25.10] 2026-03-10 - [Fixed] verifyOAuth 兼容 hash 地址 ## [2.25.9] 2026-03-06 - [Fixed] 修复 HBuilderX 找不到 SourceMap 问题 ## [2.25.8] 2026-03-03 - [Changed] 更新微信小程序端 adapter ## [2.25.5] 2026-02-04 - [Fixed] 修复用户信息 role 获取问题 ## [2.25.4] 2026-02-04 - [Added] encryptlong 兼容多端 ## [2.25.3] 2026-01-28 - [Fixed] GET/HEAD 请求时,将请求体设置为空 - [Fixed] auth\.wsWebSign action 一直走 tcb api 请求 ## [2.25.2] 2026-01-26 - [Added] 新版本 auth api 上线 ## [2.25.1] 2026-01-22 - [Added] 更新 database 依赖版本 ## [2.25.0] 2026-01-22 - [Changed] 构建 CI 发布、Node.js 版本升级至 22、代码优化 ## [2.24.10] 2026‑01‑14 - [Changed] 更新 getDeviceInfo 和错误处理 ## [2.24.9] 2026‑01‑13 - [Changed] 更新数据库依赖至 0.10.0、多认证支持、清理构建文件 ## [2.24.8] 2026‑01‑12 - [Added] 添加混元 AR 图像模型支持 ## [2.24.7 及更早版本] 2025-12-29 至 2026-01-12 - 略 ## [1.4.1] 2021-03-08 - [Fixed] 修复上传文件进度获取异常 bug ## [1.4.0] 2020-12-16 - [Added] 新增 analytics 接口 ## [1.3.3] 2020-09-25 - [Changed] 优化 TypeScript 语法提示 - [Fixed] 修复未登录调用数据库报错问题 ## [1.3.2] 2020-09-24 - [Fixed] 微信小程序插件环境获取 appSign 取插件 AppId - [Fixed] storage 满仓情况下写入抛出错误 ## [1.3.1] 2020-09-23 - [Fixed] 兼容微信小程序插件环境 ## [1.3.0] 2020-09-11 - [Changed] 数据库实时推送功能抽离为独立的模块 ## [1.2.5] 2020-09-07 - [Fixed] 修复微信小程序真机环境报错 ## [1.2.3] 2020-09-03 - [Changed] 优化 API 语法提示 ## [1.2.2] 2020-09-01 - [Changed] 优化开发环境的错误信息 ## [1.2.1] 2020-08-26 - [Added] 新增 `Auth.getAuthHeaderAsync` API ## [1.1.4] 2020-08-25 - [Fixed] 修复微信公众号登录 Bug - [Fixed] 修复微信小程序上传文件 Bug ## [1.1.2] 2020-08-24 - [Fixed] 修复小程序无法直接引用 npm 包问题 ## [1.1.1] 2020-08-19 - [Fixed] 修复用户名密码登录 bug ## [1.1.0] 2020-08-18 - [Added] 增加 `cloudbase.registerSdkName` API ## [1.0.4] 2020-08-13 - [Fixed] 修复实时推送多环境混淆问题 ## [1.0.3] 2020-08-11 - [Fixed] 修复由 Crypto-JS 引起的打包体积过大问题 ## [1.0.2] 2020-08-10 - [Changed] 优化版本统计信息 ## [1.0.1] 2020-08-05 - [Fixed] `Storage.downloadFile` 可将文件直接下载到本地 ## [1.0.0] 2020-07-31 - [Added] 发布 1.0.0 版本,旧版本 SDK [tcb-js-sdk](https://github.com/TencentCloudBase/tcb-js-sdk) 未来不再增加新功能 --- # server_nodesdk Documentation > 云开发NODE-SDK API文档, 包含云函数、数据库、存储、文件、认证、托管等API文档 # Node SDK/初始化 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/initialization [![NPM Version](https://img.shields.io/npm/v/@cloudbase/node-sdk)](https://www.npmjs.com/package/@cloudbase/node-sdk) ![node (scoped)](https://img.shields.io/node/v/@cloudbase/node-sdk) :::warning 迁移提示 `@cloudbase/node-sdk` 已停止维护(4.x next 版本完全基于 `@cloudbase/js-sdk` 设计)。请直接使用 [`@cloudbase/js-sdk` v3](/api-reference/webv3/initialization) 进行开发,该版本已支持 Node.js 端全部功能。 ::: **@Cloudbase/node-sdk** 让您可以在服务端使用 Node.js 服务访问 Cloudbase 服务和资源 > ⚠️ 注意:从 v3 版本开始,如在初始化 `init({})` 未指定 `env` 参数,则默认使用当前云函数环境 ID,不再使用云开发默认环境。如需要使用云开发默认环境,可以指定 `init({env: tcb.SYMBOL_DEFAULT_ENV})`。 ## 安装 SDK ```bash # npm npm install @cloudbase/node-sdk -S # yarn yarn add @cloudbase/node-sdk ``` ## 初始化 SDK ### 基本用法 ```js import tcb from "@cloudbase/node-sdk"; // 云函数环境使用 commonjs 引入方式 // const tcb = require('@cloudbase/node-sdk') const app = tcb.init({ env: "your-env-id", // 替换为您的环境 ID }); ``` ### 初始化参数 | 字段 | 类型 | 必填 | 说明 | | -------------- | --------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `env` | `string` | 否 | TCB 环境 ID | | `accessKey` | `string` | 否 | Cloudbase API Key,优先级最高的鉴权方式。也可通过环境变量 `CLOUDBASE_APIKEY` 配置 | | `secretId` | `string` | 否 | 腾讯云 API 固定密钥对 `secretId`,前往 [腾讯云控制台/API 密钥管理](https://console.cloud.tencent.com/cam/capi) 生成。也可通过环境变量 `TENCENTCLOUD_SECRETID` 配置 | | `secretKey` | `string` | 否 | 腾讯云 API 固定密钥对 `secretKey`,前往 [腾讯云控制台/API 密钥管理](https://console.cloud.tencent.com/cam/capi) 生成。也可通过环境变量 `TENCENTCLOUD_SECRETKEY` 配置 | | `sessionToken` | `string` | 否 | 腾讯云 API 临时密钥对 `Token`,通过 [申请扮演角色临时访问凭证](https://cloud.tencent.com/document/product/1312/48197) 接口获取 | | `credentials` | `object` | 否 | Cloudbase 私钥,包含 `private_key` 和 `private_key_id` 两个字符串,用于自定义登录场景签发 ticket
登录 [云开发平台/身份认证/登录方式](https://tcb.cloud.tencent.com/dev?#/identity/login-manage),通过自定义登录「私钥下载」获取 | | `context` | `Context` | 否 | 函数型云托管入口函数 context 参数,用于云托管场景的免签名调用 | | `timeout` | `number` | 否 | 调用接口的超时时间(ms),默认为 `15000ms`,即 `15s` | | `proxy` | `string` | 否 | 调用接口时使用的 http 代理 url | | `version` | `string` | 否 | 版本号,依赖项目的版本号 | ## 登录鉴权 **node-sdk** 使用不同环境下的鉴权方式如下: > SDK 初始化时可按以下方式选择鉴权方式(但只需选择其中一种): | 鉴权方式 | 说明 | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | 使用默认鉴权方式 | 用户在 `init` 中无需传入,自动从环境变量读取 `secretId` 和 `secretKey`,使用管理员角色身份进行访问 | | 使用客户端 `Publishable Key`(显式配置) | 用户在 `init` 中显式传入 `Publishable Key`,使用匿名角色身份进行访问 | | 使用服务端 `API Key` | 用户在 `init` 中无需传入,自动从环境变量读取 `CLOUDBASE_APIKEY`,将服务端 `API Key` 配置到环境变量 `CLOUDBASE_APIKEY` 中 | | `secretId` + `secretKey`(显式配置) | 用户在 `init` 中显式传入 `secretId` 和 `secretKey`,使用管理员角色身份进行访问 | | `sessionToken`(显式配置) | 用户在 `init` 中显式传入 `sessionToken`,使用扮演角色临时访问凭证 | | `context.extendedContext` | 云托管环境自动从 context 中获取临时密钥和环境 ID | ### 鉴权示例 用户在 `init` 中无需传入,自动从环境变量读取 `secretId` 和 `secretKey` ```js import tcb from "@cloudbase/node-sdk"; const app = tcb.init({ env: "your-env-id", }); ``` Publishable Key 详细介绍请 [参考文档说明](/api-reference/webv3/api-key#publishable-key-and-login-authentication) 。 - **用户权限**:匿名用户权限 - **有效期**:长期有效 - **获取方式**:通过[云开发平台/ApiKey 管理](https://tcb.cloud.tencent.com/dev?#/identity/token-management)获取 ```js import tcb from "@cloudbase/node-sdk"; const app = tcb.init({ env: "your-env-id", accessKey: "your-publishable-key", }); ``` 可以通过环境变量 `CLOUDBASE_APIKEY` 配置,在云函数中开启 `API key设置`,添加 `CLOUDBASE_APIKEY`。 ```js import tcb from "@cloudbase/node-sdk"; // 无需显式传入 accessKey,SDK 自动从环境变量读取 const app = tcb.init({ env: "your-env-id", }); ``` 前往 [腾讯云控制台/API 密钥管理](https://console.cloud.tencent.com/cam/capi) 生成 `secretKey`、`secretId` ```js import tcb from "@cloudbase/node-sdk"; const app = tcb.init({ env: "your-env-id", secretId: "your-secretId", secretKey: "your-secretKey", }); ``` 通过 [申请扮演角色临时访问凭证](https://cloud.tencent.com/document/product/1312/48197) 接口获取 `Token` ```js import tcb from "@cloudbase/node-sdk"; const app = tcb.init({ env: "your-env-id", sessionToken: "your-sessionToken", }); ``` ### 场景示例 在云函数环境中,SDK 会自动从环境变量中读取鉴权信息,**无需手动配置鉴权参数**。 ```js import tcb from "@cloudbase/node-sdk"; const app = tcb.init({ env: tcb.SYMBOL_DEFAULT_ENV, // 使用当前云函数默认环境 ID }); ``` 在函数型云托管环境中,通过传入 `context` 参数实现免签名调用。 > 💡 注意:如果传入了 `context` 参数但没有 `env` 参数,则会使用 `context` 中的 `envID` 参数作为环境 ID。 ```js import tcb from "@cloudbase/node-sdk"; exports.main = async (event, context) => { const app = tcb.init({ context, }); }; ``` --- # Node SDK/环境信息 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/env ## tcb.SYMBOL_CURRENT_ENV 字段说明: 初始化时使用该字段,可指定请求当前云函数的环境 > 注意:从 v3 版本开始,如在初始化 `init({})` 未指定 `env` 参数,则默认使用当前云函数环境 ID,不再使用云开发默认环境。如需要使用云开发默认环境,可以指定 `init({env: tcb.SYMBOL_DEFAULT_ENV})`。 ### 示例代码 ```ts import tcb from '@cloudbase/node-sdk' // 取当前云函数的环境初始化 const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV }) exports.main = async (event, context) => { // 业务逻辑 } ``` ## parseContext ### 1. 接口描述 接口功能:解析云函数环境下的环境变量(参数取用 云函数入口参数 `context` 即可) 接口声明:`parseContext(context): Object` ### 2. 返回结果 | 字段 | 类型 | 必填 | 说明 | | -------------------- | -------- | -- | ------------------------------------------ | | `memory_limit_in_mb` | `Number` | 是 | 云函数内存限制 | | `time_limit_in_ms` | `Number` | 是 | 运行时间限制 | | `request_id` | `String` | 是 | 请求 ID | | `environ` | `Object` | 是 | 环境变量对象(含用户设置的自定义环境变量值) | | `function_version` | `String` | 是 | 云函数版本 | | `function_name` | `String` | 是 | 云函数名 | | `namespace` | `String` | 是 | 命名空间 | ### 3. 示例代码 ```ts import tcb from '@cloudbase/node-sdk' exports.main = async (event, context) => { // context 参数 取自云函数入口函数handler的context对象 const envObj = tcb.parseContext(context) // 打印云函数环境变量 console.log(envObj) } ``` --- # Node SDK/文档型数据库/查询数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/database/fetch ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.database(); const _ = db.command; // 获取查询指令 ``` ## 单条查询 通过文档 ID 查询指定记录。 ```js db.collection(collectionName).doc(docId).get() ``` - **collectionName**:集合名称 - **docId**: 文档 ID ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ---------------- | | docId | string | 是 | 文档的唯一标识符 | ### 代码示例 ```javascript // 根据文档 ID 查询单条记录 const result = await db.collection('todos') .doc('docId') .get() ``` ### 返回结果 ```javascript { data: [{ _id: "todo-id-123", title: "学习 CloudBase", completed: false, // ... 其他字段 }], } ``` ## 多条查询 查询集合中的多条记录,支持条件筛选、排序、分页等。 ```js db.collection(collectionName).where(conditions).get() ``` - **collectionName**:集合名称 - **conditions**: 查询条件(可选) > ⚠️ 注意:`get()` 方法默认返回100条数据,如需更多数据请使用分页 ### 参数说明 | 方法 | 参数类型 | 必填 | 说明 | | --------- | -------------- | ---- | --------------------------------------------- | | where() | object | 否 | 查询条件,支持操作符 | | orderBy() | string, string | 否 | 排序字段和方向('asc' 或 'desc') | | limit() | number | 否 | 限制返回记录数,默认 100 条,最多返回 1000 条 | | skip() | number | 否 | 跳过记录数,用于分页 | | field() | object | 否 | 指定返回字段,true 表示返回,false 表示不返回 | ```javascript // 查询所有记录 const result = await db.collection('todos').get() // 条件查询 const result = await db.collection('todos') .where({ completed: false, priority: 'high' }) .get() ``` ## 复杂查询 ```javascript const _ = db.command // 复杂条件查询 const result = await db.collection('todos') .where({ // 年龄大于 18 age: _.gt(18), // 标签包含 '技术' tags: _.in(['技术', '学习']), // 创建时间在最近一周内 createdAt: _.gte(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)) }) .orderBy('createdAt', 'desc') // 按创建时间倒序 .limit(10) // 限制 10 条 .skip(0) // 跳过 0 条(分页) .field({ // 只返回指定字段 title: true, completed: true, createdAt: true }) .get() ``` ## 分页查询 ```javascript // 分页查询示例 const pageSize = 10 const pageNum = 1 const result = await db.collection('todos') .orderBy('createdAt', 'desc') .skip((pageNum - 1) * pageSize) .limit(pageSize) .get() ``` ## 聚合查询 聚合语法具体参考 [聚合查询](/api-reference/server/node-sdk/database/aggregate/) ```javascript // 统计查询 const result = await db.collection('todos') .aggregate() .group({ _id: '$priority', count: { $sum: 1 } }) .end() console.log('按优先级统计:', result.list) ``` ## 地理位置查询 > ⚠️ 注意:对地理位置字段进行查询时,请建立地理位置索引,否则查询将失败 ```js const _ = db.command // 查询附近的用户(按距离排序) db.collection("users").where({ location: _.geoNear({ geometry: new db.Geo.Point(116.404, 39.915), // 天安门坐标 maxDistance: 1000, // 最大距离1000米 minDistance: 0 // 最小距离0米 }) }).get() // 查询指定区域内的用户 db.collection("users").where({ location: _.geoWithin({ geometry: polygon // 使用上面定义的多边形 }) }).get() // 查询与路径相交的用户 db.collection("users").where({ location: _.geoIntersects({ geometry: line // 使用上面定义的路径 }) }).get() ``` --- # Node SDK/文档型数据库/新增数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/database/add ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.database(); const _ = db.command; // 获取查询指令 ``` ## 单条新增 向集合中添加一条新记录。 ```js db.collection(collectionName).add(data) ``` - **collectionName**:集合名称 - **data**: 要新增的数据对象 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ---- | ------ | ---- | ---------------- | | data | object | 是 | 要新增的数据对象 | ### 代码示例 ```javascript // 添加单条记录 const result = await db.collection('todos').add({ title: '学习 CloudBase', content: '完成数据库操作教程', completed: false, priority: 'high', createdAt: new Date(), tags: ['学习', '技术'] }) console.log('新增成功,文档 ID:', result._id) ``` ## 地理位置新增 ```js // 创建地理位置点 const point = new db.Geo.Point(longitude, latitude); // 创建地理路径 const line = new db.Geo.LineString([ new db.Geo.Point(lngA, latA), new db.Geo.Point(lngB, latB) ]); // 创建地理区域 const polygon = new db.Geo.Polygon([ new db.Geo.LineString([ new db.Geo.Point(lngA, latA), new db.Geo.Point(lngB, latB), new db.Geo.Point(lngC, latC), new db.Geo.Point(lngA, latA) // 闭合 ]) ]); const result = await db.collection('todos').add({ location: point, path: line, area: polygon }) ``` --- # Node SDK/文档型数据库/更新数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/database/update ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.database(); const _ = db.command; // 获取查询指令 ``` ## 单条更新 通过文档 ID 更新指定记录。 ```js db.collection(collectionName).doc(docId).update(data) ``` - **collectionName**:集合名称 - **docId**: 文档 ID - **data**: 要更新的数据对象 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ---------------- | | docId | string | 是 | 要更新的文档 ID | | data | object | 是 | 要更新的数据对象 | ### 代码示例 ```javascript // 更新指定文档 const result = await db.collection('todos') .doc('todo-id') .update({ title: '学习 CloudBase 数据库', completed: true, updatedAt: new Date(), completedBy: 'user123' }) ``` ## 批量更新 根据查询条件批量更新多条记录。 ```js db.collection(collectionName).where(condition).update(data) ``` - **collectionName**:集合名称 - **condition**: 查询条件 - **data**: 要更新的数据对象 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | -------------------------- | | where | object | 是 | 查询条件,确定要更新的记录 | | data | object | 是 | 要更新的数据对象 | ### 代码示例 ```javascript // 批量更新多条记录 const batchResult = await db.collection('todos') .where({ completed: false, priority: 'low' }) .update({ priority: 'normal', updatedAt: new Date() }) ``` ## 更新或创建 更新文档,如果不存在则创建: ```javascript const setResult = await db.collection('todos') .doc("doc-id") .set({ completed: false, priority: 'low' }) ``` --- # Node SDK/文档型数据库/删除数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/database/delete ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.database(); const _ = db.command; // 获取查询指令 ``` ## 单条删除 通过文档 ID 删除指定记录。 ```js db.collection(collectionName).doc(docId).remove() ``` - **collectionName**:集合名称 - **docId**: 要删除的文档 ID ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | --------------- | | docId | string | 是 | 要删除的文档 ID | ### 代码示例 ```javascript // 删除指定文档 const result = await db.collection('todos') .doc('todo-id') .remove() ``` ## 批量删除 根据查询条件批量删除多条记录。 ```js db.collection(collectionName).where(condition).remove() ``` - **collectionName**:集合名称 - **condition**: 查询条件 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | -------------------------- | | where | object | 是 | 查询条件,确定要删除的记录 | ### 代码示例 ```javascript // 批量删除多条记录 const _ = db.command const batchResult = await db.collection('todos') .where({ completed: true, createdAt: _.lt(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)) // 30天前 }) .remove() ``` --- # Node SDK/文档型数据库/操作符 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/database/command CloudBase Node SDK 提供了完整的数据库操作能力,本文将着重介绍查询相关能力 `db.command` 对象用于指定数据库操作符,我们一般如下定义command方便使用: ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.database(); const _ = db.command; ``` ### 查询操作符 | 操作符 | 说明 | 示例 | | ------ | ------------ | ------------------------------- | | `eq` | 等于 | `_.eq(value)` | | `neq` | 不等于 | `_.neq(value)` | | `gt` | 大于 | `_.gt(value)` | | `gte` | 大于等于 | `_.gte(value)` | | `lt` | 小于 | `_.lt(value)` | | `lte` | 小于等于 | `_.lte(value)` | | `in` | 包含于数组 | `_.in([value1, value2])` | | `nin` | 不包含于数组 | `_.nin([value1, value2])` | | `and` | 逻辑与 | `_.and(condition1, condition2)` | | `or` | 逻辑或 | `_.or(condition1, condition2)` | ### 字段更新操作符 | 操作符 | 说明 | 示例 | | --------- | ------------ | ------------------ | | `set` | 设置字段值 | `_.set(value)` | | `inc` | 数值自增 | `_.inc(1)` | | `mul` | 数值自乘 | `_.mul(2)` | | `remove` | 删除字段 | `_.remove()` | | `push` | 数组末尾添加 | `_.push(value)` | | `pop` | 数组末尾删除 | `_.pop()` | | `unshift` | 数组开头添加 | `_.unshift(value)` | | `shift` | 数组开头删除 | `_.shift()` | ### 地理位置操作符 | 操作符 | 说明 | 示例 | | --------------- | ------------------------ | -------------------------- | | `geoNear` | 按距离查询附近位置 | `_.geoNear(options)` | | `geoWithin` | 查询指定区域内的位置 | `_.geoWithin(options)` | | `geoIntersects` | 查询与指定图形相交的位置 | `_.geoIntersects(options)` | --- # Node SDK/文档型数据库/聚合操作/API/Aggregate > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/database/aggregate/aggregate 数据库集合的聚合操作实例 ### [Aggregate.addFields(object: Object): Aggregate](./stages/addFields) 描述: 聚合阶段。添加新字段到输出的记录。经过 addFields 聚合阶段,输出的所有记录中除了输入时带有的字段外,还将带有 addFields 指定的字段。 ### [Aggregate.bucket(object: Object): Aggregate](./stages/bucket) 描述: 聚合阶段。将输入记录根据给定的条件和边界划分成不同的组,每组即一个 bucket。 ### [Aggregate.bucketAuto(object: Object): Aggregate](./stages/bucketAuto) 描述: 聚合阶段。将输入记录根据给定的条件划分成不同的组,每组即一个 bucket。与 bucket 的其中一个不同之处在于无需指定 boundaries,bucketAuto 会自动尝试将记录尽可能平均的分散到每组中。 ### [Aggregate.count(fieldName: string): Aggregate](./stages/count) 描述: 聚合阶段。计算上一聚合阶段输入到本阶段的记录数,输出一个记录,其中指定字段的值为记录数。 ### [Aggregate.end(): Promise\](./stages/end) 描述: 聚合阶段。标志聚合操作定义完成,发起实际聚合操作 ### [Aggregate.geoNear(options: Object): Aggregate](./stages/geoNear) 描述: 聚合阶段。将记录按照离给定点从近到远输出。 ### [Aggregate.group(object: Object): Aggregate](./stages/group) 描述: 聚合阶段。将输入记录按给定表达式分组,输出时每个记录代表一个分组,每个记录的 \_id 是区分不同组的 key。输出记录中也可以包括累计值,将输出字段设为累计值即会从该分组中计算累计值。 ### [Aggregate.limit(value: number): Aggregate](./stages/limit) 描述: 聚合阶段。限制输出到下一阶段的记录数。 ### [Aggregate.lookup(object: Object): Aggregate](./stages/lookup) 描述: 聚合阶段。联表查询。与同个数据库下的一个指定的集合做 left outer join(左外连接)。对该阶段的每一个输入记录,lookup 会在该记录中增加一个数组字段,该数组是被联表中满足匹配条件的记录列表。lookup 会将连接后的结果输出给下个阶段。 ### [Aggregate.match(object: Object): Aggregate](./stages/match) 描述: 聚合阶段。根据条件过滤文档,并且把符合条件的文档传递给下一个流水线阶段。 ### [Aggregate.project(object: Object): Aggregate](./stages/project) 描述: 聚合阶段。把指定的字段传递给下一个流水线,指定的字段可以是某个已经存在的字段,也可以是计算出来的新字段。 ### [Aggregate.replaceRoot(object: Object): Aggregate](./stages/replaceRoot) 描述: 聚合阶段。指定一个已有字段作为输出的根节点,也可以指定一个计算出的新字段作为根节点。 ### [Aggregate.sample(size: number): Aggregate](./stages/sample) 描述: 聚合阶段。随机从文档中选取指定数量的记录。 ### [Aggregate.skip(value: number): Aggregate](./stages/skip) 描述: 聚合阶段。指定一个正整数,跳过对应数量的文档,输出剩下的文档。 ### [Aggregate.sort(object: Object): Aggregate](./stages/sort) 描述: 聚合阶段。根据指定的字段,对输入的文档进行排序。 ### [Aggregate.sortByCount(object: Object): Aggregate](./stages/sortByCount) 描述: 聚合阶段。根据传入的表达式,将传入的集合进行分组(group)。然后计算不同组的数量,并且将这些组按照它们的数量进行排序,返回排序后的结果。 ### [Aggregate.unwind(value: string|object): Aggregate](./stages/unwind) 描述: 聚合阶段。使用指定的数组字段中的每个元素,对文档进行拆分。拆分后,文档会从一个变为一个或多个,分别对应数组的每个元素。 ### [Aggregate.bucket(object: Object): Aggregate](./stages/bucket) 描述: 聚合阶段。将输入记录根据给定的条件和边界划分成不同的组,每组即一个 bucket。 ### [Aggregate.bucketAuto(object: Object): Aggregate](./stages/bucketAuto) 描述: 聚合阶段。将输入记录根据给定的条件划分成不同的组,每组即一个 bucket。与 bucket 的其中一个不同之处在于无需指定 boundaries,bucketAuto 会自动尝试将记录尽可能平均的分散到每组中。 ### [Aggregate.count(fieldName: string): Aggregate](./stages/count) 描述: 聚合阶段。计算上一聚合阶段输入到本阶段的记录数,输出一个记录,其中指定字段的值为记录数。 ### [Aggregate.end(): Promise\](./stages/end) 描述: 标志聚合操作定义完成,发起实际聚合操作。 ### [Aggregate.geoNear(options: Object): Aggregate](./stages/geoNear) 描述: 聚合阶段。将记录按照离给定点从近到远输出。 ### [Aggregate.group(object: Object): Aggregate](./stages/group) 描述: 聚合阶段。将输入记录按给定表达式分组,输出时每个记录代表一个分组,每个记录的 \_id 是区分不同组的 key。输出记录中也可以包括累计值,将输出字段设为累计值即会从该分组中计算累计值。 ### [Aggregate.limit(value: number): Aggregate](./stages/limit) 描述: 聚合阶段。限制输出到下一阶段的记录数。 ### [Aggregate.lookup(object: Object): Aggregate](./stages/lookup) 描述: 聚合阶段。描述: 聚合阶段。联表查询。与同个数据库下的一个指定的集合做 left outer join(左外连接)。对该阶段的每一个输入记录,lookup 会在该记录中增加一个数组字段,该数组是被联表中满足匹配条件的记录列表。lookup 会将连接后的结果输出给下个阶段。 ### [Aggregate.match(object: Object): Aggregate](./stages/match) 描述: 聚合阶段。根据条件过滤文档,并且把符合条件的文档传递给下一个流水线阶段。 ### [Aggregate.project(object: Object): Aggregate](./stages/project) 描述: 聚合阶段。把指定的字段传递给下一个流水线,指定的字段可以是某个已经存在的字段,也可以是计算出来的新字段。 ### [Aggregate.replaceRoot(object: Object): Aggregate](./stages/replaceRoot) 描述: 聚合阶段。指定一个已有字段作为输出的根节点,也可以指定一个计算出的新字段作为根节点。 ### [Aggregate.sample(size: number): Aggregate](./stages/sample) 描述: 聚合阶段。随机从文档中选取指定数量的记录。 ### [Aggregate.skip(value: number): Aggregate](./stages/skip) 描述: 聚合阶段。指定一个正整数,跳过对应数量的文档,输出剩下的文档。 ### [Aggregate.sort(object: Object): Aggregate](./stages/sort) 描述: 聚合阶段。根据指定的字段,对输入的文档进行排序。 ### [Aggregate.sortByCount(object: Object): Aggregate](./stages/sortByCount) 描述: 聚合阶段。根据传入的表达式,将传入的集合进行分组(group)。然后计算不同组的数量,并且将这些组按照它们的数量进行排序,返回排序后的结果。 ### [Aggregate.unwind(value: string|object): Aggregate](./stages/unwind) 描述: 聚合阶段。使用指定的数组字段中的每个元素,对文档进行拆分。拆分后,文档会从一个变为一个或多个,分别对应数组的每个元素。 --- # Node SDK/文档型数据库/聚合操作/聚合操作符/AggregateCommand > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/database/aggregate/aggregateCommand 数据库聚合操作符,通过 db.command.aggregate 获取 ### [AggregateCommand.abs(value: number | Expression\)](./operators/abs) 描述: 聚合操作符。返回一个数字的绝对值。 ### [AggregateCommand.add(value: Expression[])](./operators/add) 描述: 聚合操作符。将数字相加或将数字加在日期上。如果数组中的其中一个值是日期,那么其他值将被视为毫秒数加在该日期上。 ### [AggregateCommand.addToSet(value: Expression)](./operators/addToSet) 描述: 聚合操作符。聚合运算符。向数组中添加值,如果数组中已存在该值,不执行任何操作。它只能在 group stage 中使用。 ### [AggregateCommand.allElementsTrue(value: Expression[])](./operators/allElementsTrue) 描述: 聚合操作符。输入一个数组,或者数组字段的表达式。如果数组中所有元素均为真值,那么返回 true,否则返回 false。空数组永远返回 true。 ### [AggregateCommand.and(value: Expression[])](./operators/and) 描述: 聚合操作符。给定多个表达式,and 仅在所有表达式都返回 true 时返回 true,否则返回 false。 ### [AggregateCommand.anyElementTrue(value: Expression[])](./operators/anyElementTrue) 描述: 聚合操作符。输入一个数组,或者数组字段的表达式。如果数组中任意一个元素为真值,那么返回 true,否则返回 false。空数组永远返回 false。 ### [AggregateCommand.arrayElemAt(value: Expression[])](./operators/arrayElemAt) 描述: 聚合操作符。返回在指定数组下标的元素。 ### [AggregateCommand.arrayToObject(value: any)](./operators/arrayToObject) 描述: 聚合操作符。将一个数组转换为对象。 ### [AggregateCommand.avg(value: Expression\)](./operators/avg) 描述: 聚合操作符。返回一组集合中,指定字段对应数据的平均值。 ### [AggregateCommand.ceil(value: Expression\)](./operators/ceil) 描述: 聚合操作符。向上取整,返回大于或等于给定数字的最小整数。 ### [AggregateCommand.cmp(value: Expression[])](./operators/cmp) 描述: 聚合操作符。给定两个值,返回其比较值: ### [AggregateCommand.concat(value: Expression[])](./operators/concat) 描述: 聚合操作符。连接字符串,返回拼接后的字符串。 ### [AggregateCommand.concatArrays(value: Expression[])](./operators/concatArrays) 描述: 聚合操作符。将多个数组拼接成一个数组。 ### [AggregateCommand.cond(value: any)](./operators/cond) 描述: 聚合操作符。计算布尔表达式,返回指定的两个值其中之一。 ### [AggregateCommand.dateFromParts(value: any)](./operators/dateFromParts) 描述: 聚合操作符。给定日期的相关信息,构建并返回一个日期对象。 ### [AggregateCommand.dateFromString(value: any)](./operators/dateFromString) 描述: 聚合操作符。将一个日期/时间字符串转换为日期对象 ### [AggregateCommand.dateToString(value: any)](./operators/dateToString) 描述: 聚合操作符。根据指定的表达式将日期对象格式化为符合要求的字符串。 ### [AggregateCommand.dayOfMonth(value: Expression\)](./operators/dayOfMonth) 描述: 聚合操作符。返回日期字段对应的  天数(一个月中的哪一天),是一个介于 1 至 31 之间的数字。 ### [AggregateCommand.dayOfWeek(value: Expression\)](./operators/dayOfWeek) 描述: 聚合操作符。返回日期字段对应的天数(一周中的第几天),是一个介于 1(周日)到 7(周六)之间的整数。 ### [AggregateCommand.dayOfYear(value: Expression\)](./operators/dayOfYear) 描述: 聚合操作符。返回日期字段对应的天数(一年中的第几天),是一个介于 1 到 366 之间的整数。 ### [AggregateCommand.divide(value: Expression[])](./operators/divide) 描述: 聚合操作符。传入被除数和除数,求商。 ### [AggregateCommand.eq(value: Expression[])](./operators/eq) 描述: 聚合操作符。匹配两个值,如果相等则返回 true,否则返回 false。 ### [AggregateCommand.exp(value: Expression\)](./operators/exp) 描述: 聚合操作符。取 e(自然对数的底数,欧拉数) 的 n 次方。 ### [AggregateCommand.filter(value: any)](./operators/filter) 描述: 聚合操作符。根据给定条件返回满足条件的数组的子集。 ### [AggregateCommand.first(value: Expression)](./operators/first) 描述: 聚合操作符。返回指定字段在一组集合的第一条记录对应的值。仅当这组集合是按照某种定义排序( sort )后,此操作才有意义。 ### [AggregateCommand.floor(value: Expression\)](./operators/floor) 描述: 聚合操作符。向下取整,返回大于或等于给定数字的最小整数。 ### [AggregateCommand.gt(value: Expression[])](./operators/gt) 描述: 聚合操作符。匹配两个值,如果前者大于后者则返回 true,否则返回 false。 ### [AggregateCommand.gte(value: Expression[])](./operators/gte) 描述: 聚合操作符。匹配两个值,如果前者大于或等于后者则返回 true,否则返回 false。 ### [AggregateCommand.hour(value: Expression\)](./operators/hour) 描述: 聚合操作符。返回日期字段对应的小时数,是一个介于 0 到 23 之间的整数。 ### [AggregateCommand.ifNull(value: Expression[])](./operators/ifNull) 描述: 聚合操作符。计算给定的表达式,如果表达式结果为 null、undefined 或者不存在,那么返回一个替代值;否则返回原值。 ### [AggregateCommand.in(value: Expression[])](./operators/in) 描述: 聚合操作符。给定一个值和一个数组,如果值在数组中则返回 true,否则返回 false。 ### [AggregateCommand.indexOfArray(value: Expression[])](./operators/indexOfArray) 描述: 聚合操作符。在数组中找出等于给定值的第一个元素的下标,如果找不到则返回 -1。 ### [AggregateCommand.indexOfBytes(value: Expression[])](./operators/indexOfBytes) 描述: 聚合操作符。在目标字符串中查找子字符串,并返回第一次出现的 UTF-8 的字节索引(从 0 开始)。如果不存在子字符串,返回 -1。 ### [AggregateCommand.indexOfCP(value: Expression[])](./operators/indexOfCP) 描述: 聚合操作符。在目标字符串中查找子字符串,并返回第一次出现的 UTF-8 的 code point 索引(从 0 开始)。如果不存在子字符串,返回 -1。 ### [AggregateCommand.isArray(value: Expression)](./operators/isArray) 描述: 聚合操作符。判断给定表达式是否是数组,返回布尔值。 ### [AggregateCommand.isoDayOfWeek(value: Expression\)](./operators/isoDayOfWeek) 描述: 聚合操作符。返回日期字段对应的 ISO 8601 标准的天数(一周中的第几天),是一个介于 1(周一)到 7(周日)之间的整数。 ### [AggregateCommand.isoWeek(value: Expression\)](./operators/isoWeek) 描述: 聚合操作符。返回日期字段对应的 ISO 8601 标准的周数(一年中的第几周),是一个介于 1 到 53 之间的整数。 ### [AggregateCommand.isoWeekYear(value: Expression\)](./operators/isoWeekYear) 描述: 聚合操作符。返回日期字段对应的 ISO 8601 标准的天数(一年中的第几天)。 ### [AggregateCommand.last(value: Expression)](./operators/last) 描述: 聚合操作符。返回指定字段在一组集合的最后一条记录对应的值。仅当这组集合是按照某种定义排序( sort )后,此操作才有意义。 ### [AggregateCommand.let(value: any)](./operators/let) 描述: 聚合操作符。自定义变量,并且在指定表达式中使用,返回的结果是表达式的结果。 ### [AggregateCommand.literal(value: any)](./operators/literal) 描述: 聚合操作符。直接返回一个值的字面量,不经过任何解析和处理。 ### [AggregateCommand.ln(value: Expression\)](./operators/ln) 描述: 聚合操作符。计算给定数字在自然对数值。 ### [AggregateCommand.log(value: Expression[])](./operators/log) 描述: 聚合操作符。计算给定数字在给定对数底下的 log 值。 ### [AggregateCommand.log10(value: Expression\)](./operators/log10) 描述: 聚合操作符。计算给定数字在对数底为 10 下的 log 值。 ### [AggregateCommand.lt(value: Expression[])](./operators/lt) 描述: 聚合操作符。匹配两个值,如果前者小于后者则返回 true,否则返回 false。 ### [AggregateCommand.lte(value: Expression[])](./operators/lte) 描述: 聚合操作符。匹配两个值,如果前者小于或等于后者则返回 true,否则返回 false。 ### [AggregateCommand.map(value: any)](./operators/map) 描述: 聚合操作符。类似 JavaScript Array 上的 map 方法,将给定数组的每个元素按给定转换方法转换后得出新的数组。 ### [AggregateCommand.max(value: Expression)](./operators/max) 描述: 聚合操作符。返回一组数值的最大值。 ### [AggregateCommand.mergeObjects(value: Expression\)](./operators/mergeObjects) 描述: 聚合操作符。将多个文档合并为单个文档。 ### [AggregateCommand.millisecond(value: Expression\)](./operators/millisecond) 描述: 聚合操作符。返回日期字段对应的毫秒数,是一个介于 0 到 999 之间的整数。 ### [AggregateCommand.min(value: Expression)](./operators/min) 描述: 聚合操作符。返回一组数值的最小值。 ### [AggregateCommand.minute(value: Expression\)](./operators/minute) 描述: 聚合操作符。返回日期字段对应的分钟数,是一个介于 0 到 59 之间的整数。 ### [AggregateCommand.mod(value: Expression[])](./operators/mod) 描述: 聚合操作符。取模运算,取数字取模后的值。 ### [AggregateCommand.month(value: Expression\)](./operators/month) 描述: 聚合操作符。返回日期字段对应的月份,是一个介于 1 到 12 之间的整数。 ### [AggregateCommand.multiply(value: Expression[])](./operators/multiply) 描述: 聚合操作符。取传入的数字参数相乘的结果。 ### [AggregateCommand.neq(value: Expression[])](./operators/neq) 描述: 聚合操作符。匹配两个值,如果不相等则返回 true,否则返回 false。 ### [AggregateCommand.not(value: Expression)](./operators/not) 描述: 聚合操作符。给定一个表达式,如果表达式返回 true,则 not 返回 false,否则返回 true。注意表达式不能为逻辑表达式(and、or、nor、not)。 ### [AggregateCommand.objectToArray(value: Expression\)](./operators/objectToArray) 描述: 聚合操作符。将一个对象转换为数组。方法把对象的每个键值对都变成输出数组的一个元素,元素形如 `{ k: , v: }`。 ### [AggregateCommand.or(value: Expression[])](./operators/or) 描述: 聚合操作符。给定多个表达式,如果任意一个表达式返回 true,则 or 返回 true,否则返回 false。 ### [AggregateCommand.pow(value: Expression[])](./operators/pow) 描述: 聚合操作符。求给定基数的指数次幂。 ### [AggregateCommand.push(value: any)](./operators/push) 描述: 聚合操作符。在 group 阶段,返回一组中表达式指定列与对应的值,一起组成的数组。 ### [AggregateCommand.range(value: Expression[])](./operators/range) 描述: 聚合操作符。返回一组生成的序列数字。给定开始值、结束值、非零的步长,range 会返回从开始值开始逐步增长、步长为给定步长、但不包括结束值的序列。 ### [AggregateCommand.reduce(value: any)](./operators/reduce) 描述: 聚合操作符。类似 JavaScript 的 reduce 方法,应用一个表达式于数组各个元素然后归一成一个元素。 ### [AggregateCommand.reverseArray(value: Expression\)](./operators/reverseArray) 描述: 聚合操作符。返回给定数组的倒序形式。 ### [AggregateCommand.second(value: Expression\)](./operators/second) 描述: 聚合操作符。返回日期字段对应的秒数,是一个介于 0 到 59 之间的整数,在特殊情况下(闰秒)可能等于 60。 ### [AggregateCommand.setDifference(value: Expression[])](./operators/setDifference) 描述: 聚合操作符,输入两个集合,输出只存在于第一个集合中的元素。 ### [AggregateCommand.setEquals(value: Expression[])](./operators/setEquals) 描述: 聚合操作符,输入两个集合,判断两个集合中包含的元素是否相同(不考虑顺序、去重)。 ### [AggregateCommand.setIntersection(value: Expression[])](./operators/setIntersection) 描述: 聚合操作符,输入两个集合,输出两个集合的交集。 ### [AggregateCommand.setIsSubset(value: Expression[])](./operators/setIsSubset) 描述: 聚合操作符,输入两个集合,判断第一个集合是否是第二个集合的子集。 ### [AggregateCommand.setUnion(value: Expression[])](./operators/setUnion) 描述: 聚合操作符,输入两个集合,输出两个集合的并集。 ### [AggregateCommand.size(value: Expression\)](./operators/size) 描述: 聚合操作符。返回数组长度。 ### [AggregateCommand.slice(value: Expression[])](./operators/slice) 描述: 聚合操作符。类似 JavaScritp 的 slice 方法。返回给定数组的指定子集。 ### [AggregateCommand.split(value: Expression[])](./operators/split) 描述: 聚合操作符。按照分隔符分隔数组,并且删除分隔符,返回子字符串组成的数组。如果字符串无法找到分隔符进行分隔,返回原字符串作为数组的唯一元素。 ### [AggregateCommand.sqrt(value: Expression[])](./operators/sqrt) 描述: 聚合操作符。求平方根。 ### [AggregateCommand.stdDevPop(value: Expression)](./operators/stdDevPop) 描述: 聚合操作符。返回一组字段对应值的标准差。 ### [AggregateCommand.stdDevSamp(value: Expression)](./operators/stdDevSamp) 描述: 聚合操作符。计算输入值的样本标准偏差。如果输入值代表数据总体,或者不概括更多的数据,请改用 db.command.aggregate.stdDevPop。 ### [AggregateCommand.strLenBytes(value: Expression)](./operators/strLenBytes) 描述: 聚合操作符。计算并返回指定字符串中 utf-8 编码的字节数量。 ### [AggregateCommand.strLenCP(value: Expression)](./operators/strLenCP) 描述: 聚合操作符。计算并返回指定字符串的 UTF-8 code points 数量。 ### [AggregateCommand.strcasecmp(value: Expression[])](./operators/strcasecmp) 描述: 聚合操作符。对两个字符串在不区分大小写的情况下进行大小比较,并返回比较的结果。 ### [AggregateCommand.substr(value: Expression[])](./operators/substr) 描述: 聚合操作符。返回字符串从指定位置开始的指定长度的子字符串。它是 db.command.aggregate.substrBytes 的别名,更推荐使用后者。 ### [AggregateCommand.substrBytes(value: Expression[])](./operators/substrBytes) 描述: 聚合操作符。返回字符串从指定位置开始的指定长度的子字符串。子字符串是由字符串中指定的 UTF-8 字节索引的字符开始,长度为指定的字节数。 ### [AggregateCommand.substrCP(value: Expression[])](./operators/substrCP) 描述: 聚合操作符。返回字符串从指定位置开始的指定长度的子字符串。子字符串是由字符串中指定的 UTF-8 字节索引的字符开始,长度为指定的字节数。 ### [AggregateCommand.subtract(value: Expression[])](./operators/subtract) 描述: 聚合操作符。将两个数字相减然后返回差值,或将两个日期相减然后返回相差的毫秒数,或将一个日期减去一个数字返回结果的日期。 ### [AggregateCommand.sum(value: Expression)](./operators/sum) 描述: 聚合操作符。计算并且返回一组字段所有数值的总和。 ### [AggregateCommand.switch(value: any)](./operators/switch) 描述: 聚合操作符。根据给定的 switch-case-default 计算返回值、 ### [AggregateCommand.toLower(value: any)](./operators/toLower) 描述: 聚合操作符。将字符串转化为小写并返回。 ### [AggregateCommand.toUpper(value: any)](./operators/toUpper) 描述: 聚合操作符。将字符串转化为大写并返回。 ### [AggregateCommand.trunc(value: Expression\)](./operators/trunc) 描述: 聚合操作符。将数字截断为整形。 ### [AggregateCommand.week(value: Expression\)](./operators/week) 描述: 聚合操作符。返回日期字段对应的周数(一年中的第几周),是一个介于 0 到 53 之间的整数。 ### [AggregateCommand.year(value: Expression\)](./operators/year) 描述: 聚合操作符。返回日期字段对应的年份。 ### [AggregateCommand.zip(value: any)](./operators/zip) 描述: 聚合操作符。把二维数组的第二维数组中的相同序号的元素分别拼装成一个新的数组进而组装成一个新的二维数组。如可将 [ [ 1, 2, 3 ], [ "a", "b", "c" ] ] 转换成 [ [ 1, "a" ], [ 2, "b" ], [ 3, "c" ] ]。 --- # Node SDK/文档型数据库/聚合操作/Expression > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/database/aggregate/expression 聚合表达式 ### 说明 表达式可以是字段路径、常量、或聚合操作符。表达式可以嵌套表达式。 #### 字段路径 表达式用字段路径表示法来指定记录中的字段。字段路径的表示由一个 $ 符号加上字段名或嵌套字段名。嵌套字段名用点将嵌套的各级字段连接起来。如 $profile 就表示 profile 的字段路径,\$profile.name 就表示 profile.name 的字段路径(profile 字段中嵌套的 name 字段)。 #### 常量 常量可以是任意类型。默认情况下 \$ 开头的字符串都会被当做字段路径处理,如果想要避免这种行为,使用 AggregateCommand.literal 声明为常量。 #### 聚合操作符 [AggregateCommand](./aggregateCommand) --- # Node SDK/PostgreSQL 数据库/查询数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/postgresql/fetch ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 基础查询 通过 `select()` 方法查询表数据,支持条件筛选、关联查询等功能。 ```js db.from(tableName).select(columns, options) ``` - **tableName**:表名称 - **columns**:要检索的列,用逗号分隔 - **options**:查询选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ---------------------------------------------------------------------------------------------------------------------------- | | columns | string | 否 | 要检索的列,用逗号分隔。支持使用 `aliasName:foreignKey(columns)` 语法重命名外键关联字段
`columns` 取 `*` 为查询所有字段 | | options | object | 否 | 查询选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------- | ---- | ------------------------------------------------- | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | | head | boolean | 否 | 设置为 `true` 时不返回数据,仅在需要计数时有用 | ### 代码示例 #### 查询所有数据 ```javascript // 查询 articles 表中的所有数据 const { data, error } = await db.from("articles").select(); console.log('查询结果:', data); ``` #### 查询指定列 ```javascript // 只查询文章的标题和创建时间 const { data, error } = await db.from("articles").select("id, title, created_at"); console.log('查询结果:', data); ``` #### 查询外键关联数据 ```javascript // 使用别名 'category' 代替默认的 'categories' 字段名 const { data, error } = await db.from("articles").select(` id, title, created_at, category:categories(name) `); console.log('查询结果:', data); ``` #### 返回结果 ```javascript { data: [ { id: 1, title: "文章标题", created_at: "2023-01-01T00:00:00Z", category: { name: "前端开发" } }, // ... 其他记录 ], error: null } ``` ## 关联表查询 通过关联查询可以同时获取多个表的数据,支持一对一、一对多等关联关系。 ### 示例表结构 为了更好地理解关联查询,我们先了解一下示例中使用的表结构: ```mermaid erDiagram users { bigint id PK varchar name varchar email } categories { bigint id PK varchar name } articles { bigint id PK varchar title text content varchar category_id FK varchar created_by FK varchar updated_by FK timestamp created_at timestamp updated_at } users ||--o{ articles : "created_by" users ||--o{ articles : "updated_by" categories ||--o{ articles : "category_id" ``` **表关系说明:** - `articles` 表通过 `category_id` 与 `categories` 表建立多对一关系 - `articles` 表通过 `created_by` 与 `users` 表建立多对一关系(创建者) - `articles` 表通过 `updated_by` 与 `users` 表建立多对一关系(修改者)
查看完整的 SQL 建表语句 ```sql -- 用户表 CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), email VARCHAR(100) ); -- 分类表 CREATE TABLE categories ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) ); -- 文章表 CREATE TABLE articles ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(256), content TEXT, category_id BIGINT, created_by BIGINT, updated_by BIGINT, created_at TIMESTAMP, updated_at TIMESTAMP, FOREIGN KEY (category_id) REFERENCES categories(id), CONSTRAINT articles_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id), CONSTRAINT articles_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES users(id) ); ```
### 基础关联查询 ```javascript // 查询文章数据,同时获取关联的分类信息 // 基于 articles.category_id = categories.id 的关联关系 const { data, error } = await db.from("articles").select(` title, categories(name) `); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { title: "JavaScript 入门教程", categories: { name: "前端开发" } }, { title: "Node.js 实战", categories: { name: "后端开发" } } ] ```
### 多重关联查询 ```javascript // 查询文章,同时获取创建者和修改者的信息 // 使用别名区分不同的用户关联关系,让返回数据更易理解 const { data, error } = await db.from("articles").select(` title, creator:users!articles_created_by_fkey(name), updater:users!articles_updated_by_fkey(name) `); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { title: "JavaScript 入门教程", creator: { // 使用别名 'creator' 表示创建者 name: "张三" }, updater: { // 使用别名 'updater' 表示修改者 name: "李四" } } ] ```
> 💡 注意:当同一个表通过不同的外键关联多次时,**必须**使用外键约束名(如 `!articles_created_by_fkey`)来区分不同的关联关系。建议配合别名语法,让返回的数据字段更具语义。 ### 嵌套关联查询 ```javascript // 查询分类及其下的所有文章,以及文章的作者信息 // 这是一个三层嵌套的关联查询:categories -> articles -> users const { data, error } = await db.from("categories").select(` name, articles ( title, users ( name ) ) `); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { name: "前端开发", articles: [ { title: "JavaScript 入门教程", users: { name: "张三" } }, { title: "Vue.js 实战", users: { name: "李四" } } ] }, { name: "后端开发", articles: [ { title: "Node.js 实战", users: { name: "王五" } } ] } ] ```
## 高级查询 ### 条件过滤查询 ```javascript // 查询特定分类下的所有文章 // 通过分类名称过滤,获取该分类下的所有文章 const { data, error } = await db .from("articles") .select("title, categories(*)") .eq("categories.name", "技术文章"); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { title: "JavaScript 高级特性", categories: { id: 1, name: "技术文章" } }, { title: "数据库优化技巧", categories: { id: 1, name: "技术文章" } } ] ```
### 计数查询 ```javascript // 获取每个分类及其包含的文章数量 // count 会统计每个分类下有多少篇文章 const { data, error } = await db .from("categories") .select(`*, articles(count)`); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { id: 1, name: "前端开发", articles: [ { count: 5 } // 该分类下有5篇文章 ] }, { id: 2, name: "后端开发", articles: [ { count: 3 } // 该分类下有3篇文章 ] } ] ```
### 仅获取总数 ```javascript // 只获取文章总数,不返回具体数据 const { count, error } = await db .from("articles") .select("*", { count: "exact", head: true }); console.log('总数:', count); ``` ### 内连接查询 ```javascript // 只获取有分类的文章,使用内连接确保分类存在 // !inner 确保只返回在 categories 表中有匹配记录的文章 const { data, error } = await db .from("articles") .select("title, categories!inner(name)") .eq("categories.name", "教程") .limit(10); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { title: "JavaScript 基础教程", categories: { name: "教程" } }, { title: "React 入门教程", categories: { name: "教程" } } ] ```
> 💡 注意:`!inner` 表示内连接,只返回那些在关联表中存在匹配记录的数据。如果不使用 `!inner`,即使文章的 `category_id` 为 null 或指向不存在的分类,文章仍会被返回,但 `categories` 字段为 null。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 - [修饰符](./modifiers) - 了解查询修饰符的使用方法 --- # Node SDK/PostgreSQL 数据库/新增数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/postgresql/insert ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 新增数据 通过 `insert()` 方法向表中插入数据,支持单行和批量插入。 ```js db.from(tableName).insert(values, options) ``` - **tableName**:表名称 - **values**:要插入的数据 - **options**:插入选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | --------------- | ---- | ------------------------------------------------------ | | values | object \| Array | 是 | 要插入的值。传递对象以插入单行,或传递数组以插入多行。 | | options | object | 否 | 插入选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------ | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 创建记录 ```javascript // 向 articles 表中插入一条记录 const { error } = await db .from("articles") .insert({ title: "新文章标题", content: "文章内容" }); console.log('插入结果:', error ? '失败' : '成功'); ``` #### 创建记录并返回数据 ```javascript // 插入记录并返回插入的数据 const { data, error } = await db .from("articles") .insert({ title: "新文章标题", content: "文章内容" }) .select(); console.log('插入结果:', data); ``` > 💡 注意:仅当表中只有一个主键,且该主键为自增类型时,`.select()` 方法才会返回插入的行。 #### 批量创建记录 ```javascript // 一次性插入多条记录 const { error } = await db.from("articles").insert([ { title: "第一篇文章", content: "第一篇文章内容" }, { title: "第二篇文章", content: "第二篇文章内容" }, ]); console.log('批量插入结果:', error ? '失败' : '成功'); ``` --- # Node SDK/PostgreSQL 数据库/更新数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/postgresql/update ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 更新数据 通过 `update()` 方法更新表中的数据,需要结合过滤器使用以定位要更新的行。 ```js db.from(tableName).update(values, options).filter() ``` - **tableName**:表名称 - **values**:要更新的数据 - **options**:更新选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ---------- | | values | object | 是 | 要更新的值 | | options | object | 否 | 更新选项 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------------------ | | count | string | 否 | 计数算法,用于计算更新的行数:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 更新数据 ```javascript // 更新 articles 表中 id 为 1 的记录,将 title 字段改为"新标题" const { error } = await db .from("articles") .update({ title: "新标题" }) .eq("id", 1); console.log('更新结果:', error ? '失败' : '成功'); ``` #### 批量更新 ```javascript // 更新所有状态为 draft 的文章,将状态改为 published const { error } = await db .from("articles") .update({ status: "published" }) .eq("status", "draft"); console.log('批量更新结果:', error ? '失败' : '成功'); ``` ### 返回结果 ```javascript { data: null, error: null } ``` > 💡 注意:`update()` 方法必须与 [过滤器](./filters) 结合使用,以定位您希望更新的行。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 --- # Node SDK/PostgreSQL 数据库/更新或创建数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/postgresql/upsert ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 更新或创建数据 通过 `upsert()` 方法执行更新或插入操作,如果记录不存在则插入,存在则更新。 ```js db.from(tableName).upsert(values, options) ``` - **tableName**:表名称 - **values**:要 upsert 的数据 - **options**:upsert 选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | -------------------- | ---- | ---------------------------------------------------------------- | | values | object \| Array | 是 | 要 upsert 的值。传递对象以 upsert 单行,或传递数组以 upsert 多行 | | options | object | 否 | upsert 选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ---------------- | ------- | ---- | ----------------------------------------------------------------------------------------------------------------------------- | | count | string | 否 | 用于计算 upserted 行数的计数算法:`"exact"` - 底层执行 `COUNT(*)` | | ignoreDuplicates | boolean | 否 | 如果为 true,则忽略重复行。如果为 false,则重复行与现有行合并 | | onConflict | string | 否 | 逗号分隔的唯一索引列,用于指定如何确定重复行。当所有指定的列都相等时,两行被视为重复。在 PostgreSQL 中,这通常对应于唯一索引或主键 | ### 代码示例 #### Upsert 数据 ```javascript // 如果 articles 表中存在 id 为 1 的记录则更新 title 为"PostgreSQL 教程",不存在则插入新记录 const { data, error } = await db .from("articles") .upsert({ id: 1, title: "PostgreSQL 教程" }); console.log('Upsert 结果:', data); ``` #### 批量 Upsert 数据 ```javascript // 批量 upsert 多条记录:id 为 1 的记录 title 设为"PostgreSQL 教程",id 为 2 的记录 title 设为"Redis 指南" const { data, error } = await db.from("articles").upsert([ { id: 1, title: "PostgreSQL 教程" }, { id: 2, title: "Redis 指南" }, ]); console.log('批量 Upsert 结果:', data); ``` #### 指定冲突列 ```javascript // 根据 title 字段判断冲突,如果存在 title 为"唯一标题"的记录则更新,否则插入 id 为 42 的新记录 const { data, error } = await db .from("articles") .upsert( { id: 42, title: "唯一标题", content: "文章内容" }, { onConflict: "title" } ); console.log('指定冲突列 Upsert 结果:', data); ``` ### 返回结果 ```javascript { data: [ { id: 1, title: "PostgreSQL 教程", content: "文章内容" } ], error: null } ``` > 💡 注意:使用 `upsert()` 时必须包含主键列,这样才能正确判断是插入新行还是更新现有行。在 PostgreSQL 中,upsert 通常通过 `ON CONFLICT DO UPDATE` 语法实现,当插入的数据与现有主键或唯一索引冲突时,会执行更新操作。`onConflict` 参数用于指定冲突判断的列,对应 PostgreSQL 中的唯一索引列。 --- # Node SDK/PostgreSQL 数据库/删除数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/postgresql/delete ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); ``` ## 删除数据 通过 `delete()` 方法删除表中的数据,需要结合过滤器使用以定位要删除的行。 ```js db.from(tableName).delete(options).filter() ``` - **tableName**:表名称 - **options**:删除选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ------------ | | options | object | 否 | 删除选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------ | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 删除单个记录 ```javascript // 删除 articles 表中 id 为 1 的记录 const { error } = await db.from("articles").delete().eq("id", 1); console.log('删除结果:', error ? '失败' : '成功'); ``` #### 批量删除记录 ```javascript // 批量删除 articles 表中 id 为 1、2、3 的多条记录 const { error } = await db.from("articles").delete().in("id", [1, 2, 3]); console.log('批量删除结果:', error ? '失败' : '成功'); ``` #### 条件删除 ```javascript // 删除所有状态为 draft 的文章 const { error } = await db.from("articles").delete().eq("status", "draft"); console.log('条件删除结果:', error ? '失败' : '成功'); ``` ### 返回结果 ```javascript { data: null, error: null } ``` > ⚠️ 注意:`delete()` 方法必须与 [过滤器](./filters) 结合使用,以定位您希望删除的行。确保您提供的条件准确表示您打算删除的所有记录,以避免意外删除数据。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 --- # Node SDK/PostgreSQL 数据库/过滤器 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/postgresql/filters 过滤器允许只返回符合特定条件的行。 过滤器可以用于 `select()`、`update()`、`upsert()` 和 `delete()` 查询。 ## eq 仅匹配列值等于指定值的行。 要检查列值是否为 `NULL`,应该使用 `.is()` 而不是 `eq`。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .eq("title", "腾讯云开发"); ``` ## neq 仅匹配列值不等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 不等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .neq("title", "腾讯云开发"); ``` ## gt 仅匹配列值大于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 大于 2 的记录 const { data, error } = await db.from("articles").select().gt("id", 2); ``` ## gte 仅匹配列值大于或等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 大于或等于 2 的记录 const { data, error } = await db.from("articles").select().gte("id", 2); ``` ## lt 仅匹配列值小于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 小于 2 的记录 const { data, error } = await db.from("articles").select().lt("id", 2); ``` ## lte 仅匹配列值小于或等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 小于或等于 2 的记录 const { data, error } = await db.from("articles").select().lte("id", 2); ``` ## like 仅匹配列值符合特定模式的行(是否区分大小写受校验规则约束)。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------- | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | pattern | string | 必需 | 要匹配的模式 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 包含 "cloudbase" 的记录 const { data, error } = await db .from("articles") .select() .like("title", "%cloudbase%"); ``` ## is 仅匹配列值等于指定值的行。 对于非布尔列,主要用于检查列值是否为 `NULL`; 对于布尔列,也可以设置为 `true` 或 `false`,行为与 `.eq()` 相同。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | Object | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 为 null 的记录 const { data, error } = await db.from("articles").select().is("title", null); ``` ## in 仅匹配列值包含在指定数组中的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ---------------- | | column | string | 必需 | 要过滤的列 | | values | Array | 必需 | 用于过滤的值数组 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 在指定数组 ["腾讯云开发", "云开发"] 中的记录 const { data, error } = await db .from("articles") .select() .in("title", ["腾讯云开发", "云开发"]); ``` ## match 仅匹配查询键中每个列都等于其关联值的行,相当于多个 `.eq()` 的简写形式。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ----- | ------------------------------------- | ---- | -------------------------------------- | | query | `Record` | 必需 | 过滤对象,列名作为键映射到它们的过滤值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 等于 2 且 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .match({ id: 2, title: "腾讯云开发" }); ``` ## not 仅匹配不满足过滤条件的行。 与大多数过滤器不同,操作符和值按原样使用,需要遵循 PostgreSQL 语法,还需要确保它们已正确转义。 `not()` 期望使用原始的 PostgreSQL 语法作为过滤器值。 ```javascript .not('id', 'in', '(5,6,7)') // 对 `in` 过滤器使用 `()` .not('name', 'like', '%test%') // 使用 `not like` 进行模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | -------- | ------ | ---- | ----------------------------------- | | column | string | 必需 | 要过滤的列 | | operator | string | 必需 | 要取反的过滤操作符,遵循 PostgreSQL 语法 | | value | any | 必需 | 过滤值,遵循 PostgreSQL 语法 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 不为 null 的记录 const { data, error } = await db .from("articles") .select() .not("title", "is", null); ``` ## or 仅匹配满足至少一个过滤条件的行。 与大多数过滤器不同,过滤器按原样使用,需要遵循 PostgreSQL 语法,还需要确保它们已正确转义。 目前无法跨多个表进行 `.or()` 过滤。 `or()` 期望使用原始的 PostgreSQL 语法作为过滤器名称和值。 ```javascript .or('id.in.(5,6,7), name.like.%test%') // 对 `in` 过滤器使用 `()`,对模糊匹配使用 `like` 和 `%` .or('id.in.(5,6,7), name.not.like.%test%') // 使用 `not.like` 进行反向模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | ----------------------- | ------ | ---- | ------------------------------- | | filters | string | 必需 | 要使用的过滤器,遵循 PostgreSQL 语法 | | options | object | 必需 | 命名参数 | | options.referencedTable | string | 可选 | 设置为过滤引用表而不是父表 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 等于 2 或者 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .or(`id.eq.2,title.eq.腾讯云开发`); ``` ## filter 仅匹配满足过滤条件的行,这是一个逃生舱口,应该尽可能使用特定的过滤器方法。 与大多数过滤器不同,操作符和值按原样使用,需要遵循 PostgreSQL 语法,还需要确保它们已正确转义。 `filter()` 期望使用原始的 PostgreSQL 语法作为过滤器值。 ```javascript .filter('id', 'in', '(5,6,7)') // 对 `in` 过滤器使用 `()` .filter('name', 'like', '%test%') // 使用 `like` 进行模糊匹配 .filter('name', 'not.like', '%test%') // 使用 `not.like` 进行反向模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | -------- | ------ | ---- | --------------------------- | | column | string | 必需 | 要过滤的列 | | operator | string | 必需 | 过滤操作符,遵循 PostgreSQL 语法 | | value | any | 必需 | 过滤值,遵循 PostgreSQL 语法 | ### 代码示例 ```javascript // 查询 title 在指定值列表中的记录 // 从 articles 表中查询所有 title 在指定值列表 ["腾讯云开发", "云开发"] 中的记录 const { data, error } = await db .from("articles") .select() .filter("title", "in", "(腾讯云开发,云开发)"); // 在引用表上过滤 // 从 articles 表中查询所有关联的 categories.name 等于 "技术" 的记录 const { data, error } = await db .from("articles") .select() .filter("categories.name", "eq", "技术"); ``` --- # Node SDK/PostgreSQL 数据库/修饰符 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/postgresql/modifiers 修饰符用于改变响应的格式,与过滤器不同,它们作用于行级别以上的操作。 过滤器仅返回匹配特定条件的行而不改变行的形状,而修饰符允许改变响应的格式。 ## select 默认情况下,`.insert()` 不会返回插入的行。通过调用此方法,插入的行将在数据中返回。 > ⚠️ 注意:仅当表中只有一个主键,且该主键为自增类型时,`.select()` 方法才会返回插入的行。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------------------- | | columns | string | 否 | 要检索的列,用逗号分隔 | ### 代码示例 ```javascript // 在 articles 表中执行 upsert 操作,并返回修改后的完整记录 const { data, error } = await db .from("articles") .insert({ id: 1, title: "腾讯云开发新功能" }) .select(); ``` ## order 对查询结果进行排序。 可以多次调用此方法来按多个列排序。 可以对引用的表进行排序,但仅当在查询中使用 `!inner` 时,它才会影响父表的排序。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------- | | column | string | 是 | 要排序的列 | | options | object | 否 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------- | ---- | --------------------------------------------------------------- | | ascending | boolean | 否 | 如果为 `true`,结果将按升序排列 | | nullsFirst | boolean | 否 | 如果为 `true`,`null` 值将首先出现。如果为 `false`,`null` 值将最后出现 | | referencedTable | string | 否 | 设置为按引用表的列排序 | ### 代码示例 ```javascript // 按发布时间降序排列文章 const { data, error } = await db .from("articles") .select("id, title, published_at") .order("published_at", { ascending: false }); ``` #### 对引用表排序 ```javascript // 对引用表 categories 按 name 降序排列 const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .order("name", { referencedTable: "categories", ascending: false }); ``` ```javascript // 按引用表 category 的 name 列升序排列 const { data, error } = await db .from("articles") .select(` title, category:categories ( name ) `) .order("category(name)", { ascending: true }); ``` ## limit 限制返回的行数。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------------- | | count | number | 是 | 要返回的最大行数 | | options | object | 否 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------ | ---- | ---------------------------------------- | | referencedTable | string | 否 | 设置为限制引用表的行数,而不是父表的行数 | ### 代码示例 ```javascript // 限制返回 5 篇文章 const { data, error } = await db.from("articles").select("title").limit(5); ``` #### 限制引用表的行数 ```javascript // 每篇文章只返回 3 个分类 const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .limit(3, { referencedTable: "categories" }); ``` ## range 限制查询结果的范围。 通过从偏移量 `from` 开始到 `to` 结束来限制查询结果,只有在此范围内的记录会被返回。 这遵循查询顺序,如果没有排序子句,范围行为可能会不可预期。 `from` 和 `to` 值是基于 0 的且包含边界:`range(1, 3)` 将包括查询的第二、第三和第四行。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ------------------ | | from | number | 是 | 限制结果的起始索引 | | to | number | 是 | 限制结果的结束索引 | | options | object | 是 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------ | ---- | ---------------------------------------- | | referencedTable | string | 否 | 设置为限制引用表的行数,而不是父表的行数 | ### 代码示例 ```javascript // 获取文章列表的第 1-2 条记录(包含边界) const { data, error } = await db.from("articles").select("title").range(0, 1); ``` #### 对引用表使用范围限制 ```javascript // 每篇文章只返回前 2 个分类(索引 0-1) const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .range(0, 1, { referencedTable: "categories" }); ``` ## abortSignal > 💡 注意:此方法仅在 Web 环境下可用。 为 `fetch` 请求设置 `AbortSignal`。 可以使用此功能为请求设置超时。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------ | ----------- | ---- | ----------------------------- | | signal | AbortSignal | 是 | 用于 `fetch` 请求的 `AbortSignal` | ### 代码示例 ```javascript // 使用 AbortController 手动中止请求 const ac = new AbortController(); ac.abort(); const { data, error } = await db .from("articles") .select() .abortSignal(ac.signal); ``` ```javascript // 使用 AbortSignal.timeout 设置 1 秒超时 const { data, error } = await db .from("articles") .select() .abortSignal(AbortSignal.timeout(1000)); ``` ## single 检索单行数据。 将数据作为单个对象返回,而不是对象数组。 查询结果必须只有一行(例如使用 `.limit(1)` ),否则此方法会返回错误。 ### 代码示例 ```javascript // 获取第一篇文章的标题 const { data, error } = await db .from("articles") .select("title") .limit(1) .single(); ``` ## maybeSingle 检索零行或一行数据。 将数据作为单个对象返回,而不是对象数组。 查询结果必须为零行或一行(例如使用 `.limit(1)` ),否则此方法会返回错误。 ### 代码示例 ```javascript // 根据标题查找文章,可能不存在 const { data, error } = await db .from("articles") .select() .eq("title", "腾讯云开发新功能") .maybeSingle(); ``` ## overrideTypes 部分覆盖或替换成功响应的类型。 覆盖响应中 `data` 字段的返回类型,对于类型安全的查询结果转换非常有用。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ------------ | | type | T | 是 | 要覆盖的类型 | | options | object | 否 | 选项对象 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | ------ | ------- | ---- | -------------------------------------- | | merge | boolean | 否 | 如果为 `false`,则完全替换类型而不是合并 | ### 代码示例 #### 完全覆盖数组类型 ```javascript // 将响应数据完全覆盖为自定义数组类型,merge: false 表示完全替换而非合并 const { data } = await db .from("articles") .select() .overrideTypes, { merge: false }>(); ``` #### 完全覆盖对象类型 ```javascript // 与 maybeSingle 一起使用,将单个对象响应完全覆盖为自定义类型 const { data } = await db .from("articles") .select() .maybeSingle() .overrideTypes(); ``` #### 部分覆盖数组类型 ```javascript // 部分覆盖数组元素类型,只指定需要改变的字段类型(如 status 字段) const { data } = await db .from("articles") .select() .overrideTypes>(); ``` #### 部分覆盖对象类型 ```javascript // 部分覆盖单个对象类型,只指定需要改变的字段类型(如 status 字段) const { data } = await db .from("articles") .select() .maybeSingle() .overrideTypes<{ status: "A" | "B" }>(); ``` --- # Node SDK/MySQL 数据库/查询数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/mysql/fetch ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); // 或指定实例和数据库 // const db = app.rdb({ // instance: "", // database: "" // }); ``` ## 基础查询 通过 `select()` 方法查询表数据,支持条件筛选、关联查询等功能。 ```js db.from(tableName).select(columns, options) ``` - **tableName**:表名称 - **columns**:要检索的列,用逗号分隔 - **options**:查询选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ---------------------------------------------------------------------------------------------------------------------------- | | columns | string | 否 | 要检索的列,用逗号分隔。支持使用 `aliasName:foreignKey(columns)` 语法重命名外键关联字段
`columns` 取 `*` 为查询所有字段 | | options | object | 否 | 查询选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------- | ---- | ------------------------------------------------- | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | | head | boolean | 否 | 设置为 `true` 时不返回数据,仅在需要计数时有用 | ### 代码示例 #### 查询所有数据 ```javascript // 查询 articles 表中的所有数据 const { data, error } = await db.from("articles").select(); console.log('查询结果:', data); ``` #### 查询指定列 ```javascript // 只查询文章的标题和创建时间 const { data, error } = await db.from("articles").select("id, title, created_at"); console.log('查询结果:', data); ``` #### 查询外键关联数据 ```javascript // 使用别名 'category' 代替默认的 'categories' 字段名 const { data, error } = await db.from("articles").select(` id, title, created_at, category:categories(name) `); console.log('查询结果:', data); ``` #### 返回结果 ```javascript { data: [ { id: 1, title: "文章标题", created_at: "2023-01-01T00:00:00Z", category: { name: "前端开发" } }, // ... 其他记录 ], error: null } ``` ## 关联表查询 通过关联查询可以同时获取多个表的数据,支持一对一、一对多等关联关系。 ### 示例表结构 为了更好地理解关联查询,我们先了解一下示例中使用的表结构: ```mermaid erDiagram users { bigint id PK varchar name varchar email } categories { bigint id PK varchar name } articles { bigint id PK varchar title text content varchar category_id FK varchar created_by FK varchar updated_by FK timestamp created_at timestamp updated_at } users ||--o{ articles : "created_by" users ||--o{ articles : "updated_by" categories ||--o{ articles : "category_id" ``` **表关系说明:** - `articles` 表通过 `category_id` 与 `categories` 表建立多对一关系 - `articles` 表通过 `created_by` 与 `users` 表建立多对一关系(创建者) - `articles` 表通过 `updated_by` 与 `users` 表建立多对一关系(修改者)
查看完整的 SQL 建表语句 ```sql -- 用户表 CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), email VARCHAR(100) ); -- 分类表 CREATE TABLE categories ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) ); -- 文章表 CREATE TABLE articles ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(256), content TEXT, category_id BIGINT, created_by BIGINT, updated_by BIGINT, created_at TIMESTAMP, updated_at TIMESTAMP, FOREIGN KEY (category_id) REFERENCES categories(id), CONSTRAINT articles_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id), CONSTRAINT articles_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES users(id) ); ```
### 基础关联查询 ```javascript // 查询文章数据,同时获取关联的分类信息 // 基于 articles.category_id = categories.id 的关联关系 const { data, error } = await db.from("articles").select(` title, categories(name) `); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { title: "JavaScript 入门教程", categories: { name: "前端开发" } }, { title: "Node.js 实战", categories: { name: "后端开发" } } ] ```
### 多重关联查询 ```javascript // 查询文章,同时获取创建者和修改者的信息 // 使用别名区分不同的用户关联关系,让返回数据更易理解 const { data, error } = await db.from("articles").select(` title, creator:users!articles_created_by_fkey(name), updater:users!articles_updated_by_fkey(name) `); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { title: "JavaScript 入门教程", creator: { // 使用别名 'creator' 表示创建者 name: "张三" }, updater: { // 使用别名 'updater' 表示修改者 name: "李四" } } ] ```
> 💡 注意:当同一个表通过不同的外键关联多次时,**必须**使用外键约束名(如 `!articles_created_by_fkey`)来区分不同的关联关系。建议配合别名语法,让返回的数据字段更具语义。 ### 嵌套关联查询 ```javascript // 查询分类及其下的所有文章,以及文章的作者信息 // 这是一个三层嵌套的关联查询:categories -> articles -> users const { data, error } = await db.from("categories").select(` name, articles ( title, users ( name ) ) `); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { name: "前端开发", articles: [ { title: "JavaScript 入门教程", users: { name: "张三" } }, { title: "Vue.js 实战", users: { name: "李四" } } ] }, { name: "后端开发", articles: [ { title: "Node.js 实战", users: { name: "王五" } } ] } ] ```
## 高级查询 ### 条件过滤查询 ```javascript // 查询特定分类下的所有文章 // 通过分类名称过滤,获取该分类下的所有文章 const { data, error } = await db .from("articles") .select("title, categories(*)") .eq("categories.name", "技术文章"); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { title: "JavaScript 高级特性", categories: { id: 1, name: "技术文章" } }, { title: "数据库优化技巧", categories: { id: 1, name: "技术文章" } } ] ```
### 计数查询 ```javascript // 获取每个分类及其包含的文章数量 // count 会统计每个分类下有多少篇文章 const { data, error } = await db .from("categories") .select(`*, articles(count)`); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { id: 1, name: "前端开发", articles: [ { count: 5 } // 该分类下有5篇文章 ] }, { id: 2, name: "后端开发", articles: [ { count: 3 } // 该分类下有3篇文章 ] } ] ```
### 仅获取总数 ```javascript // 只获取文章总数,不返回具体数据 const { count, error } = await db .from("articles") .select("*", { count: "exact", head: true }); console.log('总数:', count); ``` ### 内连接查询 ```javascript // 只获取有分类的文章,使用内连接确保分类存在 // !inner 确保只返回在 categories 表中有匹配记录的文章 const { data, error } = await db .from("articles") .select("title, categories!inner(name)") .eq("categories.name", "教程") .limit(10); console.log('查询结果:', data); ```
查看返回结果示例 ```javascript [ { title: "JavaScript 基础教程", categories: { name: "教程" } }, { title: "React 入门教程", categories: { name: "教程" } } ] ```
> 💡 注意:`!inner` 表示内连接,只返回那些在关联表中存在匹配记录的数据。如果不使用 `!inner`,即使文章的 `category_id` 为 null 或指向不存在的分类,文章仍会被返回,但 `categories` 字段为 null。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 - [修饰符](./modifiers) - 了解查询修饰符的使用方法 --- # Node SDK/MySQL 数据库/新增数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/mysql/insert ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); // 或指定实例和数据库 // const db = app.rdb({ // instance: "", // database: "" // }); ``` ## 新增数据 通过 `insert()` 方法向表中插入数据,支持单行和批量插入。 ```js db.from(tableName).insert(values, options) ``` - **tableName**:表名称 - **values**:要插入的数据 - **options**:插入选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | --------------- | ---- | ------------------------------------------------------ | | values | object \| Array | 是 | 要插入的值。传递对象以插入单行,或传递数组以插入多行。 | | options | object | 否 | 插入选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------ | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 创建记录 ```javascript // 向 articles 表中插入一条记录 const { error } = await db .from("articles") .insert({ title: "新文章标题", content: "文章内容" }); console.log('插入结果:', error ? '失败' : '成功'); ``` #### 创建记录并返回数据 ```javascript // 插入记录并返回插入的数据 const { data, error } = await db .from("articles") .insert({ title: "新文章标题", content: "文章内容" }) .select(); console.log('插入结果:', data); ``` > 💡 注意:仅当表中只有一个主键,且该主键为自增类型时,`.select()` 方法才会返回插入的行。 #### 批量创建记录 ```javascript // 一次性插入多条记录 const { error } = await db.from("articles").insert([ { title: "第一篇文章", content: "第一篇文章内容" }, { title: "第二篇文章", content: "第二篇文章内容" }, ]); console.log('批量插入结果:', error ? '失败' : '成功'); ``` --- # Node SDK/MySQL 数据库/更新数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/mysql/update ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); // 或指定实例和数据库 // const db = app.rdb({ // instance: "", // database: "" // }); ``` ## 更新数据 通过 `update()` 方法更新表中的数据,需要结合过滤器使用以定位要更新的行。 ```js db.from(tableName).update(values, options).filter() ``` - **tableName**:表名称 - **values**:要更新的数据 - **options**:更新选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ---------- | | values | object | 是 | 要更新的值 | | options | object | 否 | 更新选项 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------------------ | | count | string | 否 | 计数算法,用于计算更新的行数:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 更新数据 ```javascript // 更新 articles 表中 id 为 1 的记录,将 title 字段改为"新标题" const { error } = await db .from("articles") .update({ title: "新标题" }) .eq("id", 1); console.log('更新结果:', error ? '失败' : '成功'); ``` #### 批量更新 ```javascript // 更新所有状态为 draft 的文章,将状态改为 published const { error } = await db .from("articles") .update({ status: "published" }) .eq("status", "draft"); console.log('批量更新结果:', error ? '失败' : '成功'); ``` ### 返回结果 ```javascript { data: null, error: null } ``` > 💡 注意:`update()` 方法必须与 [过滤器](./filters) 结合使用,以定位您希望更新的行。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 --- # Node SDK/MySQL 数据库/更新或创建数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/mysql/upsert ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); // 或指定实例和数据库 // const db = app.rdb({ // instance: "", // database: "" // }); ``` ## 更新或创建数据 通过 `upsert()` 方法执行更新或插入操作,如果记录不存在则插入,存在则更新。 ```js db.from(tableName).upsert(values, options) ``` - **tableName**:表名称 - **values**:要 upsert 的数据 - **options**:upsert 选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | -------------------- | ---- | ---------------------------------------------------------------- | | values | object \| Array | 是 | 要 upsert 的值。传递对象以 upsert 单行,或传递数组以 upsert 多行 | | options | object | 否 | upsert 选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ---------------- | ------- | ---- | ----------------------------------------------------------------------------------------------------------------------------- | | count | string | 否 | 用于计算 upserted 行数的计数算法:`"exact"` - 底层执行 `COUNT(*)` | | ignoreDuplicates | boolean | 否 | 如果为 true,则忽略重复行。如果为 false,则重复行与现有行合并 | | onConflict | string | 否 | 逗号分隔的唯一索引列,用于指定如何确定重复行。当所有指定的列都相等时,两行被视为重复。在 MySQL 中,这通常对应于唯一索引或主键 | ### 代码示例 #### Upsert 数据 ```javascript // 如果 articles 表中存在 id 为 1 的记录则更新 title 为"MySQL 教程",不存在则插入新记录 const { data, error } = await db .from("articles") .upsert({ id: 1, title: "MySQL 教程" }); console.log('Upsert 结果:', data); ``` #### 批量 Upsert 数据 ```javascript // 批量 upsert 多条记录:id 为 1 的记录 title 设为"MySQL 教程",id 为 2 的记录 title 设为"Redis 指南" const { data, error } = await db.from("articles").upsert([ { id: 1, title: "MySQL 教程" }, { id: 2, title: "Redis 指南" }, ]); console.log('批量 Upsert 结果:', data); ``` #### 指定冲突列 ```javascript // 根据 title 字段判断冲突,如果存在 title 为"唯一标题"的记录则更新,否则插入 id 为 42 的新记录 const { data, error } = await db .from("articles") .upsert( { id: 42, title: "唯一标题", content: "文章内容" }, { onConflict: "title" } ); console.log('指定冲突列 Upsert 结果:', data); ``` ### 返回结果 ```javascript { data: [ { id: 1, title: "MySQL 教程", content: "文章内容" } ], error: null } ``` > 💡 注意:使用 `upsert()` 时必须包含主键列,这样才能正确判断是插入新行还是更新现有行。在 MySQL 中,upsert 通常通过 `ON DUPLICATE KEY UPDATE` 语法实现,当插入的数据与现有主键或唯一索引冲突时,会执行更新操作。`onConflict` 参数用于指定冲突判断的列,对应 MySQL 中的唯一索引列。 --- # Node SDK/MySQL 数据库/删除数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/mysql/delete ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const db = app.rdb(); // 或指定实例和数据库 // const db = app.rdb({ // instance: "", // database: "" // }); ``` ## 删除数据 通过 `delete()` 方法删除表中的数据,需要结合过滤器使用以定位要删除的行。 ```js db.from(tableName).delete(options).filter() ``` - **tableName**:表名称 - **options**:删除选项配置 ### 参数说明 | 参数 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ------------ | | options | object | 否 | 删除选项配置 | ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ------------------------------------------------------ | | count | string | 否 | 计数算法,可选值:`"exact"` - 底层执行 `COUNT(*)` | ### 代码示例 #### 删除单个记录 ```javascript // 删除 articles 表中 id 为 1 的记录 const { error } = await db.from("articles").delete().eq("id", 1); console.log('删除结果:', error ? '失败' : '成功'); ``` #### 批量删除记录 ```javascript // 批量删除 articles 表中 id 为 1、2、3 的多条记录 const { error } = await db.from("articles").delete().in("id", [1, 2, 3]); console.log('批量删除结果:', error ? '失败' : '成功'); ``` #### 条件删除 ```javascript // 删除所有状态为 draft 的文章 const { error } = await db.from("articles").delete().eq("status", "draft"); console.log('条件删除结果:', error ? '失败' : '成功'); ``` ### 返回结果 ```javascript { data: null, error: null } ``` > ⚠️ 注意:`delete()` 方法必须与 [过滤器](./filters) 结合使用,以定位您希望删除的行。确保您提供的条件准确表示您打算删除的所有记录,以避免意外删除数据。 ## 相关文档 - [过滤器](./filters) - 了解如何使用过滤条件 --- # Node SDK/MySQL 数据库/过滤器 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/mysql/filters 过滤器允许只返回符合特定条件的行。 过滤器可以用于 `select()`、`update()`、`upsert()` 和 `delete()` 查询。 ## eq 仅匹配列值等于指定值的行。 要检查列值是否为 `NULL`,应该使用 `.is()` 而不是 `eq`。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .eq("title", "腾讯云开发"); ``` ## neq 仅匹配列值不等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 不等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .neq("title", "腾讯云开发"); ``` ## gt 仅匹配列值大于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 大于 2 的记录 const { data, error } = await db.from("articles").select().gt("id", 2); ``` ## gte 仅匹配列值大于或等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 大于或等于 2 的记录 const { data, error } = await db.from("articles").select().gte("id", 2); ``` ## lt 仅匹配列值小于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 小于 2 的记录 const { data, error } = await db.from("articles").select().lt("id", 2); ``` ## lte 仅匹配列值小于或等于指定值的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | any | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 小于或等于 2 的记录 const { data, error } = await db.from("articles").select().lte("id", 2); ``` ## like 仅匹配列值符合特定模式的行(是否区分大小写受校验规则约束)。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------- | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | pattern | string | 必需 | 要匹配的模式 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 包含 "cloudbase" 的记录 const { data, error } = await db .from("articles") .select() .like("title", "%cloudbase%"); ``` ## is 仅匹配列值等于指定值的行。 对于非布尔列,主要用于检查列值是否为 `NULL`; 对于布尔列,也可以设置为 `true` 或 `false`,行为与 `.eq()` 相同。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ------------ | | column | string | 必需 | 要过滤的列 | | value | Object | 必需 | 用于过滤的值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 为 null 的记录 const { data, error } = await db.from("articles").select().is("title", null); ``` ## in 仅匹配列值包含在指定数组中的行。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ------ | ------ | ---- | ---------------- | | column | string | 必需 | 要过滤的列 | | values | Array | 必需 | 用于过滤的值数组 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 在指定数组 ["腾讯云开发", "云开发"] 中的记录 const { data, error } = await db .from("articles") .select() .in("title", ["腾讯云开发", "云开发"]); ``` ## match 仅匹配查询键中每个列都等于其关联值的行,相当于多个 `.eq()` 的简写形式。 ### 参数 | 参数 | 类型 | 必需 | 说明 | | ----- | ------------------------------------- | ---- | -------------------------------------- | | query | `Record` | 必需 | 过滤对象,列名作为键映射到它们的过滤值 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 等于 2 且 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .match({ id: 2, title: "腾讯云开发" }); ``` ## not 仅匹配不满足过滤条件的行。 与大多数过滤器不同,操作符和值按原样使用,需要遵循 MySQL 语法,还需要确保它们已正确转义。 `not()` 期望使用原始的 MySQL 语法作为过滤器值。 ```javascript .not('id', 'in', '(5,6,7)') // 对 `in` 过滤器使用 `()` .not('name', 'like', '%test%') // 使用 `not like` 进行模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | -------- | ------ | ---- | ----------------------------------- | | column | string | 必需 | 要过滤的列 | | operator | string | 必需 | 要取反的过滤操作符,遵循 MySQL 语法 | | value | any | 必需 | 过滤值,遵循 MySQL 语法 | ### 代码示例 ```javascript // 从 articles 表中查询所有 title 不为 null 的记录 const { data, error } = await db .from("articles") .select() .not("title", "is", null); ``` ## or 仅匹配满足至少一个过滤条件的行。 与大多数过滤器不同,过滤器按原样使用,需要遵循 MySQL 语法,还需要确保它们已正确转义。 目前无法跨多个表进行 `.or()` 过滤。 `or()` 期望使用原始的 MySQL 语法作为过滤器名称和值。 ```javascript .or('id.in.(5,6,7), name.like.%test%') // 对 `in` 过滤器使用 `()`,对模糊匹配使用 `like` 和 `%` .or('id.in.(5,6,7), name.not.like.%test%') // 使用 `not.like` 进行反向模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | ----------------------- | ------ | ---- | ------------------------------- | | filters | string | 必需 | 要使用的过滤器,遵循 MySQL 语法 | | options | object | 必需 | 命名参数 | | options.referencedTable | string | 可选 | 设置为过滤引用表而不是父表 | ### 代码示例 ```javascript // 从 articles 表中查询所有 id 等于 2 或者 title 等于 "腾讯云开发" 的记录 const { data, error } = await db .from("articles") .select() .or(`id.eq.2,title.eq.腾讯云开发`); ``` ## filter 仅匹配满足过滤条件的行,这是一个逃生舱口,应该尽可能使用特定的过滤器方法。 与大多数过滤器不同,操作符和值按原样使用,需要遵循 MySQL 语法,还需要确保它们已正确转义。 `filter()` 期望使用原始的 MySQL 语法作为过滤器值。 ```javascript .filter('id', 'in', '(5,6,7)') // 对 `in` 过滤器使用 `()` .filter('name', 'like', '%test%') // 使用 `like` 进行模糊匹配 .filter('name', 'not.like', '%test%') // 使用 `not.like` 进行反向模糊匹配 ``` ### 参数 | 参数 | 类型 | 必需 | 说明 | | -------- | ------ | ---- | --------------------------- | | column | string | 必需 | 要过滤的列 | | operator | string | 必需 | 过滤操作符,遵循 MySQL 语法 | | value | any | 必需 | 过滤值,遵循 MySQL 语法 | ### 代码示例 ```javascript // 查询 title 在指定值列表中的记录 // 从 articles 表中查询所有 title 在指定值列表 ["腾讯云开发", "云开发"] 中的记录 const { data, error } = await db .from("articles") .select() .filter("title", "in", "(腾讯云开发,云开发)"); // 在引用表上过滤 // 从 articles 表中查询所有关联的 categories.name 等于 "技术" 的记录 const { data, error } = await db .from("articles") .select() .filter("categories.name", "eq", "技术"); ``` --- # Node SDK/MySQL 数据库/修饰符 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/mysql/modifiers 修饰符用于改变响应的格式,与过滤器不同,它们作用于行级别以上的操作。 过滤器仅返回匹配特定条件的行而不改变行的形状,而修饰符允许改变响应的格式。 ## select 默认情况下,`.insert()` 不会返回插入的行。通过调用此方法,插入的行将在数据中返回。 > ⚠️ 注意:仅当表中只有一个主键,且该主键为自增类型时,`.select()` 方法才会返回插入的行。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------------------- | | columns | string | 否 | 要检索的列,用逗号分隔 | ### 代码示例 ```javascript // 在 articles 表中执行 upsert 操作,并返回修改后的完整记录 const { data, error } = await db .from("articles") .insert({ id: 1, title: "腾讯云开发新功能" }) .select(); ``` ## order 对查询结果进行排序。 可以多次调用此方法来按多个列排序。 可以对引用的表进行排序,但仅当在查询中使用 `!inner` 时,它才会影响父表的排序。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------- | | column | string | 是 | 要排序的列 | | options | object | 否 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------- | ---- | --------------------------------------------------------------- | | ascending | boolean | 否 | 如果为 `true`,结果将按升序排列 | | nullsFirst | boolean | 否 | 如果为 `true`,`null` 值将首先出现。如果为 `false`,`null` 值将最后出现 | | referencedTable | string | 否 | 设置为按引用表的列排序 | ### 代码示例 ```javascript // 按发布时间降序排列文章 const { data, error } = await db .from("articles") .select("id, title, published_at") .order("published_at", { ascending: false }); ``` #### 对引用表排序 ```javascript // 对引用表 categories 按 name 降序排列 const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .order("name", { referencedTable: "categories", ascending: false }); ``` ```javascript // 按引用表 category 的 name 列升序排列 const { data, error } = await db .from("articles") .select(` title, category:categories ( name ) `) .order("category(name)", { ascending: true }); ``` ## limit 限制返回的行数。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ---------------- | | count | number | 是 | 要返回的最大行数 | | options | object | 否 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------ | ---- | ---------------------------------------- | | referencedTable | string | 否 | 设置为限制引用表的行数,而不是父表的行数 | ### 代码示例 ```javascript // 限制返回 5 篇文章 const { data, error } = await db.from("articles").select("title").limit(5); ``` #### 限制引用表的行数 ```javascript // 每篇文章只返回 3 个分类 const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .limit(3, { referencedTable: "categories" }); ``` ## range 限制查询结果的范围。 通过从偏移量 `from` 开始到 `to` 结束来限制查询结果,只有在此范围内的记录会被返回。 这遵循查询顺序,如果没有排序子句,范围行为可能会不可预期。 `from` 和 `to` 值是基于 0 的且包含边界:`range(1, 3)` 将包括查询的第二、第三和第四行。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ------------------ | | from | number | 是 | 限制结果的起始索引 | | to | number | 是 | 限制结果的结束索引 | | options | object | 是 | 命名参数 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | --------------- | ------ | ---- | ---------------------------------------- | | referencedTable | string | 否 | 设置为限制引用表的行数,而不是父表的行数 | ### 代码示例 ```javascript // 获取文章列表的第 1-2 条记录(包含边界) const { data, error } = await db.from("articles").select("title").range(0, 1); ``` #### 对引用表使用范围限制 ```javascript // 每篇文章只返回前 2 个分类(索引 0-1) const { data, error } = await db .from("articles") .select(` title, categories ( name ) `) .range(0, 1, { referencedTable: "categories" }); ``` ## abortSignal > 💡 注意:此方法仅在 Web 环境下可用。 为 `fetch` 请求设置 `AbortSignal`。 可以使用此功能为请求设置超时。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------ | ----------- | ---- | ----------------------------- | | signal | AbortSignal | 是 | 用于 `fetch` 请求的 `AbortSignal` | ### 代码示例 ```javascript // 使用 AbortController 手动中止请求 const ac = new AbortController(); ac.abort(); const { data, error } = await db .from("articles") .select() .abortSignal(ac.signal); ``` ```javascript // 使用 AbortSignal.timeout 设置 1 秒超时 const { data, error } = await db .from("articles") .select() .abortSignal(AbortSignal.timeout(1000)); ``` ## single 检索单行数据。 将数据作为单个对象返回,而不是对象数组。 查询结果必须只有一行(例如使用 `.limit(1)` ),否则此方法会返回错误。 ### 代码示例 ```javascript // 获取第一篇文章的标题 const { data, error } = await db .from("articles") .select("title") .limit(1) .single(); ``` ## maybeSingle 检索零行或一行数据。 将数据作为单个对象返回,而不是对象数组。 查询结果必须为零行或一行(例如使用 `.limit(1)` ),否则此方法会返回错误。 ### 代码示例 ```javascript // 根据标题查找文章,可能不存在 const { data, error } = await db .from("articles") .select() .eq("title", "腾讯云开发新功能") .maybeSingle(); ``` ## overrideTypes 部分覆盖或替换成功响应的类型。 覆盖响应中 `data` 字段的返回类型,对于类型安全的查询结果转换非常有用。 ### 参数 | 参数名 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | ------------ | | type | T | 是 | 要覆盖的类型 | | options | object | 否 | 选项对象 | #### options 参数详情 | 参数名 | 类型 | 必填 | 描述 | | ------ | ------- | ---- | -------------------------------------- | | merge | boolean | 否 | 如果为 `false`,则完全替换类型而不是合并 | ### 代码示例 #### 完全覆盖数组类型 ```javascript // 将响应数据完全覆盖为自定义数组类型,merge: false 表示完全替换而非合并 const { data } = await db .from("articles") .select() .overrideTypes, { merge: false }>(); ``` #### 完全覆盖对象类型 ```javascript // 与 maybeSingle 一起使用,将单个对象响应完全覆盖为自定义类型 const { data } = await db .from("articles") .select() .maybeSingle() .overrideTypes(); ``` #### 部分覆盖数组类型 ```javascript // 部分覆盖数组元素类型,只指定需要改变的字段类型(如 status 字段) const { data } = await db .from("articles") .select() .overrideTypes>(); ``` #### 部分覆盖对象类型 ```javascript // 部分覆盖单个对象类型,只指定需要改变的字段类型(如 status 字段) const { data } = await db .from("articles") .select() .maybeSingle() .overrideTypes<{ status: "A" | "B" }>(); ``` --- # Node SDK/数据模型/查询数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/model/fetch ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models ``` ## 单条查询 通过指定条件查询单条记录。 ```js models.modelName.get(options) ``` - **modelName**: 数据模型名称 - **options**: 查询参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/server/node-sdk/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | ------------ | | **filter.where** | `object` | 否 | 查询条件 | | **select** | `object` | 是 | 指定返回字段 | ### 代码示例 ```javascript // 查询 todo数据模型 _id 为 todo-id-123 的记录 const todo = await models.todo.get({ filter: { where: { _id: { $eq: "todo-id-123" } } }, select: { $master: true, // 返回所有字段 } }) console.log('查询结果:', todo.data) ``` ### 返回结果 ```javascript { data: { records: [{ _id: "todo-id-123", title: "服务端任务", completed: false, // ... 其他字段 }], total: 1 } } ``` ## 多条查询 查询多条记录,支持条件筛选、排序、分页等。 ```js models.modelName.list(options) ``` - **modelName**: 数据模型名称 - **options**: 查询参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/server/node-sdk/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | ------------ | | **filter.where** | `object` | 否 | 查询条件 | | **select** | `object` | 是 | 指定返回字段 | ### 代码示例 ```javascript // 查询 todo数据模型 completed 为 false 的记录 const todos = await models.todo.list({ filter: { where: { completed: { $eq: false } }, } }) console.log('查询结果:', todos.data) ``` ### 返回结果 ```javascript { data: { records: [{ _id: "todo-id-1", title: "任务1", completed: false }, { _id: "todo-id-2", title: "任务2", completed: false } ], total: 2 } } --- # Node SDK/数据模型/新增数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/model/add ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models ``` ## 单条新增 向数据模型中添加一条新记录。 ```js models.modelName.create(options) ``` - **modelName**: 数据模型名称 - **options**: 新增参数 ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | -------- | -------- | ---- | ---------------- | | **data** | `object` | 是 | 要新增的数据对象 | ### 代码示例 ```javascript // 创建单条记录 const todo = await models.todo.create({ data: { title: "服务端任务", description: "使用 node-sdk 创建的任务", priority: "high", completed: false, createdAt: new Date(), createdBy: "system", metadata: { source: "api", version: "1.0" } } }) console.log('创建成功:', todo) ``` ## 批量新增 一次性向数据模型中添加多条记录。 > ⚠️ 注意:暂不支持批量新增关联关系字段 ```js models.modelName.createMany(options) ``` - **modelName**: 数据模型名称 - **options**: 新增参数 ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | -------- | ------- | ---- | ---------------------- | | **data** | `array` | 是 | 包含多个数据对象的数组 | ### 代码示例 ```javascript // 批量创建多条记录 const todos = await models.todo.createMany({ data: [{ title: "批量任务1", priority: "high" }, { title: "批量任务2", priority: "medium" }, { title: "批量任务3", priority: "low" } ] }) console.log('批量创建成功:', todos) --- # Node SDK/数据模型/更新数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/model/update ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models ``` ## 单条更新 根据条件更新单条记录。 ```js models.modelName.update(options) ``` - **modelName**: 数据模型名称 - **options**: 更新参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/server/node-sdk/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **data** | `object` | 是 | 要更新的数据对象 | | **filter.where** | `object` | 是 | 查询条件,确定要更新的记录 | ### 代码示例 ```javascript // 更新单条记录 const result = await models.todo.update({ data: { title: "更新后的任务", completed: true, updatedAt: new Date(), updatedBy: "admin" }, filter: { where: { _id: { $eq: "todo-id" // 推荐使用 _id 进行精确操作 } } } }) console.log('更新结果:', result) ``` ## 批量更新 根据查询条件批量更新多条记录。 > ⚠️ 注意:暂不支持批量更新关联关系字段 ```js models.modelName.updateMany(options) ``` - **modelName**: 数据模型名称 - **options**: 更新参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/server/node-sdk/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **data** | `object` | 是 | 要更新的数据对象 | | **filter.where** | `object` | 是 | 查询条件,确定要更新的记录 | ### 代码示例 ```javascript // 批量更新多条记录 const result = await models.todo.updateMany({ data: { status: "archived", archivedAt: new Date() }, filter: { where: { completed: { $eq: true }, createdAt: { $lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) // 30天前 } } } }) console.log('批量更新结果:', result) ``` ## 创建或更新数据 根据查询条件创建或更新记录。 > ⚠️ 注意:暂不支持批量新增更新关联关系字段 ```js models.modelName.upsert(options) ``` - **modelName**: 数据模型名称 - **options**: 更新参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/server/node-sdk/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **create** | `object` | 是 | 要新增的数据对象 | | **update** | `object` | 是 | 要更新的数据对象 | | **filter.where** | `object` | 是 | 查询条件,确定要更新的记录 | ### 代码示例 ```javascript const post = { title: "Hello World", body: "Hello World", _id: "foo", }; const { data } = await models.post.upsert({ create: post, update: post, filter: { where: { _id: { $eq: post._id, }, }, }, }); console.log(data); --- # Node SDK/数据模型/删除数据 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/model/delete ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models ``` ## 单条删除 根据条件删除单条记录。 ```js models.modelName.delete(options) ``` - **modelName**: 数据模型名称 - **options**: 删除参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/server/node-sdk/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **filter.where** | `object` | 是 | 查询条件,确定要删除的记录 | ### 代码示例 ```javascript // 删除单条记录 const result = await models.todo.delete({ filter: { where: { _id: { $eq: "todo-id" } } } }) console.log('删除结果:', result) ``` ## 批量删除 根据查询条件批量删除多条记录。 ```js models.modelName.deleteMany(options) ``` - **modelName**: 数据模型名称 - **options**: 删除参数 ### options 参数说明 具体查询参数说明请参考 [查询参数详解](/api-reference/server/node-sdk/model/select) 文档。 | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | ---- | -------------------------- | | **filter.where** | `object` | 是 | 查询条件,确定要删除的记录 | ### 代码示例 ```javascript // 批量删除已完成的任务 const result = await models.todo.deleteMany({ filter: { where: { completed: { $eq: true }, createdAt: { $lt: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000) // 90天前 } } } }) console.log('批量删除结果:', result) --- # Node SDK/数据模型/查询参数详解 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/model/select 云开发数据模型提供了强大的查询功能,支持复杂的查询条件、字段选择、排序、分页和关联查询。本文将详细介绍数据模型查询操作的各种参数和使用方法。 ## 查询参数概览 数据模型的查询操作主要包含以下参数: | 参数 | 类型 | 说明 | 必填 | | ------------ | ----------------- | ----------------------------------------------------------------------------- | ---- | | `envType` | `pre` \| `prod` | 指定查询的数据环境,不传为正式数据
`pre` :体验数据, `prod` :正式数据 | 否 | | `select` | `SelectParams` | 指定返回的字段 | 否 | | `filter` | `FilterParams` | 查询过滤条件 | 否 | | `orderBy` | `OrderByParams[]` | 排序条件 | 否 | | `pageSize` | `number` | 分页大小, 最大200 | 否 | | `pageNumber` | `number` | 页码 | 否 | | `getCount` | `boolean` | 是否返回总数 | 否 | ## 指定数据环境 `envType` `envType` 参数用于指定查询的数据环境,默认为正式数据, `pre` :体验数据, `prod` :正式数据。 ```javascript const { data } = await models.post.list({ envType: 'pre', filter: { where: {}, }, select: { $master: true, }, }); ``` ## 字段选择 `select` `select` 参数用于指定查询结果中应包含的字段,支持主模型字段和关联模型字段的精确控制。 > ✅ 建议仅选择需要的字段和关系来减少响应数据的大小并提高查询速度。 ### 返回主模型所有字段 使用 `$master: true` 可以返回主模型的所有字段: ```javascript const { data } = await models.post.get({ select: { $master: true, // 查询主模型所有的字段 }, filter: { where: { _id: { $eq: _id, // 推荐传入_id数据标识进行操作 }, }, }, }); console.log(data); // { // "owner": "Anonymous(95fblM7nvPi01yQmYxBvBg)", // "createdAt": 1717488585078, // "createBy": "Anonymous(95fblM7nvPi01yQmYxBvBg)", // "updateBy": "Anonymous(95fblM7nvPi01yQmYxBvBg)", // "_openid": "95fblM7nvPi01yQmYxBvBg", // "_id": "e2764d2d665ecbc9024b058f1d6b33a4", // "title": "你好,世界👋", // "body": "文章内容...", // "slug": "hello-world", // "updatedAt": 1717490751944 // } ``` ### 返回指定字段 通过指定字段名为 `true` 来选择特定字段: ```javascript const { data } = await models.post.list({ select: { _id: true, title: true, updatedAt: true, }, filter: { where: {}, }, getCount: true, }); console.log(data); // { // "records": [ // { // "_id": "e2764d2d665ecbc9024b058f1d6b33a4", // "title": "你好,世界👋", // "updatedAt": 1717492882289 // } // // ... 更多记录 // ], // "total": 51 // } ``` ### 返回关联模型字段 可以同时查询关联模型的字段: ```javascript const { data } = await models.post.list({ select: { _id: true, title: true, updatedAt: true, // 关联查询评论数据 comments: { _id: true, createdAt: true, comment: true, }, // 也可以直接传 true 返回评论中所有的字段 }, filter: { where: {}, }, getCount: true, }); console.log(data); // { // "records": [ // { // "_id": "9FSAHWM9VV", // "title": "Bonjour le Monde👋", // "updatedAt": 1718096503886, // "comments": [ // { // "_id": "9FSAJF3GLG", // "createdAt": 1718096509916, // "comment": "这是一条评论" // } // ] // } // // ... 更多记录 // ], // "total": 2 // } ``` ## 查询过滤 `filter` `filter` 参数用于指定查询条件,支持基础查询和关联查询两种方式。 ### 基础查询 `where` 基础查询用于对主模型字段进行过滤: ```javascript const { data } = await models.post.list({ filter: { where: { // 标题包含"世界" title: { $search: "世界", }, // 正文不为空 body: { $nempty: true, }, }, }, select: { $master: true, }, }); ``` ### 关联查询 `relateWhere` 关联查询用于根据关联模型的条件进行过滤: ```javascript const { data } = await models.post.list({ filter: { // 查询有评论的文章 relateWhere: { comments: { where: { comment: { $nempty: true, // 评论内容不为空 }, }, }, }, where: {}, // 主模型查询条件 }, select: { $master: true, comments: { comment: true, }, }, }); ``` ### 组合查询示例 需要用到与或关系时,可以使用 `where` 参数的 `$and` 和 `$or` 操作符 如果用到了 `$and` 和 `$or` 操作符,需要将它们放在一个数组中: ```javascript const { data } = await models.post.list({ filter: { where: { $and: [{ // 标题包含"技术"或"教程" $or: [{ title: { $search: "技术" } }, { title: { $search: "教程" } } ] }, { // 创建时间在最近30天内 createdAt: { $gte: Date.now() - 30 * 24 * 60 * 60 * 1000 } }, { // 状态为已发布 status: { $eq: "published" } } ] }, }, select: { _id: true, title: true, createdAt: true, status: true, }, }); ``` ### 查询运算符 | 操作符 | 说明 | 示例 | | --------------- | ------------------------------------------ | ----------------------------------------------------------- | | **$eq** | 等于 | `{status: {$eq: "active"}}` | | **$ne** | 不等于 | `{status: {$ne: "deleted"}}` | | **$gt** | 大于 | `{score: {$gt: 80}}` | | **$gte** | 大于等于 | `{age: {$gte: 18}}` | | **$lt** | 小于 | `{price: {$lt: 100}}` | | **$lte** | 小于等于 | `{discount: {$lte: 0.5}}` | | **$in** | 在数组中 | `{category: {$in: ["tech", "news"]}}` | | **$nin** | 不在数组中 | `{status: {$nin: ["deleted", "banned"]}}` | | **$and** | 逻辑与 | `{$and: [{age: {$gte: 18}}, {status: {$eq: "active"}}]}` | | **$or** | 逻辑或 | `{$or: [{priority: {$eq: "high"}}, {urgent: {$eq: true}}]}` | | **$search** | 模糊查询, 大小写敏感 | `{status: {$search: "active"}}` | | **$search_ci** | 模糊查询, 大小写不敏感 | `{status: {$search_ci: "active"}}` | | **$nsearch** | 查找不包含指定字符串值的记录, 大小写敏感 | `{status: {$nsearch: "active"}}` | | **$nsearch_ci** | 查找不包含指定字符串值的记录, 大小写不敏感 | `{status: {$nsearch_ci: "active"}}` | | **$empty** | 数据为 null | `{status: {$empty: true}}` | | **$nempty** | 数据不为 null | `{status: {$nempty: true}}` | ## 排序 `orderBy` `orderBy` 参数用于指定查询结果的排序方式,支持多字段排序(最多3个字段)。 ### 单字段排序 ```javascript const { data } = await models.post.list({ filter: { where: {}, }, orderBy: [{ createdAt: "desc", // 按创建时间倒序 }, ], select: { $master: true, }, }); ``` ### 多字段排序 ```javascript const { data } = await models.post.list({ filter: { where: {}, }, orderBy: [{ featured: "desc", // 首先按推荐状态倒序 }, { createdAt: "desc", // 然后按创建时间倒序 }, { title: "asc", // 最后按标题升序 }, ], select: { $master: true, }, }); ``` 排序方向: * `"asc"`: 升序排列 * `"desc"`: 降序排列 ## 分页参数 ### `pageSize` 和 `pageNumber` 用于实现分页查询: ```javascript const { data } = await models.post.list({ filter: { where: {}, }, pageSize: 10, // 每页10条记录 pageNumber: 2, // 第2页(从1开始) getCount: true, // 获取总数用于计算总页数 select: { $master: true, }, }); console.log(data); // { // "records": [...], // 第2页的10条记录 // "total": 156 // 总记录数 // } // 计算总页数 const totalPages = Math.ceil(data.total / 10); ``` ### `getCount` 控制是否返回满足条件的记录总数: ```javascript const { data } = await models.post.list({ filter: { where: { status: { $eq: "published" } }, }, getCount: true, // 设置为 true 时返回 total 字段 select: { _id: true, title: true, }, }); console.log(data.total); // 满足条件的总记录数 ``` ## 完整查询示例 以下是一个包含所有参数的完整查询示例: ```javascript const { data } = await models.post.list({ // 字段选择 select: { _id: true, title: true, excerpt: true, createdAt: true, updatedAt: true, author: { _id: true, name: true, avatar: true, }, comments: { _id: true, content: true, createdAt: true, }, }, // 查询条件 filter: { where: { $and: [{ status: { $eq: "published" } }, { $or: [{ title: { $search: "技术" } }, { tags: { $in: ["前端", "后端"] } } ] }, { createdAt: { $gte: Date.now() - 7 * 24 * 60 * 60 * 1000 // 最近7天 } } ] }, relateWhere: { comments: { where: { status: { $eq: "approved" } // 只查询已审核的评论 } } } }, // 排序 orderBy: [{ featured: "desc" }, { createdAt: "desc" } ], // 分页 pageSize: 20, pageNumber: 1, getCount: true, }); console.log("查询结果:", data.records); console.log("总记录数:", data.total); console.log("总页数:", Math.ceil(data.total / 20)); ``` ## 关联查询详解 ### 按关联关系过滤 使用 `relateWhere` 可以根据关联模型的条件来过滤主模型: ```javascript // 查询有特定评论的文章 const { data } = await models.post.list({ filter: { relateWhere: { // 文章必须有评论 comments: { where: { $and: [{ content: { $nempty: true } }, // 评论内容不为空 { status: { $eq: "approved" } }, // 评论已审核 { rating: { $gte: 4 } } // 评论评分>=4 ] } }, // 文章必须有标签 tags: { where: { name: { $in: ["技术", "教程", "分享"] } } } }, where: { status: { $eq: "published" } } }, select: { $master: true, comments: { content: true, rating: true, status: true, }, tags: { name: true, } } }); ``` ### 关联查询性能优化 1. **精确选择字段**:只选择需要的关联字段 2. **合理使用关联过滤**:避免过于复杂的关联条件 3. **分页处理**:对于大量关联数据使用分页 ```javascript // 优化示例:只获取必要的关联数据 const { data } = await models.post.list({ filter: { where: { status: { $eq: "published" } } }, select: { _id: true, title: true, excerpt: true, // 只获取作者的基本信息 author: { _id: true, name: true, }, // 只获取最新的3条评论 comments: { _id: true, content: true, createdAt: true, } }, orderBy: [{ createdAt: "desc" }], pageSize: 10, pageNumber: 1, }); ``` ## 最佳实践 ### 1. 字段选择优化 ```javascript // ❌ 不推荐:查询所有字段 const { data } = await models.post.list({ select: { $master: true }, filter: { where: {} } }); // ✅ 推荐:只选择需要的字段 const { data } = await models.post.list({ select: { _id: true, title: true, excerpt: true, createdAt: true, }, filter: { where: {} } }); ``` ### 2. 查询条件优化 ```javascript // ✅ 推荐:使用索引字段进行查询 const { data } = await models.post.list({ filter: { where: { _id: { $eq: "specific-id" }, // 主键查询,性能最佳 status: { $eq: "published" }, // 索引字段 createdAt: { $gte: timestamp } // 时间范围查询 } } }); // ⚠️ 注意:模糊查询性能较差,谨慎使用 const { data } = await models.post.list({ filter: { where: { title: { $search: "关键词" } // 模糊查询,建议配合其他条件 } } }); ``` ### 3. 分页处理 ```javascript // ✅ 推荐:合理的分页大小 const pageSize = 20; // 建议10-50之间 const pageNumber = 1; const { data } = await models.post.list({ filter: { where: {} }, pageSize, pageNumber, getCount: true, // 获取总数用于分页计算 select: { _id: true, title: true, createdAt: true, } }); // 计算分页信息 const totalPages = Math.ceil(data.total / pageSize); const hasNextPage = pageNumber < totalPages; const hasPrevPage = pageNumber > 1; ``` ### 4. 错误处理 ```javascript try { const { data } = await models.post.list({ filter: { where: { status: { $eq: "published" } } }, select: { _id: true, title: true, }, pageSize: 10, pageNumber: 1, getCount: true, }); console.log("查询成功:", data); } catch (error) { console.error("查询失败:", error); // 处理错误情况 } ``` 通过合理使用这些查询参数,可以实现高效、灵活的数据查询操作,满足各种复杂的业务需求。 --- # Node SDK/数据模型/执行 SQL 模板 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/model/sql-template ## 初始化 SDK ```js import cloudbase from "@cloudbase/node-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const models = app.models ``` ## 执行 SQL 模板 通过 SQL 模板在 MySQL 数据模型上执行参数化 SQL 查询。 ```js models.modelName.runSQLTemplate(options) ``` - **modelName**: 数据模型名称 - **options**: 执行参数 ### options 参数说明 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | **templateName** | `string` | 是 | SQL 模板名称 | | **params** | `object` | 否 | 模板参数对象 | ### 代码示例 #### 基础查询 ```javascript // 执行用户查询模板 const result = await models.user.runSQLTemplate({ templateName: "getUserByStatus", params: { status: "active" } }); console.log('查询结果:', result.data); ``` ## 注意事项 > 💡 注意: SQL 模板需要在 [云开发平台/MySQL数据库/数据模型](https://tcb.cloud.tencent.com/dev?#/db/mysql/model/?sourceType=internal_mysql) 中预先创建 > ⚠️ 注意: 参数值会自动进行 SQL 注入防护,请勿在模板中直接拼接用户输入 ## 相关文档 - [SQL 模板使用指南](/model/sql-template) --- # Node SDK/身份认证 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/auth ## getUserInfo ### 1. 接口描述 接口功能:获取用户信息 接口声明:`getUserInfo(): IGetUserInfoResult` ### 2. 输入参数 无 ### 3. 返回结果 | 字段 | 类型 | 必填 | 说明 | | -------------- | -------- | ---- | ------------------------------------------- | | `openId` | `string` | 是 | 微信 `openId`,非微信授权登录则空 | | `appId` | `string` | 是 | 微信 `appId`,非微信授权登录则空 | | `uid` | `string` | 是 | 用户唯一 ID | | `customUserId` | `string` | 是 | 开发者自定义的用户唯一 id,非自定义登录则空 | ### 4. 示例代码 ```ts import tcb from '@cloudbase/node-sdk' const app = tcb.init({ env: 'xxx' }) const auth = app.auth() exports.main = async (event, context) => { const { openId, //微信openId,非微信授权登录则空 appId, //微信appId,非微信授权登录则空 uid, //用户唯一ID customUserId //开发者自定义的用户唯一id,非自定义登录则空 } = auth.getUserInfo() console.log(openId, appId, uid, customUserId) } ``` ## getEndUserInfo ### 1. 接口描述 接口功能:获取用户信息 接口声明:`getEndUserInfo(uid?: string, opts?: ICustomReqOpts): Promise` :::tip 提示 自 Node SDK 2.2.5 版本起支持此接口 ::: ### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | ------ | -------- | ---- | --------------------------------------------------------- | | `uid` | `string` | 否 | 云开发用户身份标识。如果不传入,则从环境变量中读取用户信息。 | | `opts` | `Object` | 否 | 自定义请求配置 | ### 3. 返回结果 | 字段 | 类型 | 必填 | 说明 | | ----------- | ------------- | ---- | -------------- | | `userInfo` | `EndUserInfo` | 是 | 云开发用户信息 | | `requestId` | `string` | 否 | 请求唯一标识 | **EndUserInfo**: | 字段 | 类型 | 说明 | | -------------- | --------------------------------- | ------------------------------------------- | | `openId` | `string` | 微信 openId,非微信授权登录则空 | | `appId` | `string` | 微信 appId,非微信授权登录则空 | | `uid` | `string` | 用户唯一 ID | | `customUserId` | `string` | 开发者自定义的用户唯一 id,非自定义登录则空 | | `envName` | `string` | 云开发环境名 | | `nickName` | `string` | 用户昵称 | | `email` | `string` | 用户登录邮箱 | | `username` | `string` | 用户登录用户名 | | `hasPassword` | `boolean` | 用户是否设置密码 | | `gender` | `"MALE" \| "FEMALE" \| "UNKNOWN"` | 云开发登录用户名 | | `country` | `string` | 地理位置(国家) | | `province` | `string` | 地理位置(省份) | | `city` | `string` | 地理位置(城市) | | `avatarUrl` | `string` | 用户头像地址 | | `qqMiniOpenId` | `string` | 用户绑定的 qq 小程序标识 | ### 4. 示例代码 ```ts import tcb from '@cloudbase/node-sdk' const app = tcb.init({ env: "xxx" }); const auth = app.auth(); exports.main = async (event, context) => { try { const { userInfo } = await auth.getEndUserInfo("your user uuid"); console.log(userInfo); } catch (error) { console.log(error.message); } }; ``` ## queryUserInfo ### 1. 接口描述 接口功能:获取用户信息 接口声明:`queryUserInfo(query: IUserInfoQuery, opts?: ICustomReqOpts): Promise` :::tip 提示 自 Node SDK 2.6.1 版本起支持此接口 ::: ### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | ----- | -------------- | ---- | -------------------- | | `query` | `IUserInfoQuery` | 否 | 云开发用户身份标识。 | | `opts` | `Object` | 否 | 自定义请求配置 | ### IUserInfoQuery | 字段 | 类型 | 必填 | 说明 | | ------------ | -------- | ---- | ----------------------------------------------------------------- | | `platform` | `string` | 否 | 登录类型,已支持 PHONE, USERNAME, EMAIL,CUSTOM | | `platformId` | `string` | 否 | 登录标识,对应 platform 分别为手机号,用户名,邮箱,自定义登录 ID | | `uid` | `string` | 否 | 用户唯一 ID,若指定该字段,则优先用该字段搜索 | ### 3. 返回结果 | 字段 | 类型 | 必填 | 说明 | | ----------- | ------------- | ---- | -------------- | | `userInfo` | `EndUserInfo` | 是 | 云开发用户信息 | | `requestId` | `string` | 否 | 请求唯一标识 | **EndUserInfo**: | 字段 | 类型 | 说明 | | -------------- | --------------------------------- | ------------------------------------------- | | `openId` | `string` | 微信 openId,非微信授权登录则空 | | `appId` | `string` | 微信 appId,非微信授权登录则空 | | `uid` | `string` | 用户唯一 ID | | `customUserId` | `string` | 开发者自定义的用户唯一 id,非自定义登录则空 | | `envName` | `string` | 云开发环境名 | | `nickName` | `string` | 用户昵称 | | `email` | `string` | 用户登录邮箱 | | `username` | `string` | 用户登录用户名 | | `hasPassword` | `boolean` | 用户是否设置密码 | | `gender` | `"MALE" \| "FEMALE" \| "UNKNOWN"` | 云开发登录用户名 | | `country` | `string` | 地理位置(国家) | | `province` | `string` | 地理位置(省份) | | `city` | `string` | 地理位置(城市) | | `avatarUrl` | `string` | 用户头像地址 | | `qqMiniOpenId` | `string` | 用户绑定的 qq 小程序标识 | ### 4. 示例代码 ```ts import tcb from '@cloudbase/node-sdk' const app = tcb.init({ env: "xxx" }); const auth = app.auth(); exports.main = async (event, context) => { try { const { userInfo } = await auth.getEndUserInfo("your user uuid"); console.log(userInfo); } catch (error) { console.log(error.message); } }; ``` ## getClientIP ### 1. 接口描述 接口功能:获取客户端 IP 接口声明:`getClientIP(): string` ### 2. 输入参数 无 ### 3. 返回结果 | 字段 | 类型 | 必填 | 说明 | | ---- | ------ | ---- | --------- | | - | string | 是 | 客户端 IP | ### 4. 示例代码 ```ts import tcb from '@cloudbase/node-sdk' const app = tcb.init({ env: 'xxx' }) const auth = app.auth() exports.main = async (event, context) => { const ip = auth.getClientIP() // string console.log(ip) } ``` ## createTicket ### 1. 接口描述 接口功能:获取自定义登录的登录凭据 ticket 接口声明:`createTicket: (customUserId: string, options?: ICreateTicketOpts) => string` ### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | -------------------- | -------- | ---- | ------------------------------ | | `customUserId` | `string` | 是 | 开发者自定义的用户唯一 id | | `ICreateTicketOpts` | `string` | 是 | 微信 `appId`,非微信授权登录则空 | `ICreateTicketOpts` | 字段 | 类型 | 必填 | 说明 | | --------- | ------ | ---- | ----------------------- | | `refresh` | `number` | 否 | `access_token` 的刷新时间 | | `expire` | `number` | 否 | `access_token` 的过期时间 | ### 3. 返回结果 | 字段 | 类型 | 必填 | 说明 | | ---- | ------ | ---- | --------------------- | | - | string | 是 | 自定义登录凭据 ticket | ### 4. 示例代码 ```ts import tcb from '@cloudbase/node-sdk' const app = tcb.init({ env: 'xxx', credentials: require('/path/to/your/tcb_custom_login.json') }) const auth = app.auth() const customUserId = '123456' // 开发者自定义的用户唯一id const ticket = auth.createTicket(customUserId, { refresh: 3600 * 1000 // access_token的刷新时间 }) console.log(ticket) ``` --- # Node SDK/云函数 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/functions ## callFunction ### 1. 接口描述 接口功能:执行云函数 接口声明:`callFunction(callFunctionOptions: ICallFunctionOptions, opts?: ICustomReqOpts): Promise>` ### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | --------------------- | ----------------------------- | ---- | -------------------------------------------------------------- | | `callFunctionOptions` | `ICallFunctionOptions` | 是 | 云函数调用请求参数 | | `opts` | `ICustomReqOpts` | 否 | 自定义配置,目前支持 SDK 请求超时时间设置,`{timeout: number}` | `ICallFunctionOptions` 云函数参数: | 字段 | 类型 | 必填 | 说明 | | ----------- | -------- | ---- | --------------------------------------------------------------------------------- | | `name` | `string` | 是 | 云函数名称 | | `data` | `ParaT` | 否 | 云函数参数 | | `qualifier` | `string` | 否 | 云函数版本标识:`$LATEST`(最新版本) `1` `2` `3`,缺省时按平台配置流量比例分配流量 | 函数型云托管 额外可以传参数,传入 `type:'cloudrun'` 参数后,将调用函数型云托管服务 | 字段 | 类型 | 必填 | 说明 | | -------- | ------------------------ | ---- | ---------------------------------- | | `type` | `cloudrun` | 否 | 是否调用 基于 云托管的函数型云托管 | | `method` | `string` | 否 | HTTP 请求方法 | | `path` | `string` | 否 | HTTP 请求路径 | | `header` | `Record` | 否 | HTTP 请求头 | | `data` | `object` | 否 | HTTP 请求体 | ### 3. 返回结果 `Promise>` | 字段 | 类型 | 必填 | 说明 | | ----------- | --------- | ---- | ------------------------ | | `result` | `ResultT` | 否 | 云函数执行结果 | | `requestId` | `string` | 否 | 请求序列号,用于错误排查 | > 函数执行报错,将通过异常抛出 ### 4. 示例代码 ```ts import tcb from "@cloudbase/node-sdk"; const app = tcb.init({ env: "xxx", }); exports.main = async (event, context) => { const res = await app.callFunction({ name: "test", data: { a: 1 }, }); console.log(res); // 打印函数调用结果 const res1 = await app.callFunction( { name: "test", data: { a: 1 }, }, { timeout: 5000, } ); console.log(res1); }; ``` 函数型云托管 示例代码: ```ts import tcb from "@cloudbase/node-sdk"; exports.main = async (event, context) => { const { httpContext } = context; const { url, httpMethod } = httpContext; return `[${httpMethod}][${url}] Hello world!`; const tcbapp = tcb.init({ context }); const result = await tcbapp.callFunction( { name: "helloworld", // 函数型云托管 参数 type: "cloudrun", method: "POST", path: "/abc", data: { key1: "test value 1", key2: "test value 2", }, }, { timeout: 5000, } ); console.log(result); }; ``` --- # Node SDK/云托管 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/cloudrun ## callContainer ### 1. 接口描述 接口功能:调用云托管服务 接口声明:`callContainer(callContainerOptions: ICallContainerOptions, opts?: ICustomReqOpts): Promise>` ### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | ---------------------- | ------------------------------ | ---- | -------------------------------------------------------------- | | `callContainerOptions` | `ICallContainerOptions` | 是 | 云托管调用请求参数 | | `opts` | `ICustomReqOpts` | 否 | 自定义配置,目前支持 SDK 请求超时时间设置,`{timeout: number}` | `ICallContainerOptions` | 字段 | 类型 | 必填 | 说明 | | -------- | ------------------------ | ---- | ------------- | | `name` | `string` | 是 | 云托管服务名 | | `method` | `string` | 否 | HTTP 请求方法 | | `path` | `string` | 否 | HTTP 请求路径 | | `header` | `Record` | 否 | HTTP 请求头 | | `data` | `object` | 否 | HTTP 请求体 | ### 3. 返回结果 `Promise>` | 字段 | 类型 | 必填 | 说明 | | ------------ | ------------------------ | ---- | ----------------------------- | | `requestId` | `string` | 否 | RequestId,用于错误排查 | | `statusCode` | `number` | 否 | HTTP 响应状态码,用于错误排查 | | `header` | `Record` | 否 | HTTP 响应头,用于错误排查 | | `data` | `ResultT` | 否 | HTTP 响应体数据 | > 函数执行报错,将通过异常抛出 ### 4. 示例代码 ```ts import tcb from "@cloudbase/node-sdk"; exports.main = async (event, context) => { const { httpContext } = context; const { url, httpMethod } = httpContext; console.log(`[${httpMethod}][${url}]`); const tcbapp = tcb.init({ context }); const result = await tcbapp.callContainer( { name: "helloworld", method: "POST", path: "/abc", data: { key1: "test value 1", key2: "test value 2", }, }, { timeout: 5000, } ); console.log(result); }; ``` --- # Node SDK/云存储 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/storage ## uploadFile #### 1. 接口描述 接口功能:上传文件到文件管理服务 接口声明:`uploadFile(object: Object): Promise` #### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | ----------- | ------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cloudPath | String | 是 | 文件的绝对路径,包含文件名。例如 foo/bar.jpg、foo/bar/baz.jpg 等,不能包含除[0-9 , a-z , A-Z]、/、!、-、\_、.、、\*和中文以外的字符,使用 / 字符来实现类似传统文件系统的层级结构。[查看详情](https://cloud.tencent.com/document/product/436/13324) | | fileContent | fs.ReadStream | 是 | buffer 或要上传的文件[可读流](https://nodejs.org/api/stream.html#stream_class_stream_readable) | #### 3. 返回结果 | 字段 | 类型 | 必填 | 说明 | | --------- | ------ | ---- | --------------------------------------- | | fileID | string | 否 | 文件唯一 ID,用来访问文件,建议存储起来 | | requestId | string | 是 | 请求序列号,用于错误排查 | | code | string | 否 | 状态码,操作成功则不返回 | | message | string | 否 | 错误描述,操作成功则不返回 | #### 4. 示例代码 ```javascript // 初始化 const tcb = require("@cloudbase/node-sdk"); const fs = require("fs"); const app = tcb.init({ env: "xxx", }); exports.main = async (event, context) => { const result = await app.uploadFile({ cloudPath: "test-admin.jpeg", fileContent: fs.createReadStream(`${__dirname}/cos.jpeg`), }); console.log(result.fileID); // 输出文件ID }; ``` ## getTempFileURL #### 1. 接口描述 接口功能:获取文件 CDN 下载链接 接口声明:`getTempFileURL(object: IGetTempFileURLOpts, opts: Object): Promise` #### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | ------ | ------------------- | ---- | ----------------------------------------------------- | | object | IGetTempFileURLOpts | 是 | 获取下载链接请求参数 | | opts | Object | 否 | 自定义配置,目前只支持超时时间设置,`{timeout: number}` | ##### IGetTempFileURLOpts | 字段 | 类型 | 必填 | 说明 | | -------- | -------------------------------- | ---- | -------------------------- | | fileList | <Array>.string Or fileItem | 是 | 要下载的文件 ID 组成的数组 | ##### fileItem | 字段 | 类型 | 必填 | 说明 | | ------ | ------ | ---- | -------------- | | fileID | string | 是 | 文件 ID | | maxAge | number | 是 | 文件链接有效期 | #### 3. 返回结果 | 字段 | 类型 | 必填 | 说明 | | --------- | ------------------------- | ---- | ---------------------------- | | fileList | <Array>.fileUrlItem | 否 | 存储下载链接的数组 | | requestId | string | 是 | 请求序列号,用于错误排查 | | code | string | 否 | 状态码,操作成功则为 SUCCESS | | message | string | 否 | 错误描述 | ##### fileUrlItem | 字段 | 类型 | 必填 | 说明 | | ----------- | ------ | ---- | ------------------------ | | code | string | 否 | 删除结果,成功为 SUCCESS | | fileID | string | 是 | 文件 ID | | tempFileURL | string | 是 | 文件访问链接 | #### 4. 示例代码 ```javascript // 初始化 const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "xxx", }); exports.main = async (event, context) => { const result = await app.getTempFileURL({ fileList: ["cloud://test-28farb/a.png"], }); result.fileList.forEach((item) => { console.log(item.tempFileURL); // 打印文件访问链接 }); }; ``` ## deleteFile #### 1. 接口描述 接口功能:删除文件 接口声明:`deleteFile(object: IDeleteFileOpts, opts: Object): Promise` #### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | ------ | --------------- | ---- | ----------------------------------------------------- | | object | IDeleteFileOpts | 是 | 删除文件请求参数 | | opts | Object | 否 | 自定义配置,目前只支持超时时间设置,`{timeout: number}` | ##### IDeleteFileOpts | 字段 | 类型 | 必填 | 说明 | | -------- | -------------------- | ---- | -------------------------- | | fileList | <Array>.string | 是 | 要删除的文件 ID 组成的数组 | #### 3. 返回结果 | 字段 | 类型 | 必填 | 说明 | | --------- | ---------------------------- | ---- | ------------------------ | | code | string | 否 | 状态码,操作成功则不返回 | | message | string | 否 | 错误描述 | | fileList | <Array>.deleteFileItem | 否 | 删除结果组成的数组 | | requestId | string | 是 | 请求序列号,用于错误排查 | ##### deleteFileItem | 字段 | 类型 | 必填 | 说明 | | ------ | ------ | ---- | ------------------------ | | code | string | 否 | 删除结果,成功为 SUCCESS | | fileID | string | 是 | 文件 ID | #### 4. 示例代码 ```javascript // 初始化 const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "xxx", }); exports.main = async (event, context) => { const result = await app.deleteFile({ fileList: ["HHOeahVQ0fRTDsums4GVgMCsF6CE3wb7kmIkZbX+yilTJE4NPSQQW5EYks"], }); result.fileList.forEach((item) => { if (item.code === "SUCCESS") { // 文件删除成功 } }); }; ``` ## downloadFile #### 1. 接口描述 接口功能:下载文件到本地 接口声明:`downloadFile(object: IDownloadFileOpts, opts: Object): Promise` #### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | ------ | ----------------- | ---- | ----------------------------------------------------- | | object | IDownloadFileOpts | 是 | 下载文件请求参数 | | opts | Object | 否 | 自定义配置,目前只支持超时时间设置,`{timeout: number}` | ##### IDownloadFileOpts | 字段 | 类型 | 必填 | 说明 | | ------------ | ------ | ---- | ---------------------- | | fileID | string | 是 | 要下载的文件的 id | | tempFilePath | string | 否 | 下载的文件要存储的位置 | #### 3. 返回结果 | 字段 | 类型 | 必填 | 说明 | | ----------- | ------ | ---- | ------------------------------------------------------ | | code | string | 否 | 状态码,操作成功则不返回 | | message | string | 否 | 错误描述 | | fileContent | buffer | 否 | 下载的文件的内容。如果传入 tempFilePath 则不返回该字段 | | requestId | string | 是 | 请求序列号,用于错误排查 | #### 4. 示例代码 ```javascript // 初始化 const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "xxx", }); exports.main = async (event, context) => { const result = await app.downloadFile({ fileID: "cloud://aa-99j9f/my-photo.png", // tempFilePath: '/tmp/test/storage/my-photo.png' }); // 未传入tempFilePath 可打印fileContent, 传入则进入对应目录查看文件 console.log(result.fileContent); }; ``` ## copyFile #### 1. 接口描述 接口功能:批量复制文件 接口声明:`copyFile({ fileList }: { fileList: ICopyFileParam[] }): Promise` :::tip 注意 该操作不会对文件的权限进行更改。 ::: #### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | -------- | ---------------------------- | ---- | -------------------------- | | fileList | <Array>.ICopyFileParam | 是 | 要复制的文件信息组成的数组 | `ICopyFileParam`: | 字段 | 类型 | 必填 | 说明 | | -------------- | ------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | srcPath | string | 是 | 源文件的绝对路径,包含文件名。例如 foo/bar.jpg、foo/bar/baz.jpg 等,不能包含除[0-9 , a-z , A-Z]、/、!、-、\_、.、、\*和中文以外的字符,使用 / 字符来实现类似传统文件系统的层级结构 | | dstPath | string | 是 | 目标文件的绝对路径,包含文件名。例如 foo/bar.jpg、foo/bar/baz.jpg 等,不能包含除[0-9 , a-z , A-Z]、/、!、-、\_、.、、\*和中文以外的字符,使用 / 字符来实现类似传统文件系统的层级结构 | | overwrite | boolean | 否 | 当目标文件已经存在时,是否允许覆盖已有文件,默认 true | | removeOriginal | boolean | 否 | 复制文件后是否删除源文件,默认 false | #### 3. 返回结果 | 字段 | 类型 | 必填 | 说明 | | --------- | -------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------- | | requestId | string | 是 | 请求序列号,用于错误排查 | | fileList | `.{fileId?: string,code?: string,message?: string}` | 是 | 请求结果列表。若该请求成功,fileId 为文件 id,如 cloud://xxx.yyy/abc.png;若该请求失败,返回 code 和 message 描述错误信息 | #### 4. 示例代码 ```javascript // 初始化 sdk const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "xxx", // 填入环境 ID }); const path = "a.png"; // 填入源文件路径 const fileList = [ { srcPath: path, dstPath: `dst/${path}`, // 填入目标文件路径 removeOriginal: true, // 复制后删除源文件,等效为移动文件 }, ]; const result = await app.copyFile({ fileList }); ``` --- # Node SDK/AI > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/ai 提供云开发 AI 接入能力,快速接入大模型和 Agent。 ## 初始化 使用 node sdk 进行初始化后,通过 `.ai()` 获取 [AI](#ai-1) 实例。 ```js import tcb from '@cloudbase/node-sdk' const app = tcb.init({ env: 'xxx' }) const ai = app.ai() ``` ## AI 用于创建 AI 模型的类。 ### createModel() 创建指定的 AI 文生文模型。 #### 使用示例 ```ts const model = ai.createModel("cloudbase"); ``` #### 类型声明 ```ts function createModel(model: string): ChatModel; ``` 返回一个实现了 [ChatModel](#chatmodel) 抽象类的模型实例,该实例提供 AI 生成文本相关能力。 ### createImageModel() 创建指定的图片生成模型。 #### 使用示例 ```ts const imageModel = ai.createImageModel("hunyuan-image"); ``` #### 类型声明 ```ts function createImageModel(provider: string): ImageModel; ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | -------- | ---- | ------ | ------------------------------------ | | provider | 是 | string | 模型提供方名称,如 `"hunyuan-image"` | #### 返回值 返回一个 [ImageModel](#imagemodel) 实例,该实例提供 AI 图片生成相关能力。 ### registerFunctionTool() 注册函数工具。在进行大模型调用时,可以告知大模型可用的函数工具,当大模型的响应被解析为工具调用时,会自动调用对应的函数工具。 #### 使用示例 ```js // 省略初始化 AI sdk 的操作... // 1. 定义获取天气的工具,详见 FunctionTool 类型 const getWeatherTool = { name: "get_weather", description: "返回某个城市的天气信息。调用示例:get_weather({city: '北京'})", fn: ({ city }) => `${city}的天气是:秋高气爽!!!`, // 在这定义工具执行的内容 parameters: { type: "object", properties: { city: { type: "string", description: "要查询的城市", }, }, required: ["city"], }, }; // 2. 注册我们刚定义好的工具 ai.registerFunctionTool(getWeatherTool); // 3. 在给大模型发送消息的同时,告知大模型可用一个获取天气的工具 const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", tools: [getWeatherTool], // 这里我们传入了获取天气工具 messages: [ { role: "user", content: "请告诉我北京的天气状况", }, ], }); console.log(result.text); ``` #### 类型声明 ```ts function registerFunctionTool(functionTool: FunctionTool); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ------------ | ---- | ------------ | ---------------------------------- | | functionTool | 是 | FunctionTool | 详见 [FunctionTool](#functiontool) | #### 返回值 `undefined` ## ChatModel 这个抽象类描述了 AI 生文模型类提供的接口。 ### generateText() 调用大模型生成文本。 #### 使用示例 ```ts const hy = ai.createModel("cloudbase"); const res = await hy.generateText({ model: "hy3", messages: [{ role: "user", content: "你好,请你介绍一下李白" }], }); console.log(res.text); // 打印生成的文本 ``` #### 类型声明 ```ts function generateText(data: BaseChatModelInput): Promise<{ rawResponses: Array; text: string; messages: Array; usage: Usage; error?: unknown; }>; ``` #### 参数 | 参数名 | 必填 | 类型 | 示例 | 说明 | | ------ | ---- | ------------------ | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | data | 是 | BaseChatModelInput | `{model: "deepseek-v4-flash", messages: [{ role: "user", content: "你好,请你介绍一下李白" }]}` | 参数类型定义为 [BaseChatModelInput](#basechatmodelinput) ,作为基础的入参定义。实际上各家大模型还会有各自独特的输入参数,开发者可按需根据实际使用的大模型官方文档传入其他不在此类型中被定义的参数,充分利用大模型提供的能力。其他参数会被透传至大模型接口, SDK 侧不对它们不做额外处理。 | #### 返回值 | 属性名 | 类型 | 示例 | 说明 | | ------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | | text | string | `"李白是一位唐朝诗人。"` | 大模型生成的文本。 | | rawResponses | unknown[] | `[{"choices": [{"finish_reason": "stop","message": {"role": "assistant", "content": "你好呀,有什么我可以帮忙的吗?"}}], "usage": {"prompt_tokens": 14, "completion_tokens": 9, "total_tokens": 23}}]` | 大模型的完整返回值,包含更多详细数据,如消息创建时间等等。由于各家大模型返回值互有出入,请根据实际情况使用。 | | res.messages | ChatModelMessage[] | `[{role: 'user', content: '你好'},{role: 'assistant', content: '你好!很高兴与你交流。请问有什么我可以帮助你的吗?无论是关于生活、工作、学习还是其他方面的问题,我都会尽力为你提供帮助。'}]` | 本次调用的完整消息列表。 | | usage | Usage | `{"completion_tokens":33,"prompt_tokens":3,"total_tokens":36}` | 本次调用消耗的 token。 | | error | unknown | | 调用过程中产生的错误。 | ### streamText() 以流式调用大模型生成文本。流式调用时,生成的文本及其他响应数据会通过 SSE 返回,该接口的返回值对 SSE 做了不同程度的封装,开发者能根据实际需求获取到文本流和完整数据流。 #### 使用示例 ```ts const hy = ai.createModel("cloudbase"); const res = await hy.streamText({ model: "hy3", messages: [{ role: "user", content: "你好,请你介绍一下李白" }], }); for await (let str of res.textStream) { console.log(str); // 打印生成的文本 } for await (let data of res.dataStream) { console.log(data); // 打印每次返回的完整数据 } ``` #### 类型声明 ```ts function streamText(data: BaseChatModelInput): Promise; ``` #### 参数 | 参数名 | 必填 | 类型 | 示例 | 说明 | | ------ | ---- | ------------------ | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | data | 是 | BaseChatModelInput | `{model: "deepseek-v4-flash", messages: [{ role: "user", content: "你好,请你介绍一下李白" }]}` | 参数类型定义为 [BaseChatModelInput](#basechatmodelinput) ,作为基础的入参定义。实际上各家大模型还会有各自独特的输入参数,开发者可按需根据实际使用的大模型官方文档传入其他不在此类型中被定义的参数,充分利用大模型提供的能力。其他参数会被透传至大模型接口, SDK 侧不对它们不做额外处理。 | #### 返回值 | StreamTextResult 属性名 | 类型 | 说明 | | ----------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | textStream | `ReadableStream` | 以流式返回的大模型生成文本,可参考使用示例获取到生成的增量文本。 | | dataStream | `ReadableStream` | 以流式返回的大模型响应数据,可参考使用示例获取到生成的增量数据。由于各家大模型响应值互有出入,请根据实际情况合理使用。 | | messages | `Promise` | 本次调用的完整消息列表。 | | usage | `Promise` | 本次调用消耗的 token。 | | error | `unknown` | 本次调用产生的错误。 | | DataChunk 属性名 | 类型 | 说明 | | ------------------------ | ------------------ | ---------------------- | | choices | `Array` | | | choices[n].finish_reason | `string` | 模型终止推断的原因。 | | choices[n].delta | `ChatModelMessage` | 本次请求的消息。 | | usage | `Usage` | 本次请求消耗的 token。 | | rawResponse | `unknown` | 大模型返回的原始回复。 | #### 示例 ```js const hy = ai.createModel("cloudbase"); const res = await hy.streamText({ model: "hy3", messages: [{ role: "user", content: "1+1结果是" }], }); // 文本流 for await (let str of res.textStream) { console.log(str); } // 1 // 加 // 1 // 的结果 // 是 // 2 // 。 // 数据流 for await (let str of res.dataStream) { console.log(str); } // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hy3", version: "202404011000", choices: Array(1), …} ``` ## ImageModel 这个类描述了 AI 图片生成模型类提供的接口。 ### generateImage() 调用大模型生成图片。 #### 使用示例 ```ts const imageModel = ai.createImageModel("hunyuan-image"); const res = await imageModel.generateImage({ model: "HY-Image-3.0-Plus-4090-Tob-v1.0", prompt: "一只可爱的猫咪在草地上玩耍", }); console.log(res.data[0].url); // 打印生成的图片 URL ``` #### 类型声明 ```ts function generateImage(input: HunyuanARGenerateImageInput): Promise; ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ------ | ---- | --------------------------- | ------------------------------------------------------------------------------ | | input | 是 | HunyuanARGenerateImageInput | 图片生成参数,详见 [HunyuanARGenerateImageInput](#hunyuanargenerateimageinput) | #### 返回值 `Promise`,详见 [HunyuanARGenerateImageOutput](#hunyuanargenerateimageoutput)。 ## 类型定义 ### BaseChatModelInput ```ts interface BaseChatModelInput { model: string; messages: Array; temperature?: number; topP?: number; tools?: Array; toolChoice?: "none" | "auto" | "custom"; maxSteps?: number; onStepFinish?: (prop: IOnStepFinish) => unknown; } ``` | BaseChatModelInput 属性名 | 类型 | 说明 | | ------------------------- | ---------------------------------- | --------------------------------------------------- | | model | `string` | 模型名称。 | | messages | `Array` | 消息列表。 | | temperature | `number` | 采样温度,控制输出的随机性。 | | topP | `number` | 温度采样,即模型考虑概率质量为 top_p 的标记的结果。 | | tools | `Array` | 大模型可用的工具列表。 | | toolChoice | `string` | 指定大模型选择工具的方式。 | | maxSteps | `number` | 请求大模型的最大次数。 | | onStepFinish | `(prop: IOnStepFinish) => unknown` | 当对大模型的一次请求完成时,出发的回调函数。 | ### ChatModelMessage ```ts type ChatModelMessage = | UserMessage | SystemMessage | AssistantMessage | ToolMessage; ``` #### UserMessage ```ts type UserMessage = { role: "user"; content: string; }; ``` #### SystemMessage ```ts type SystemMessage = { role: "system"; content: string; }; ``` #### AssistantMessage ```ts type AssistantMessage = { role: "assistant"; content?: string; tool_calls?: Array; }; ``` #### ToolMessage ```ts type ToolMessage = { role: "tool"; tool_call_id: string; content: string; }; ``` ### ToolCall ```ts export type ToolCall = { id: string; type: string; function: { name: string; arguments: string }; }; ``` ### FunctionTool 工具定义类型。 ```ts type FunctionTool = { name: string; description: string; fn: CallableFunction; parameters: object; }; ``` | FunctionTool 属性名 | 类型 | 说明 | | ------------------- | ------------------ | -------------------------------------------------------------------------------------------------- | | name | `string` | 工具名称。 | | description | `string` | 工具的描述。清楚的工具描述有助于大模型认识工具的用途。 | | fn | `CallableFunction` | 工具的执行函数。当 AI SDK 解析出大模型的响应需要该工具调用时,会调用此函数,并将结果返回给大模型。 | | parameters | `object` | 工具执行函数的入参。需要使用 JSON Schema 的格式定义入参。 | ### IOnStepFinish 大模型响应后出发的回调函数的入参类型。 ```ts interface IOnStepFinish { messages: Array; text?: string; toolCall?: ToolCall; toolResult?: unknown; finishReason?: string; stepUsage?: Usage; totalUsage?: Usage; } ``` | IOnStepFinish 属性名 | 类型 | 说明 | | -------------------- | ------------------------- | -------------------------------- | | messages | `Array` | 到当前步骤为止所有的消息列表。 | | text | `string` | 当前响应的文本。 | | toolCall | `ToolCall` | 当前响应调用的工具。 | | toolResult | `unknown` | 对应的工具调用结果。 | | finishReason | `string` | 大模型推理结束的原因。 | | stepUsage | `Usage` | 当前步骤所花费的 token。 | | totalUsage | `Usage` | 到当前步骤为止所花费的总 token。 | ### Usage ```ts type Usage = { completion_tokens: number; prompt_tokens: number; total_tokens: number; }; ``` ### HunyuanARGenerateImageInput 混元图片生成 v3.0 输入参数,支持自定义宽高比。 ```ts interface HunyuanARGenerateImageInput { /** 模型名称:HY-Image-3.0-Plus-4090-Tob-v1.0 */ model: 'HY-Image-3.0-Plus-4090-Tob-v1.0'; /** 生成图片使用的文本,不超过 8192 字符 */ prompt: string; /** * 图片尺寸,格式 "${宽}x${高}",默认 "1024x1024" * 宽高范围 [512, 2048],面积不超过 1024x1024 */ size?: string; /** 生成种子,仅当生成图片数为 1 时生效,范围 [1, 4294967295] */ seed?: number; /** 业务自定义水印内容,限制 16 字符,生成在图片右下角 */ footnote?: string; /** 是否对 prompt 改写,默认开启。改写会增加约 30s 耗时 */ revise?: { value: boolean }; /** 改写是否开启 thinking 模式,默认开启。开启后效果提升但耗时增加(最大 60s) */ enable_thinking?: { value: boolean }; } ``` | HunyuanARGenerateImageInput 属性名 | 类型 | 说明 | | ---------------------------------- | -------------------- | ---------------------------------------------------------------------------------------- | | model | `string` | 模型名称,`HY-Image-3.0-Plus-4090-Tob-v1.0` | | prompt | `string` | 生成图片使用的文本,不超过 8192 字符 | | size | `string` | 图片尺寸,格式 `"宽x高"`,默认 `"1024x1024"`,宽高范围 [512, 2048],面积不超过 1024x1024 | | seed | `number` | 生成种子,仅当生成图片数为 1 时生效,范围 [1, 4294967295] | | footnote | `string` | 业务自定义水印内容,限制 16 字符,生成在图片右下角 | | revise | `{ value: boolean }` | 是否对 prompt 改写,默认开启。改写会增加约 30s 耗时 | | enable_thinking | `{ value: boolean }` | 改写是否开启 thinking 模式,默认开启。开启后效果提升但耗时增加(最大 60s) | ### HunyuanARGenerateImageOutput 混元图片生成 v3.0 输出。 ```ts interface HunyuanARGenerateImageOutput { /** 此次请求的 id */ id: string; /** unix 时间戳 */ created: number; /** 返回的图片生成内容 */ data: Array<{ /** 生成的图片 url,有效期为 24 小时 */ url: string; /** 改写后的 prompt */ revised_prompt?: string; }>; } ``` | HunyuanARGenerateImageOutput 属性名 | 类型 | 说明 | | ----------------------------------- | --------------- | -------------------------------- | | id | `string` | 此次请求的 id | | created | `number` | unix 时间戳 | | data | `Array` | 返回的图片生成内容 | | data[n].url | `string` | 生成的图片 url,有效期为 24 小时 | | data[n].revised_prompt | `string` | 改写后的 prompt | --- # Node SDK/消息通知 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/templateNotify ## 能力介绍 “消息通知”是云开发工作台提供的一种消息通知能力,开发者可根据自定义通知策略,在满足相关通知触发规则时,进行包括不限于站内信、短信等渠道的消息通知发送。 ## 能力特征 ### 通知策略管理 * 包含当前环境下所有消息通知策略,可进行策略的启停、编辑、删除(通知策略失效并不可恢复)。 * 通知策略主要构成部分包含监控类型、触发动作、触发条件、通知内容、通知对象、通知渠道。 ### 通知触发历史 * 成功触发的通知消息记录,支持以时间维度进行消息通知的记录查询。 * 支持历史通知消息的查看与删除(不可恢复)。 ## 使用说明 ### 1.自动触发 1. 前往工作台-数据管理,新建目标监控数据模型“消息通知”,以姓名(name)和年龄(age)字段为触发条件示例。 ![image.png](https://qcloudimg.tencent-cloud.cn/raw/454a0d9782726a4f15b4d8c25375bb53.png) 2. 前往工作台-消息通知管理,新建通知策略。 ![image.png](https://qcloudimg.tencent-cloud.cn/raw/92aae5011b51a28899336b3b2727a66c.png) 3. 编辑通知策略相关信息,例如设置数据模型触发动作为新增数据,触发条件为“name=rocky并且age >55“,那么当目标数据模型新增的数据满足此条件时,才会触发通知消息。 ![image.png](https://qcloudimg.tencent-cloud.cn/raw/4490ac7764075ccb88ec96c2adc09630.png) * 触发方式:自动触发和手动触发(默认为自动触发)。 * 监控类型:目前支持“数据模型”,模型名选择预先创建好的数据模型,触发动作包含数据的增删改,触发条件支持同时设置多个字段。 * 通知机制:实时推送。 * 通知内容:根据多行输入框里面的提示内容,填写相应的文案,其中双大括号 `{{}}` 表示模板变量,用来引用数据模型字段值。 * 通知用户:表示可接收到通知消息的用户对象,可在工作台-用户权限管理添加更多用户。 ![](https://qcloudimg.tencent-cloud.cn/raw/9a6a1c823ed6967748c1b3f30a186c00.png) * 通知渠道:目前支持工作台站内信和短信(以通知用户绑定的手机号为推送对象)通知。 4. 配置好相关信息后,保存通知策略。 ![image.png](https://qcloudimg.tencent-cloud.cn/raw/9fa88a67d9b4d10614df03b0612edaf2.png) 5. 前往数据模型管理界面,在模板模型中新增数据,进行消息通知推送测试。 ![image.png](https://qcloudimg.tencent-cloud.cn/raw/06f5abaeb42f88556617581acfcbb4fd.png) 6. 通知策略触发成功后,工作台会收到站内通知。 ![image.png](https://qcloudimg.tencent-cloud.cn/raw/bf75f427a4daf8b9cd73b4eac4d7cfbb.png) 7. 站内信消息面板,可以查询全部类型消息,包括监控告警和业务消息。 ![image.png](https://qcloudimg.tencent-cloud.cn/raw/ea190e3b0086513be099e558f721a969.png) 8. 手机短信通知展示。 ![image.png](https://qcloudimg.tencent-cloud.cn/raw/7301673bc3721b08a125335a05c9eae1.png) 9. 可以在消息通知管理界面,查询消息通知触发历史。 ![image.png](https://qcloudimg.tencent-cloud.cn/raw/ae64798c1639829bce52f4e0c2865556.png) ### 2.手动触发(SDK触发) #### **1. 接口介绍** * 接口描述:可在 [微信云开发云后台 / 消息通知管理](https://tcb.cloud.tencent.com/cloud-admin/#/management/notification/policyList) 模块配置消息策略,触发方式选择“手动触发”,需要在业务代码中主动调用,才会触发消息通知。 ![image.png](https://qcloudimg.tencent-cloud.cn/raw/a98ff0430a3361c2c40ee7318ae70597.png) * 接口功能:发送通知消息。 * 接口声明:`sendTemplateNotification(params: ITemplateNotifyReq, opts?: ICustomReqOpts)`。 #### **2. 输入参数** | 字段 | 类型 | 必填 | 说明 | | -------- | -------------------- | ---- | -------------------------------------------------------------- | | `params` | `ITemplateNotifyReq` | 是 | 请求参数 | | `opts` | `ICustomReqOpts` | 否 | 自定义配置,目前支持 SDK 请求超时时间设置,`{timeout: number}` | `ITemplateNotifyReq` | 字段 | 类型 | 必填 | 说明 | | ---------- | ------------------------- | ---- | ------------------------------------ | | `notifyId` | `string` | 是 | 告警策略 Id | | `data` | `Record` | 否 | 告警策略中模板变量键值对 | | `url` | `string` | 否 | 用户收到告警消息后点击打开的页面地址 | #### **3. 返回结果** `Promise>` | 字段 | 类型 | 必填 | 说明 | | ----------- | --------- | ---- | ------------------------- | | `result` | `ResultT` | 否 | 执行结果 | | `requestId` | `string` | 否 | 请求唯一 Id,用于错误排查 | > 代码执行报错,将通过异常抛出 #### **4. 示例代码** ```ts // 云函数入口文件 const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ secretId: "", // 腾讯云 API 固定密钥对,在云函数内执行可不填 secretKey: "", // 同上 env: "", // 云开发环境id }); exports.main = async (event, context) => { const result = await app.sendTemplateNotification({ notifyId: "xxx", data: { orderId: "aaa", amount: 200 }, url: "https://cloud.tencent.com/solution/la", }); return result; }; ``` :::info说明 云开发数据库支持监听集合中符合查询条件的数据的更新事件,更多可参考文档 [实时推送](https://docs.cloudbase.net/database/realtime) ::: --- # Node SDK/其他参考/CHANGELOG > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/changelog ## v4.0.2 - [feat] 基于 `@cloudbase/js-sdk` 4.x 架构,与 JS SDK 共享底层能力 - [breaking] 底层依赖从独立实现切换为 `@cloudbase/js-sdk`,最低 Node.js 版本要求与 js-sdk 保持一致 ## v3.18.0 - [feat] 添加 API Key 授权支持 ## v3.17.0 - [fix] 修复 wx request 响应类型问题 - [feat] 支持自定义数据库 ## v3.16.0 - [feat] 新增 AI 模块支持 ## v3.15.0 - [feat] callContainer 支持更多请求方法 - [feat] 添加 RDB 客户端支持 ## v3.14.0 - [feat] 支持配置 region 参数以区分国内和国际区域 - [feat] 添加 MySQL SDK 支持 - [feat] 添加 callApis 方法 ## v2.3.4 - [bugfix] 修复 geo centerSphere 兼容问题 ## v2.3.3 - [feat] 跨帐号支持调用支持 ## v2.3.2 - [fix] 本地注入环境变量使用 bug 修复 ## v2.3.1 - [fix] geoWithin api 补齐 centerSphere 用法 ## v2.3.0 - [add] 新增 updateAndReturn 接口 - [refactor] 重构 aggregate 接口 ## v2.2.5 - [add] 新增 getEndUserInfo 接口 ## v2.2.4 - [fix] 修复获取用户信息接口 ## v2.2.3 - [add] 支持云开发容器免秘钥调用 ## v2.2.1 - [add] 数据库条件查询,批量插入支持事务 ## v2.1.1 - [fix] 修复数据库处理 query 或 data 内容时 undefined 转为 null 导致报错的问题 ## v2.1.0 - [add] 新增获取云函数下全部环境变量方法 getCloudbaseContext - [add] 请求时对 env 参数校验,若 init 时未指定 env,则 warning 提示使用默认环境, 若指定 env 但请求时发现为空,则抛错处理 - [add] createTicket 时 校验私钥中环境与 init 指定环境是否一致,不一致则报错 ## v2.0.2 - [add] 新增扩展注册,调用方法 ## v2.0.1 - [fix] 修复 db transaction add 接口未携带事务 ID 导致异常问题 ## v2.0.0 - [add] 支持 db 新特性&灰度兼容 ## v1.1.1 - [fix] 修复函数调函数时请求签名问题 ## v1.1.0 - [add] 支持函数灰度发布 - [fix] 修复 elemMatch 中使用 neq 无效 bug ## v1.0.2 - [fix] 本地调试逻辑优化 ## v1.0.0 - [add] 迁移 tcb-admin-node sdk 功能至本仓库 --- # Node SDK/其他参考/常见问题 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/faq ### 查询条件正确但结果为空 在使用数据库查询时,如果返回空结果,通常有以下两种情况: 1. 没有符合查询条件的数据 2. 数据被权限控制过滤 #### 排查方法 1. **确认数据存在性** - 在云开发控制台直接查看集合中是否存在目标数据 - 检查数据的创建时间和字段值是否符合预期 2. **检查权限配置** - 查看集合的基础权限设置是否允许当前用户读取 - 数据库查询时会以 `_openid` 字段作为数据归属判定依据 - 如果使用安全规则,验证规则表达式是否正确 - 确认查询条件是否包含安全规则要求的必要字段 3. **验证查询条件** - 简化查询条件,逐步排查哪个条件导致结果为空 - 检查字段名称、数据类型和查询语法是否正确 --- # manager_node Documentation > 云开发NODE-SDK API文档 # Manager Node SDK/初始化 > 当前文档链接: https://docs.cloudbase.net/api-reference/manager/node/introduction [![NPM Version](https://img.shields.io/npm/v/@cloudbase/manager-node)](https://www.npmjs.com/package/@cloudbase/manager-node) **@cloudbase/manager-node** 支持开发者通过接口形式对云开发提供的云函数、数据库、文件存储等资源进行创建、管理、配置等操作 ## 安装 SDK ```bash npm install @cloudbase/manager-node -S ``` ```bash yarn add @cloudbase/manager-node ``` ```bash pnpm add @cloudbase/manager-node ``` ## 初始化 SDK ```js import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ secretId: "your-secret-id", secretKey: "your-secret-key", envId: "your-env-id" // 替换为您的环境 ID }) // 获取各功能模块 const { database, functions, storage, env, commonService } = app ``` 如果需要管理**多个腾讯云账号**下的云开发服务,你也可以选择使用构造方法 `new CloudBase()` 初始化 SDK,每次初始化都会得到一个全新的 `CloudBase` 实例。 ```js import CloudBase from '@cloudbase/manager-node' // 管理账号 A 的环境 const appA = new CloudBase({ secretId: "account-a-secret-id", secretKey: "account-a-secret-key", envId: "account-a-env-id" }) // 管理账号 B 的环境 const appB = new CloudBase({ secretId: "account-b-secret-id", secretKey: "account-b-secret-key", envId: "account-b-env-id" }) ``` ### 初始化参数 | 字段 | 类型 | 必填 | 说明 | | ----------- | -------- | ---- | ------------------------------------------------------------------------------------------------------------------------ | | `envId` | `string` | 否 | TCB 环境 ID,在 [云开发控制台](https://tcb.cloud.tencent.com/dev) 概览页面获取。不填使用默认环境,因为后续的很多接口依赖于环境,在未传递的情况下,需要通过 `addEnvironment()` 添加环境方可进行后续接口调用 | | `secretId` | `string` | 否 | 腾讯云 API 固定密钥对 `secretId`,前往 [腾讯云控制台/API密钥管理](https://console.cloud.tencent.com/cam/capi) 生成。`secretId` 与 `secretKey` 必须同时传递,在云函数内执行可不填 | | `secretKey` | `string` | 否 | 腾讯云 API 固定密钥对 `secretKey`,前往 [腾讯云控制台/API密钥管理](https://console.cloud.tencent.com/cam/capi) 生成。`secretId` 与 `secretKey` 必须同时传递,在云函数内执行可不填 | | `token` | `string` | 否 | 腾讯云 API 临时密钥对 `Token`,通过 [申请扮演角色临时访问凭证](https://cloud.tencent.com/document/product/1312/48197) 接口获取。传递此字段时意味着使用的是临时凭证,如果显式传递临时凭证,则此参数必传 | | `timeout` | `number` | 否 | 调用接口的超时时间(ms),默认为 `15000ms`,即 `15s` | | `proxy` | `string` | 否 | 调用接口时使用的 http 代理 url | | `region` | `string` | 否 | 指定地域,目前支持的地域列表[参考](https://cloud.tencent.com/document/product/876/51107),云函数环境下默认取当前云函数环境地域 | :::warning 注意事项 - 服务端环境下(非云函数环境),需要传入 `secretId`、`secretKey` - 若当前账户为子账户,请先通过主账户授权开通 `QcloudTCBFullAccess`(云开发全读写访问)、`QcloudAccessForTCBRole`(云开发对云资源的访问权限),[子账户权限设置指引](https://cloud.tencent.com/document/product/598/36256) ::: ## 登录鉴权 **manager-node** 使用管理员权限,不同环境下的鉴权方式如下: ### 云函数环境 在云函数环境中,**无需手动配置鉴权参数**。 ```js import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ envId: "your-env-id" }) ``` ### Node.js 环境 在 Node.js 环境中,需要配置以下任意一种鉴权方式: 前往 [腾讯云控制台/API密钥管理](https://console.cloud.tencent.com/cam/capi) 生成 `secretKey`、`secretId` ```js import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ envId: "your-env-id", secretId: "your-secret-id", secretKey: "your-secret-key" }) ``` 通过 [申请扮演角色临时访问凭证](https://cloud.tencent.com/document/product/1312/48197) 接口获取 `token` ```js import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ envId: "your-env-id", token: "your-token" }) ``` ## 环境管理 ### 核心概念 `@cloudbase/manager-node` 支持管理多个云开发环境。SDK 内部通过 `EnvironmentManager` 管理所有环境实例,并维护一个**当前环境**的概念: - **环境实例 (Environment)**:代表一个云开发环境,每个环境有独立的资源(云函数、数据库、存储等) - **当前环境**:SDK 默认操作的环境,所有资源管理 API(如 `app.functions`、`app.database`)都作用于当前环境 - **环境管理器 (EnvironmentManager)**:管理多个环境实例,支持环境的添加和切换 ### 单环境使用 大多数场景下,你只需要管理一个环境: ```js import CloudBase from '@cloudbase/manager-node' // 初始化时指定 envId,该环境自动成为当前环境 const app = CloudBase.init({ secretId: "your-secret-id", secretKey: "your-secret-key", envId: "your-env-id" }) // 直接操作当前环境的资源 await app.functions.getFunctionDetail("test") await app.database.listCollections() ``` ### 多环境管理 当需要管理多个环境时(例如同时操作开发环境和生产环境),可以使用环境管理器: ```js import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ secretId: "your-secret-id", secretKey: "your-secret-key", envId: "dev-env-id" // 开发环境作为初始当前环境 }) // 获取环境管理器 const envManager = app.getEnvironmentManager() // 添加生产环境 envManager.addEnvironment("prod-env-id") // 获取当前环境(此时是 dev-env-id) const currentEnv = envManager.currentEnvironment() console.log(currentEnv.EnvId) // 输出:dev-env-id // 操作开发环境的云函数 await app.functions.getFunctionDetail("test") // 切换到生产环境 envManager.switchEnv("prod-env-id") // 现在操作的是生产环境的云函数 await app.functions.getFunctionDetail("test") ``` :::tip 提示 - `addEnvironment(envId)` 不会在云开发服务中创建新环境,envId 对应的环境必须预先存在 - 如果初始化时未指定 `envId`,添加的第一个环境会自动成为当前环境 - 调用 `switchEnv(envId)` 后,所有资源管理对象(`functions`、`database` 等)都会作用于新的当前环境 ::: ### 环境管理 API | 方法 | 说明 | 返回值 | |------|------|--------| | `getEnvironmentManager()` | 获取环境管理器实例 | `EnvironmentManager` | | `addEnvironment(envId)` | 添加环境实例到管理器中 | `void` | | `currentEnvironment()` | 获取当前环境实例 | `Environment` | | `switchEnv(envId)` | 切换当前环境 | `void` | ### 功能模块 所有资源管理能力都与当前环境关联,通过以下属性访问: ```js const app = CloudBase.init({ secretId: "your-secret-id", secretKey: "your-secret-key", envId: "your-env-id" }) // 获取当前环境下的各功能模块 app.functions // 云函数管理 app.database // 数据库管理 app.storage // 文件存储管理 app.env // 环境配置管理(含自定义域名与访问路由,v5.0.0+) app.commonService // 通用服务管理 app.hosting // 静态网站托管 app.cloudBaseRun // 云托管(TCBR)管理 app.cloudAppService // 应用部署管理(v5.0.0+) app.log // CLS 日志服务(v5.0.0+) app.permission // 权限与角色管理(v5.0.0+) app.mysql // MySQL 数据库管理(v5.0.0+) app.docs // 官方文档检索与读取(v5.0.0+) app.aiModel // AI 模型分组管理(v5.2.0+) ``` 每个模块提供完整的资源管理能力,详见各功能模块的 API 文档。 ## 示例代码 ### 基础使用 ```js import CloudBase from '@cloudbase/manager-node' // 1. 初始化 CloudBase const app = CloudBase.init({ secretId: "your-secret-id", secretKey: "your-secret-key", envId: "your-env-id" }) async function test() { // 2. 调用云函数管理下的 getFunction let result = await app.functions.getFunctionDetail("test") // 3. 打印结果 console.log(result) } test() ``` --- # Manager Node SDK/模块管理/环境管理 > 当前文档链接: https://docs.cloudbase.net/api-reference/manager/node/env ## listEnvs ### 1. 接口描述 接口功能:获取所有环境信息 接口声明:`listEnvs(): Promise` ### 2. 输入参数 无 ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | --------------------- | ------------ | | RequestId | 是 | String | 请求唯一标识 | | EnvList | 是 | `Array` | 环境数组 | #### EnvItem | 字段 | 必填 | 类型 | 说明 | | --------------- | ---- | ------ | ------------------------------------------------ | | EnvId | 是 | String | 环境 ID | | Source | 是 | String | 环境来源。`miniapp` 微信小程序 / `qcloud` 腾讯云 | | Alias | 是 | String | 环境别名 | | Status | 是 | String | 环境状态。`NORMAL` 正常 / `UNAVAILABLE` 不可用 | | CreateTime | 是 | String | 创建时间 | | UpdateTime | 是 | String | 更新时间 | | Region | 是 | String | 地域,如 `ap-shanghai` | | PackageId | 是 | String | 环境套餐 ID | | PackageName | 是 | String | 套餐中文名称 | | PackageType | 否 | String | 套餐类型。空 / `baas` / `tcbr` | | EnvType | 否 | String | 环境类型。`baas` / `run` / `hosting` / `weda` | | PayMode | 否 | String | 付费方式。`prepayment` 预付费 / `postpaid` 后付费 | | IsAutoDegrade | 否 | Boolean | 是否到期自动降为免费版 | | IsDefault | 否 | Boolean | 是否为默认环境 | | EnvChannel | 否 | String | 环境渠道 | | Databases | 否 | Array | 数据库资源详情 | | Storages | 否 | Array | 存储资源详情 | | Functions | 否 | Array | 函数资源详情 | | LogServices | 否 | Array | 日志资源详情 | | StaticStorages | 否 | [StaticStorageInfo](#staticstorageinfo)[] | 静态资源信息(含静态托管域名) | | Tags | 否 | Array<{Key, Value}> | 环境标签 | | EnvPreferences | 否 | Record<string, any> | 用户环境配置 | #### StaticStorageInfo 静态 CDN 资源信息。 | 字段 | 类型 | 说明 | | --------------- | ------ | ------------------------------------------------- | | StaticDomain | String | 静态托管 CDN 域名,如 `xxx.tcloudbaseapp.com` | | DefaultDirName | String | 静态 CDN 默认文件夹,当前为根目录 | | Status | String | 资源状态。`process` / `online` / `offline` / `init` | | Region | String | COS 所属区域 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { const res = await env.listEnvs() const { EnvList } = res for (let envItem of EnvList) { console.log(envItem.EnvId, envItem.Alias) // 获取静态托管域名 const staticDomain = envItem.StaticStorages?.[0]?.StaticDomain if (staticDomain) { console.log('静态托管域名:', staticDomain) } } } test() ``` ## getEnvAuthDomains ### 1. 接口描述 接口功能:获取合法域名列表 接口声明:`getEnvAuthDomains(): Promise` ### 2. 输入参数 无 ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | ------- | ---- | -------------------- | -------- | | Domains | 是 | `Array` | 域名列表 | | envId | 是 | String | 环境 ID | **Domain** | 字段 | 必填 | 类型 | 说明 | | ---------- | ---- | ------ | ----------------------------------- | | Id | 是 | String | 域名 ID | | Domain | 是 | String | 域名 | | Type | 是 | String | 域名类型。包含以下取值:system user | | Status | 是 | String | 状态。包含以下取值:ENABLE DISABLE | | CreateTime | 是 | String | 创建时间 | | UpdateTime | 是 | String | 更新时间 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { const res = await env.getEnvAuthDomains() const { Domains } = res for (let domain in Domains) { console.log(domain) } } test() ``` ## createEnvDomain ### 1. 接口描述 接口功能:添加环境安全域名 接口声明:`createEnvDomain(domains: string[]): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ------- | ---- | -------------------- | ------------ | | domains | 是 | `Array` | 安全域名数组 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --------- | ------ | ------- | | RequestId | String | 请求 ID | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { const res = await env.createEnvDomain(['luke.com']) console.log(res) } test() ``` ## deleteEnvDomain ### 1. 接口描述 接口功能:删除环境安全域名 接口声明:`deleteEnvDomain(domains: string[]): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ------- | ---- | -------------------- | ------------ | | domains | 是 | `Array` | 安全域名数组 | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | ---------------- | | RequestId | 是 | String | 请求 ID | | Deleted | 是 | Number | 删除成功的域名数 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { const res = await env.deleteEnvDomain(['luke.com']) const { Deleted } = res console.log(Deleted) // 删除域名数 } test() ``` ## getEnvInfo ### 1. 接口描述 接口功能:获取环境信息 接口声明:`getEnvInfo(): Promise` ### 2. 输入参数 无 ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------------------- | -------- | | RequestId | 是 | String | 请求 ID | | EnvInfo | 是 | [EnvItem](#EnvItem) | 环境信息 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { const res = await env.getEnvInfo() const { EnvInfo } = res console.log(EnvInfo) } test() ``` ## updateEnvInfo ### 1. 接口描述 接口功能:修改环境别名 接口声明:`updateEnvInfo(alias: string): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ----- | ---- | ------ | -------- | | alias | 是 | String | 环境别名 | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | ------- | | RequestId | 是 | String | 请求 ID | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { const res = await env.updateEnvInfo('lukemodify') console.log(res) } test() ``` ## getLoginConfigList ### 1. 接口描述 接口功能:拉取登录配置列表 接口声明:`getLoginConfigList(): Promise` ### 2. 输入参数 无 ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | ---------- | ---- | ------------------------ | ------------ | | RequestId | 是 | String | 请求 ID | | ConfigList | 是 | `Array` | 登录配置列表 | #### ConfigItem | 字段 | 必填 | 类型 | 说明 | | ---------- | ---- | ------ | ------------ | | Id | 是 | String | 配置 ID | | Platform | 是 | String | 平台类型 | | PlatformId | 是 | String | 平台 ID | | Status | 是 | String | 配置状态 | | UpdateTime | 是 | String | 配置更新时间 | | CreateTime | 是 | String | 配置创建时间 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { const res = await env.getLoginConfigList() const { ConfigList } = res for (let config in ConfigList) { console.log(config) } } test() ``` ## createLoginConfig ### 1. 接口描述 接口功能:创建登录方式 接口声明:`createLoginConfig(platform, appId, appSecret): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | --------------------------------------------------------------------------------------- | | platform | 是 | String | 平台 "WECHAT-OPEN" "WECHAT-PUBLIC" "QQ" "ANONYMOUS" | | appId | 是 | String | 第三方平台的 AppID 注意:如果是匿名登录方式(platform:ANONYMOUS),appId 填: anonymous | | appSecret | 否 | String | 第三方平台的 AppSecret,注意:如果是 匿名登录方式(platform:ANONYMOUS), appSecret 可不填 | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | ------- | | RequestId | 是 | String | 请求 ID | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { await env.createLoginConfig('WECHAT-OPEN', 'appId', 'appSecret') } test() ``` ## updateLoginConfig ### 1. 接口描述 接口功能:更新登录方式配置 接口声明:`updateLoginConfig(configId, status, appId, appSecret): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | -------------------------------------------------------- | | configId | 是 | String | 配置的记录 ID | | status | 是 | String | ”ENABLE”, “DISABLE” | | appId | 是 | String | 第三方平台的 AppId,如果是匿名登录, appId 填: anonymous | | appSecret | 否 | String | 第三方平台的 AppSecret,如果是匿名登录,可不填该字段 | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | ------- | | RequestId | 是 | String | 请求 ID | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { const loginConfigRes = await env.getLoginConfigList() await env.updateLoginConfig( loginConfigRes.ConfigList[0].Id, 'ENABLE', 'appId', 'appSecret' ) } test() ``` ## createBillingDeal ### 1. 接口描述 接口功能:创建环境计费订单 接口声明:`createBillingDeal(params: CreateBillingDealParams): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | -------------- | ---- | ---------- | ------------------------------------------------------------------------------------------------------------ | | DealType | 是 | String | 下单操作类型,可取值:`purchase`(购买)、`renew`(续费)、`modify`(变配) | | ProductType | 是 | String | 购买的产品类型,可取值:`tcb-baas`(baas 套餐)、`tcb-promotion`(大促包)、`tcb-package`(资源包) | | PackageId | 是 | String | 目标下单产品/套餐 ID | | CreateAndPay | 否 | Boolean | 是否自动支付 | | TimeSpan | 否 | Number | 购买时长 | | TimeUnit | 否 | String | 购买时长单位,可取值:`d`(天)、`m`(月)、`y`(年)、`p`(一次性) | | ResourceId | 否 | String | 资源唯一标识符 | | Source | 否 | String | 来源,可取值:`qcloud`、`miniapp` | | Alias | 否 | String | 资源别名 | | EnvId | 否 | String | 环境 ID | | EnableExcess | 否 | Boolean | 开启超限按量计费 | | ModifyPackageId| 否 | String | 变配目标产品/套餐 ID | | Extension | 否 | String | 附加信息(JSON 字符串) | | AutoVoucher | 否 | Boolean | 是否自动选择代金券支付 | | ResourceTypes | 否 | String[] | 资源类型,新购环境时需要发货的资源,可取值:`flexdb`、`cos`、`cdn`、`scf` | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | ---------- | | RequestId | 是 | String | 请求 ID | | EnvId | 是 | String | 环境 ID | | TranId | 是 | String | 订单交易 ID | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { const res = await env.createBillingDeal({ DealType: 'purchase', ProductType: 'tcb-baas', PackageId: 'baas_personal', CreateAndPay: false, TimeSpan: 1, TimeUnit: 'm', Alias: 'test', EnvId: 'test-12345' }) console.log(res.TranId) // 订单交易ID } test() ``` ## cancelDeal ### 1. 接口描述 接口功能:取消未支付的订单 接口声明:`cancelDeal(params: Object): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ------------ | ---- | ------ | --------------------- | | TranId | 是 | String | 订单交易 ID | | UserClientIp | 是 | String | 用户客户端 IP | | WxAppId | 否 | String | 微信 AppId(可选) | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | ------- | | RequestId | 是 | String | 请求 ID | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { // 先创建订单 const dealRes = await env.createBillingDeal({ DealType: 'purchase', ProductType: 'tcb-baas', PackageId: 'baas_personal', CreateAndPay: false, TimeSpan: 1, TimeUnit: 'm', Alias: 'test', EnvId: 'test-12345' }) // 取消订单 const res = await env.cancelDeal({ TranId: dealRes.TranId, UserClientIp: '127.0.0.1' }) console.log(res) } test() ``` ## createCustomLoginKeys ### 1. 接口描述 接口功能:创建自定义登录密钥 接口声明:`createCustomLoginKeys(): Promise` ### 2. 输入参数 无 ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | ---------- | ---- | ------ | ------- | | RequestId | 是 | String | 请求 ID | | KeyID | 是 | String | 密钥 ID | | PrivateKey | 是 | String | 私钥 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const { env } = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 }) async function test() { const res = await env.createCustomLoginKeys() const { KeyID, PrivateKey } = res console.log(KeyID, PrivateKey) } test() ``` --- ## destroyEnvWithParams ### 1. 接口描述 接口功能:销毁指定环境,支持强制删除和绕过资源检查等选项 接口声明:`app.env.destroyEnvWithParams(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | EnvId | 是 | String | 环境 ID | | IsForce | 否 | Boolean | 是否强制删除(针对预付费隔离中的环境),默认 `false` | | BypassCheck | 否 | Boolean | 是否绕过资源数据检查直接删除,默认 `false` | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | RequestId | String | 请求唯一标识 | ## describeEnvs ### 1. 接口描述 接口功能:查询账号下的云开发环境列表,支持多维度过滤 接口声明:`app.env.describeEnvs(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | EnvId | 否 | String | 按环境 ID 过滤 | | WxAppId | 否 | String | 微信 AppId(微信渠道必传) | | IsVisible | 否 | Boolean | 与 `Channels` 配合,控制可见/不可见渠道的过滤方向 | | Channels | 否 | String[] | 渠道列表,如 `["ide","qc_console"]` | | EnvType | 否 | String | 环境类型:`baas` / `run` / `weda` / `hosting` | | EnvTypes | 否 | String[] | 环境类型列表(优先级高于 `EnvType`) | | Limit | 否 | Number | 分页大小 | | Offset | 否 | Number | 分页偏移量 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | EnvList | Array | 环境列表 | | RequestId | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { const { EnvList } = await app.env.describeEnvs({ EnvType: 'baas' }) EnvList.forEach(e => console.log(e.EnvId, e.Alias)) } test() ``` ## createEnv ### 1. 接口描述 接口功能:创建新的云开发环境 接口声明:`app.env.createEnv(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Alias | 是 | String | 环境别名,小写字母/数字/减号,不能以减号开头或结尾,最长 20 位 | | PackageId | 是 | String | 套餐 ID,可通过 `describeBaasPackageList` 获取 | | Resources | 是 | String[] | 需要发货的资源:`flexdb` / `storage` / `function` | | Period | 否 | Number | 购买时长(月),默认 1 | | AutoVoucher | 否 | Boolean | 是否自动使用代金券 | | RenewFlag | 否 | String | 续费策略:`NOTIFY_AND_AUTO_RENEW` / `NOTIFY_AND_MANUAL_RENEW` | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | EnvId | String | 新创建的环境 ID | | RequestId | String | 请求唯一标识 | ## describeBaasPackageList ### 1. 接口描述 接口功能:查询云开发套餐列表,用于创建/变配/续费前获取可选套餐 接口声明:`app.env.describeBaasPackageList(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | PackageName | 否 | String | 套餐 ID,不填返回全量 | | EnvId | 否 | String | 环境 ID | | Source | 否 | String | 套餐归属方:`miniapp` / `qcloud`,默认 `miniapp` | | TargetAction | 否 | String | 用途:`new`(新购)/ `modify`(变配)/ `renew`(续费) | | GroupName | 否 | String | 套餐分组:`calculation` / `flux` / `capacity` | | InternationalPackage | 否 | Boolean | 是否查询国际版套餐 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | PackageList | BaasPackageInfo[] | 套餐列表 | | RequestId | String | 请求唯一标识 | ## modifyEnvPlan ### 1. 接口描述 接口功能:变更指定环境的套餐(升降配) 接口声明:`app.env.modifyEnvPlan(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | EnvId | 是 | String | 环境 ID | | PackageId | 是 | String | 目标套餐 ID | | AutoVoucher | 否 | Boolean | 是否自动使用代金券 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | RequestId | String | 请求唯一标识 | ## renewEnv ### 1. 接口描述 接口功能:续费指定环境 接口声明:`app.env.renewEnv(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | EnvId | 是 | String | 环境 ID | | Period | 否 | Number | 续费时长(月),默认 1 | | AutoVoucher | 否 | Boolean | 是否自动使用代金券 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | RequestId | String | 请求唯一标识 | ## calculatePackageCreatePrice ### 1. 接口描述 接口功能:计算新购套餐的预估价格 接口声明:`app.env.calculatePackageCreatePrice(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | packageId | 是 | String | 套餐 ID | | region | 是 | String | 地域,如 `ap-shanghai` | | period | 否 | Number | 购买时长(月) | | currency | 否 | String | 币种:`CNY` 或 `USD` | ### 3. 返回结果 返回价格计算结果,包含原价、折后价等字段。 ## calculatePackageRenewPrice ### 1. 接口描述 接口功能:计算续费套餐的预估价格 接口声明:`app.env.calculatePackageRenewPrice(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | envId | 是 | String | 环境 ID | | period | 否 | Number | 续费时长(月) | | currency | 否 | String | 币种:`CNY` 或 `USD` | ### 3. 返回结果 返回价格计算结果,包含原价、折后价等字段。 ## calculatePackageModifyPrice ### 1. 接口描述 接口功能:计算变配套餐的预估价格 接口声明:`app.env.calculatePackageModifyPrice(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | envId | 是 | String | 环境 ID | | packageId | 是 | String | 目标套餐 ID | | currency | 否 | String | 币种:`CNY` 或 `USD` | ### 3. 返回结果 返回价格计算结果,包含原价、折后价等字段。 ## describeBillingInfo ### 1. 接口描述 接口功能:查询指定环境的计费信息 接口声明:`app.env.describeBillingInfo(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | EnvId | 否 | String | 环境 ID,不传查全量 | ### 3. 返回结果 返回环境计费详情(包含套餐、到期时间、计费状态等,具体结构依后端实现)。 ## describeEnvAccountCircle ### 1. 接口描述 接口功能:查询环境的计费周期信息(当前周期及历史周期) 接口声明:`app.env.describeEnvAccountCircle(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | EnvId | 是 | String | 环境 ID | | WxAppId | 否 | String | 微信 AppId | | WithHistoryCircle | 否 | Boolean | 是否同时返回上一个和上上个计费周期,默认 `false` | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | StartTime | String | 当前计费周期开始时间 | | EndTime | String | 当前计费周期结束时间 | | HistoryTime | Array | 历史周期列表(含开始/结束时间) | | RequestId | String | 请求唯一标识 | ## describeCreditsUsageDetail ### 1. 接口描述 接口功能:查询环境资源点的用量明细,支持按模块和日期过滤 接口声明:`app.env.describeCreditsUsageDetail(params): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | EnvId | 是 | String | 环境 ID | | Modules | 是 | String[] | 模块列表,可选:`EKS` / `Database` / `SCF` / `COS` / `AI` / `HOSTING` / `Auth` / `Other` | | StartDate | 是 | String | 开始日期,格式 `YYYY-MM-DD` | | EndDate | 是 | String | 结束日期,格式 `YYYY-MM-DD` | | NeedUsageDetails | 是 | Boolean | 是否返回每日用量明细 | ### 3. 返回结果 返回各模块资源点用量明细(具体结构依后端实现)。 ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { const res = await app.env.describeCreditsUsageDetail({ EnvId: 'your-env-id', Modules: ['SCF', 'Database', 'COS'], StartDate: '2025-01-01', EndDate: '2025-01-31', NeedUsageDetails: true }) console.log(JSON.stringify(res, null, 2)) } test() ``` --- # Manager Node SDK/模块管理/登录配置 > 当前文档链接: https://docs.cloudbase.net/api-reference/manager/node/login-config ## getLoginConfig ### 1. 接口描述 接口功能:查询登录策略配置 接口声明:`app.env.getLoginConfig(): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: 查询指定云开发环境的登录策略配置,包括手机号短信登录、邮箱登录、用户名密码登录和匿名登录方式的开启状态,同时包含短信验证码发送通道、MFA 多因子认证和密码更新策略。 ### 2. 输入参数 无 ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | RequestId | 是 | String | 请求唯一标识 | | EmailLogin | 是 | Boolean | 是否开启邮箱登录方式 | | AnonymousLogin | 是 | Boolean | 是否开启匿名登录方式 | | UserNameLogin | 是 | Boolean | 是否开启用户名密码登录方式 | | PhoneNumberLogin | 是 | Boolean | 是否开启手机号短信登录方式 | | SmsVerificationConfig | 是 | [SmsVerificationConfig](#smsverificationconfig) | 短信验证码发送配置,见下方说明 | | MfaConfig | 否 | [MfaConfig](#mfaconfig) | MFA 多因子认证登录配置 | | PwdUpdateStrategy | 否 | [PwdUpdateStrategy](#pwdupdatestrategy) | 密码修改策略配置 | #### SmsVerificationConfig | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Type | 否 | String | 短信发送通道类型,如 `default` | | Name | 否 | String | 自定义 APIs 数据源名称 | | Method | 否 | String | 调用方法 | | SmsDayLimit | 否 | Number | 每日发送限额,`-1` 表示不限制 | #### MfaConfig | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | On | 否 | String | MFA 因子认证,`TRUE` 或 `FALSE` | | Sms | 否 | String | 短信验证,`TRUE` 或 `FALSE` | | Email | 否 | String | 邮箱验证,`TRUE` 或 `FALSE` | | RequiredBindPhone | 否 | String | 强制绑定手机号,`TRUE` 或 `FALSE` | #### PwdUpdateStrategy | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | FirstLoginUpdate | 否 | Boolean | 首次登录是否强制修改密码 | | PeriodUpdate | 否 | Boolean | 是否开启定期强制修改密码 | | PeriodValue | 否 | Number | 定期修改密码的周期值 | | PeriodType | 否 | String | 周期时间单位,如 `YEAR`、`MONTH`、`WEEK` | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { const res = await app.env.getLoginConfig() console.log('邮箱登录:', res.EmailLogin) console.log('匿名登录:', res.AnonymousLogin) console.log('用户名密码登录:', res.UserNameLogin) console.log('手机号短信登录:', res.PhoneNumberLogin) } test() ``` ## modifyLoginConfig ### 1. 接口描述 接口功能:修改登录策略配置 接口声明:`app.env.modifyLoginConfig(params): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: 修改指定云开发环境的登录策略配置。支持开启或关闭手机号短信登录、邮箱登录、用户名密码登录和匿名登录,同时可配置短信验证码发送通道、MFA 多因子认证和密码更新策略。修改后立即生效,影响该环境下所有终端用户的登录行为。 ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | PhoneNumberLogin | 是 | Boolean | 手机号短信登录开关 | | EmailLogin | 是 | Boolean | 邮箱登录开关 | | UserNameLogin | 是 | Boolean | 用户名密码登录开关 | | AnonymousLogin | 是 | Boolean | 匿名登录开关 | | SmsVerificationConfig | 否 | Object | 短信验证码发送配置,不传则不修改当前配置,结构见 [SmsVerificationConfig](#smsverificationconfig) | | MfaConfig | 否 | Object | MFA 多因子认证登录配置,不传则不修改当前配置,结构见 [MfaConfig](#mfaconfig) | | PwdUpdateStrategy | 否 | Object | 密码更新策略配置,不传则不修改当前配置,结构见 [PwdUpdateStrategy](#pwdupdatestrategy) | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | RequestId | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { // 先查询当前配置 const config = await app.env.getLoginConfig() // 开启匿名登录 const res = await app.env.modifyLoginConfig({ PhoneNumberLogin: config.PhoneNumberLogin, EmailLogin: config.EmailLogin, UserNameLogin: config.UserNameLogin, AnonymousLogin: true }) console.log(res.RequestId) } test() ``` ## describeClient ### 1. 接口描述 接口功能:查询应用客户端详情 接口声明:`app.env.describeClient(id): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: 获取指定云开发环境下某个客户端的配置信息,包括 OAuth 凭证、Token 有效期、会话控制策略等。当客户端 ID 等于环境 ID 时,返回该环境的默认客户端配置。 ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Id | 是 | String | 客户端唯一标识符(Client ID),一般使用环境 ID | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | RequestId | 是 | String | 请求唯一标识 | | Id | 是 | String | 客户端的唯一标识符(Client ID) | | CreatedAt | 否 | String | 客户端的创建时间,ISO 8601 格式 | | UpdatedAt | 否 | String | 客户端信息的最后修改时间,ISO 8601 格式 | | RefreshTokenExpiresIn | 否 | Number | Refresh Token 的有效期,单位为秒 | | AccessTokenExpiresIn | 否 | Number | Access Token 的有效期,单位为秒 | | MaxDevice | 否 | Number | 单个用户允许同时登录的最大会话数量,`-1` 表示不限制 | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { const res = await app.env.describeClient('your-env-id') console.log('Client ID:', res.Id) console.log('Refresh Token 有效期:', res.RefreshTokenExpiresIn, '秒') console.log('Access Token 有效期:', res.AccessTokenExpiresIn, '秒') console.log('最大会话数:', res.MaxDevice) } test() ``` ## modifyClient ### 1. 接口描述 接口功能:修改应用客户端配置 接口声明:`app.env.modifyClient(params): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: 采用增量更新策略,仅更新请求中传入的非空字段,未传入的字段保持原值不变。支持修改 Token 有效期、会话控制策略等配置。当客户端 ID 等于环境 ID 且客户端尚未创建时,将自动创建默认客户端配置。 ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Id | 是 | String | 客户端唯一标识符(Client ID) | | RefreshTokenExpiresIn | 否 | Number | Refresh Token 的有效期,单位为秒,取值范围 `1800~2592000`,默认 `2592000` | | AccessTokenExpiresIn | 否 | Number | Access Token 的有效期,单位为秒,最小 `1800`,默认 `7200`,应小于 RefreshTokenExpiresIn | | MaxDevice | 否 | Number | 单个用户允许同时登录的最大会话数量,`-1` 不限制,`0` 按 User-Agent 区分,`1~50` 为精确限制 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | RequestId | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { const res = await app.env.modifyClient({ Id: 'your-env-id', RefreshTokenExpiresIn: 2592000, AccessTokenExpiresIn: 7200, MaxDevice: 5 }) console.log(res.RequestId) } test() ``` ## getProviders ### 1. 接口描述 接口功能:获取三方认证源列表 接口声明:`app.env.getProviders(): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: 查询指定云开发环境下的身份认证源列表,包括第三方登录(OAuth、OIDC、SAML)、微信小程序登录、自定义登录和邮箱登录等。若自定义登录或邮箱登录的身份源尚未创建,接口会自动追加一个默认关闭状态的记录。 ### 2. 输入参数 无 ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | RequestId | 是 | String | 请求唯一标识 | | Total | 否 | Number | 认证源总数 | | Data | 否 | `Array` | 三方认证源列表 | #### Provider | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Id | 是 | String | 身份源唯一标识,2~32 位小写字母和数字 | | Config | 是 | Object | 身份源安全认证配置,见 [ProviderConfig](#providerconfig) | | Name | 否 | [LocalizedMessage](#localizedmessage) | 身份源名称,支持国际化多语言配置 | | Picture | 否 | String | 身份源图标 URL | | Homepage | 否 | String | 身份源官方主页地址 | | ProviderType | 否 | String | 身份源协议类型:`OAUTH`、`OIDC`、`SAML`、`CUSTOM`、`EMAIL` 等 | | On | 否 | String | 身份源启用状态,`TRUE` 或 `FALSE` | | AutoSignUpWithProviderUser | 否 | String | 是否自动注册系统用户,`TRUE`、`FALSE`、`UNSPECIFIED` | | TransparentMode | 否 | String | 是否开启信息透传模式,`TRUE` 或 `FALSE` | | ReuseUserId | 否 | String | 是否复用第三方用户 ID,`TRUE` 或 `FALSE` | | EmailConfig | 否 | [EmailConfig](#emailconfig) | 邮箱身份源专项配置,仅 ProviderType 为 `EMAIL` 时有效 | | Description | 否 | [LocalizedMessage](#localizedmessage) | 身份源描述,支持国际化多语言配置 | | AutoSignInWhenEmailMatch | 否 | String | 是否开启邮箱自动关联登录,`TRUE`、`FALSE`、`UNSPECIFIED` | | AutoSignInWhenPhoneNumberMatch | 否 | String | 是否开启手机号码自动关联登录,`TRUE`、`FALSE`、`UNSPECIFIED` | #### ProviderConfig | 字段 | 类型 | 必选 | 说明 | | --- | --- | --- | --- | | Issuer | String | OIDC 时建议填写 | 身份提供方的唯一标识符(Issuer URL),用于验证 ID Token 中的 `iss` 字段。仅当 ProviderType 为 `OIDC` 时需要填写,值通常为第三方 OIDC 服务的根地址,如 `https://accounts.google.com`。填写后平台将自动通过 `/.well-known/openid-configuration` 发现并填充 AuthorizationEndpoint、TokenEndpoint、UserinfoEndpoint、JwksUri 等端点地址 | | JwksUri | String | OIDC 时需要 | JSON Web Key Set 地址,用于获取公钥以验证 ID Token 签名。仅当 ProviderType 为 `OIDC` 时需要填写。若已填写 Issuer,该字段将通过 Discovery 自动获取,无需手动填写 | | ClientId | String | OIDC/OAUTH 时必填 | 在第三方身份提供方注册的应用客户端 ID。当 ProviderType 为 `OIDC` 或 `OAUTH` 时必须填写,可在对应平台的开发者控制台中获取 | | ClientSecret | String | OIDC/OAUTH 时必填 | 在第三方身份提供方注册的应用客户端密钥,与 ClientId 配合使用,用于在 Token 端点进行身份验证。当 ProviderType 为 `OIDC` 或 `OAUTH` 时必须填写,请妥善保管,避免泄露 | | RedirectUri | String | OIDC/OAUTH 时必填 | OAuth 授权完成后第三方平台回调的地址,需与在第三方平台注册的回调地址完全一致。当 ProviderType 为 `OIDC` 或 `OAUTH` 时必须填写。示例:`https://envId.ap-shanghai.tcb-api.tencentcloudapi.com/auth/v1/callback` | | Scope | String | OIDC/OAUTH 时必填 | 向第三方申请的权限范围,多个 scope 空格分隔。当 ProviderType 为 `OIDC` 或 `OAUTH` 时必须填写,OIDC 场景下通常至少包含 `openid`,如需获取邮箱或手机号可追加 `email`、`phone` 等。若已填写 Issuer 且未指定 Scope,将自动使用 Discovery 返回的 `scopes_supported`。示例:`openid email name` | | AuthorizationEndpoint | String | OIDC/OAUTH 时必填 | 授权端点地址,用于引导用户跳转至第三方登录页面。当 ProviderType 为 `OIDC` 或 `OAUTH` 时必须填写。若已填写 Issuer,该字段将通过 Discovery 自动获取 | | TokenEndpoint | String | OIDC/OAUTH 时必填 | Token 端点地址,用于通过授权码(code)换取 Access Token 和 ID Token。当 ProviderType 为 `OIDC` 或 `OAUTH` 时必须填写。若已填写 Issuer,该字段将通过 Discovery 自动获取 | | UserinfoEndpoint | String | OIDC/OAUTH 时按需 | 用户信息端点地址,用于通过 Access Token 获取用户基本信息(昵称、头像、邮箱等)。当 ProviderType 为 `OIDC` 或 `OAUTH` 且需要获取用户详细信息时填写。若已填写 Issuer,该字段将通过 Discovery 自动获取 | | ResponseType | String | 否 | 授权请求的响应类型。可选值:`code`(授权码模式,推荐)、`token`(隐式模式)、`id_token`(直接返回 ID Token)。ProviderType 为 `OIDC` 时默认 `id_token`,其他类型默认 `code` | | SignoutEndpoint | String | 否 | 单点退出端点地址。配置后,用户退出时将跳转至该地址使第三方 IDP 登录态失效。不填则退出时仅清除本平台登录态 | | TokenEndpointAuthMethod | String | 否 | Token 端点的客户端身份验证方式。可选值:`CLIENT_SECRET_POST`(凭证放在请求 Body 中)、`CLIENT_SECRET_BASIC`(凭证通过 HTTP Basic Auth Header 传递),默认 `CLIENT_SECRET_POST` | | SamlMetadata | String | SAML 时必填 | SAML 身份提供方的 Metadata XML 内容,包含实体 ID、SSO 端点地址、签名证书等,最大 10KB。仅当 ProviderType 为 `SAML` 时填写,通常可从第三方 IDP 管理控制台下载 | | RequestParametersMap | [RequestParametersMap](#requestparametersmap) | 否 | 请求参数映射配置,用于处理非标准 OAuth 协议的参数转换。默认严格遵循 OAuth 2.0 标准,对接微信、企业微信等非标准平台时需配置 | | ResponseParametersMap | [ResponseParametersMap](#responseparametersmap) | 否 | 响应参数映射配置,用于处理非标准 OAuth 协议的响应参数转换。对接微信等非标准平台时,可将第三方返回的字段映射为平台标准字段 | :::info 必填条件汇总 - **OIDC 类型**:建议填写 `Issuer`(自动发现端点),或手动填写 `ClientId`、`ClientSecret`、`RedirectUri`、`Scope`、`AuthorizationEndpoint`、`TokenEndpoint`、`JwksUri` - **OAUTH 类型**:必填 `ClientId`、`ClientSecret`、`RedirectUri`、`Scope`、`AuthorizationEndpoint`、`TokenEndpoint` - **SAML 类型**:必填 `SamlMetadata` - **其他类型**(CUSTOM、EMAIL 等):所有字段均为可选 ::: #### RequestParametersMap 请求参数映射,用于非标准 OAuth 协议的参数转换。默认可为空,对接国内平台(如微信、企业微信)时需配置。 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | ClientId | 否 | String | OAuth 标准 `client_id` 的映射字段名,如微信对应 `appid` | | ClientSecret | 否 | String | OAuth 标准 `client_secret` 的映射字段名 | | RedirectUri | 否 | String | OAuth 标准 `redirect_uri` 的映射字段名 | | GrantType | 否 | String | OAuth 授权模式匹配的参数字段名 | | ClientCredentials | 否 | String | OAuth 授权模式类型,如 `authorization_code`、`client_credentials` | | AccessToken | 否 | String | OAuth 返回中 `access_token` 的映射字段名 | | ExpiresIn | 否 | String | OAuth 返回中 Token 有效期的映射字段名 | | AuthPosition | 否 | String | OAuth 获取 Token 时认证信息的请求位置:`URL`、`Headers`、`Body` | | RegisterUserRoleId | 否 | String | 身份源注册用户时自动绑定的角色 ID | | RegisterUserAutoLicense | 否 | String | 注册用户时是否自动授予许可证,`TRUE` 或 `FALSE`,默认 `FALSE` | | RegisterUserType | 否 | String | 注册用户时的用户类型:`externalUser`(外部用户)或 `internalUser`(内部用户),默认 `externalUser` | #### ResponseParametersMap 响应参数映射,用于非标准 OAuth 协议的响应参数转换。默认可为空,对接国内平台(如微信、企业微信)时需配置。 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Sub | 否 | String | 用户唯一标识(`sub`)的映射字段名,对应 OIDC 标准 `sub` | | Name | 否 | String | 用户名称的映射字段名,对应 OIDC 标准 `name` | | Picture | 否 | String | 用户头像的映射字段名,需公网可访问 URL | | Username | 否 | String | 用户登录名的映射字段名,对应 OIDC 标准 `preferred_username` | | Email | 否 | String | 用户邮箱的映射字段名,对应 OIDC 标准 `email` | | PhoneNumber | 否 | String | 用户手机号的映射字段名,对应 OIDC 标准 `phone_number` | | Groups | 否 | String | 用户角色/分组的映射字段名,对应 OIDC 标准 `groups`,支持字符串数组 | #### LocalizedMessage 国际化多语言文本结构,用于身份认证源的 `Name` 和 `Description` 等字段。可以为每种语言配置不同的展示文本。 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Message | 是 | String | 默认展示的文本 | | Localized | 否 | `Array` | 针对每种语言的展示文字列表 | #### MessageLocalized | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Message | 是 | String | 该语言的展示文本 | | Locale | 是 | String | 语言标识 | **示例**: ```javascript { Message: '微信登录', Localized: [ { Message: 'WeChat Login', Locale: 'en' }, { Message: '위챗 로그인', Locale: 'ko' } ] } ``` ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { const res = await app.env.getProviders() console.log('认证源总数:', res.Total) res.Data?.forEach(provider => { console.log(`${provider.Id} (${provider.ProviderType}): ${provider.On}`) }) } test() ``` ## addProvider ### 1. 接口描述 接口功能:添加身份认证源 接口声明:`app.env.addProvider(params): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: 在指定云开发环境下创建新的身份认证源,支持 OAuth 2.0、OIDC、SAML 2.0 等标准协议,以及自定义登录和邮箱登录等多种认证方式。若身份源 ID 已存在将返回错误。每个环境最多允许添加 20 个认证源。 ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Name | 是 | [LocalizedMessage](#localizedmessage) | 身份源显示名称,支持国际化多语言配置 | | ProviderType | 是 | String | 身份源协议类型:`OAUTH`、`OIDC`、`SAML`、`WX_MICRO_APP`、`WX_QRCODE_MICRO_APP`、`WX_CLOUDBASE_MICRO_APP`、`WX_MP`、`WX_OPEN`、`WX_WORK_INTERNAL`、`WX_WORK_AGENT`、`WX_WORK_THIRD_PARTY`、`WX_WORK_THIRD_PARTY_ASSOCIATION`、`CUSTOM`、`EMAIL` | | Id | 否 | String | 身份源唯一标识符,2~32 位小写字母和数字,不填则系统自动生成 | | Picture | 否 | String | 身份源图标 URL,建议 64×64 SVG 格式 | | Homepage | 否 | String | 身份源官方主页地址 | | Config | 否 | Object | 身份认证源协议连接配置,结构见 [ProviderConfig](#providerconfig) | | TransparentMode | 否 | String | 是否开启透传模式,`TRUE`、`FALSE`、`UNSPECIFIED`,默认 `FALSE` | | On | 否 | String | 身份源启用状态,`TRUE`、`FALSE`、`UNSPECIFIED`,默认 `TRUE` | | Description | 否 | [LocalizedMessage](#localizedmessage) | 身份源描述,支持国际化多语言配置 | | ReuseUserId | 否 | String | 是否复用第三方用户 ID 作为平台用户 ID,`TRUE`、`FALSE`、`UNSPECIFIED` | | AutoSignInWhenEmailMatch | 否 | String | 是否开启邮箱自动关联登录,默认 `FALSE` | | AutoSignInWhenPhoneNumberMatch | 否 | String | 是否开启手机号自动关联登录,默认 `TRUE` | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | RequestId | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { // 添加一个 OAuth 身份认证源 const res = await app.env.addProvider({ Name: { Message: 'My OAUTH Provider' }, ProviderType: 'OAUTH', Id: 'myoauth', Config: { ClientId: 'your-client-id', ClientSecret: 'your-client-secret', AuthorizationEndpoint: 'https://www.example.com/auth', TokenEndpoint: 'https://www.example.com/token', UserinfoEndpoint: 'https://www.example.com/userinfo', Scope: 'openid profile', ResponseType: 'code', TokenEndpointAuthMethod: 'CLIENT_SECRET_POST' }, On: 'TRUE' }) console.log(res.RequestId) } test() ``` ## modifyProvider ### 1. 接口描述 接口功能:修改身份认证源 接口声明:`app.env.modifyProvider(params): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: 更新已有身份认证源的配置信息,支持修改基本信息、协议连接配置、登录行为控制及启用状态。采用增量更新策略,仅更新传入的字段,未传入的字段保持原值。OIDC 类型修改 Issuer 后将自动通过 Discovery 重新获取端点配置。若 CUSTOM 或 EMAIL 身份源尚不存在,调用时将自动创建。 ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Id | 是 | String | 身份源唯一标识符,2~32 位小写字母和数字 | | Name | 否 | [LocalizedMessage](#localizedmessage) | 身份源显示名称,支持国际化多语言配置 | | Picture | 否 | String | 身份源图标 URL | | Homepage | 否 | String | 身份源官方主页地址 | | ProviderType | 否 | String | 身份源协议类型 | | Config | 否 | Object | 身份认证源协议连接配置,结构见 [ProviderConfig](#providerconfig) | | TransparentMode | 否 | String | 是否开启透传模式,开启后 ReuseUserId 强制 `TRUE`,AutoSignUpWithProviderUser 强制 `FALSE` | | On | 否 | String | 身份源启用状态,`TRUE`、`FALSE`、`UNSPECIFIED`,默认 `TRUE` | | Description | 否 | [LocalizedMessage](#localizedmessage) | 身份源描述,支持国际化多语言配置 | | ReuseUserId | 否 | String | 是否复用第三方用户 ID,`TRUE`、`FALSE`、`UNSPECIFIED` | | AutoSignUpWithProviderUser | 否 | String | 是否自动注册系统用户,`TRUE`、`FALSE`、`UNSPECIFIED` | | EmailConfig | 否 | [EmailConfig](#emailconfig) | 邮箱身份源专项配置,仅 ProviderType 为 `EMAIL` 时有效 | | AutoSignInWhenEmailMatch | 否 | String | 是否开启邮箱自动关联登录,默认 `FALSE` | | AutoSignInWhenPhoneNumberMatch | 否 | String | 是否开启手机号自动关联登录,默认 `TRUE` | #### EmailConfig 邮箱身份源专项配置,用于配置邮件发送方式和自定义模板。仅当 ProviderType 为 `EMAIL` 时有效。被 [modifyProvider](#modifyprovider) 引用。 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | On | 否 | String | 是否采用平台默认代发,`TRUE` 或 `FALSE` | | SmtpConfig | 否 | [SmtpConfig](#smtpconfig) | SMTP 配置 | | TemplateConfig | 否 | [EmailTemplateConfig](#emailtemplateconfig) | 邮件模板配置 | #### SmtpConfig 自有 SMTP 邮件服务器连接配置。 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | SenderAddress | 是 | String | 发件人邮箱地址,作为邮件的 `From` 头显示 | | ServerHost | 是 | String | SMTP 服务器域名或 IP 地址,如 `smtp.example.com` | | ServerPort | 是 | Number | SMTP 服务器端口,常用值:`465`(SSL)、`587`(STARTTLS)、`25` | | AccountUsername | 是 | String | SMTP 登录账号,通常为发件人邮箱地址或 SMTP 用户名 | | AccountPassword | 是 | String | SMTP 登录密码或授权码,请妥善保管,避免泄露 | | SecurityMode | 否 | String | 加密模式:`AUTO`(自动选择)、`SSL`、`STARTSSL`、`NO_SSL`,默认 `AUTO` | #### EmailTemplateConfig 邮件模板配置。 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | RegisterSignIn | 否 | [LocalizedTemplate](#localizedtemplate) | 注册/登录模板 | | DefaultTpl | 否 | [LocalizedTemplate](#localizedtemplate) | 默认模板 | **入参限制**:模板中必须包含 `{{.VerificationCode}}` 变量用于展示验证码,可选变量包括 `{{.Usage}}`(用途说明)、`{{.ExpireMinutes}}`(过期时间)、`{{.Email}}`(收件人邮箱)。模板中禁止包含 `script`、`javascript`、`onclick`、`onload`、`iframe`、`link` 标签及 CSS `expression`、CSS `url()` 等。 #### LocalizedTemplate 多语言邮件模板,按语言代码分别指定 HTML 模板内容。 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | ZhCN | 否 | String | 中文模板,HTML 格式。必须包含 `{{.VerificationCode}}` 变量,可选变量有 `{{.Usage}}`、`{{.ExpireMinutes}}`、`{{.Email}}`。禁止包含 `script`、`javascript`、`onclick`、`onload`、`iframe`、`link` 标签及 CSS `expression`、CSS `url()` 等 | | EnUS | 否 | String | 英文模板,HTML 格式。变量和限制同 ZhCN | **示例**: ```javascript { RegisterSignIn: { ZhCN: `

验证码

您的验证码是: {{.VerificationCode}}

5分钟内有效

`, EnUS: `

Verification Code

Your Verification Code Is: {{.VerificationCode}}

expire in 5 min

` } } ``` ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | RequestId | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { // 启用微信开放平台登录 const res = await app.env.modifyProvider({ Id: 'wx_open', On: 'TRUE', Config: { ClientId: 'your-wechat-appid', ClientSecret: 'your-wechat-appsecret' } }) console.log(res.RequestId) } test() ``` ## deleteProvider ### 1. 接口描述 接口功能:删除第三方认证源 接口声明:`app.env.deleteProvider(id): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | Id | 是 | String | 认证源 ID,2~32 位小写英文字母或数字,如 `github` | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | RequestId | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { const res = await app.env.deleteProvider('myoidc') console.log(res.RequestId) } test() ``` ## createApiKey ### 1. 接口描述 接口功能:创建 API Key 接口声明:`app.env.createApiKey(params): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: 在指定云开发环境下创建 API Key 访问凭证。支持两种类型: - `api_key`:服务端管理员访问凭证,可设置有效期,单个环境最多 5 个 - `publish_key`:前端匿名访问凭证,固定有效期,每个环境仅保留一个 创建成功后返回 API Key 明文 Token,该值仅在创建时返回一次,请妥善保存。 ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | KeyType | 是 | String | 密钥类型:`api_key` 或 `publish_key` | | KeyName | 否 | String | 密钥自定义名称,建议填写描述性名称,如 `server-prod` | | ExpireIn | 否 | Number | 密钥有效期,单位秒,最短 `7200` 秒。`0` 或不设置表示永不过期 | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | RequestId | 是 | String | 请求唯一标识 | | KeyId | 是 | String | API Key 唯一标识符 | | Name | 是 | String | API Key 名称 | | ApiKey | 否 | String | API Key 令牌值(JWT 格式),仅在创建时返回完整明文 | | ExpireAt | 否 | String | 过期时间,ISO 8601 格式 | | CreateAt | 否 | String | 创建时间,ISO 8601 格式 | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { const res = await app.env.createApiKey({ KeyType: 'api_key', KeyName: 'server-prod', ExpireIn: 86400 }) console.log('KeyId:', res.KeyId) console.log('ApiKey:', res.ApiKey) // 请妥善保存,仅创建时返回 } test() ``` ## describeApiKeyList ### 1. 接口描述 接口功能:查询 API Key 列表 接口声明:`app.env.describeApiKeyList(params?): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: 分页查询指定云开发环境下的 API Key 访问凭证列表,支持按类型过滤。未指定类型时,默认仅返回 api_key 类型的记录。`api_key` 类型的令牌值脱敏处理,`publish_key` 类型返回完整明文。 ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | KeyType | 否 | String | 密钥类型过滤:`api_key` 或 `publish_key`,默认 `api_key` | | PageNumber | 否 | Number | 分页页码,从 1 开始,默认 1 | | PageSize | 否 | Number | 每页条数 | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | RequestId | 是 | String | 请求唯一标识 | | Data | 否 | `Array` | API Key 列表 | | Total | 否 | Number | 总数 | #### ApiKeyToken | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | KeyId | 否 | String | API Key 的唯一标识符 | | Name | 否 | String | API Key 的名称 | | ApiKey | 否 | String | API Key 的令牌值,`api_key` 类型列表查询时脱敏处理 | | ExpireAt | 否 | String | 过期时间,ISO 8601 格式 | | CreateAt | 否 | String | 创建时间,ISO 8601 格式 | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { const res = await app.env.describeApiKeyList({ KeyType: 'api_key', PageNumber: 1, PageSize: 10 }) console.log('总数:', res.Total) res.Data?.forEach(key => { console.log(`${key.Name} (${key.KeyId}): 过期时间 ${key.ExpireAt}`) }) } test() ``` ## deleteApiKey ### 1. 接口描述 接口功能:删除 API Key 接口声明:`app.env.deleteApiKey(keyId): Promise` :::tip 版本提示 自 v5.1.0 起支持此接口 ::: 删除指定云开发环境下的某个 API Key 服务端访问凭证。删除后该 Key 对应的 Token 将被吊销,已使用该 Key 发起的请求将失败。该操作具有幂等性,若指定的 API Key 不存在则直接返回成功。 ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | KeyId | 是 | String | 密钥唯一标识符,可通过查询密钥列表接口获取 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --- | --- | --- | | RequestId | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'your-env-id' }) async function test() { // 先查询 API Key 列表 const listRes = await app.env.describeApiKeyList({ KeyType: 'api_key' }) if (listRes.Data?.length) { const res = await app.env.deleteApiKey(listRes.Data[0].KeyId) console.log(res.RequestId) } } test() ``` --- # Manager Node SDK/模块管理/用户管理 > 当前文档链接: https://docs.cloudbase.net/api-reference/manager/node/user ## createUser ### 1. 接口描述 接口功能:创建用户,支持用户名密码登录 接口声明:`app.user.createUser(options): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ------------ | ---- | ------ | --------------------------------------------------------- | | name | 是 | String | 用户名,1~64 位,字母/数字开头,仅允许字母、数字、`.`、`_`、`-` | | uid | 否 | String | 自定义用户 UID,最长 64 位 | | type | 否 | String | 用户类型:`internalUser`(内部)或 `externalUser`(外部) | | password | 否 | String | 密码,8~32 位,需包含大写、小写、数字、特殊字符中的至少 3 类 | | userStatus | 否 | String | 初始状态:`ACTIVE`(激活)或 `BLOCKED`(封禁),默认 `ACTIVE` | | nickName | 否 | String | 昵称,2~64 位 | | phone | 否 | String | 手机号(11位中国大陆号码) | | email | 否 | String | 邮箱 | | avatarUrl | 否 | String | 头像 URL | | description | 否 | String | 描述,最长 200 字符 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --------- | ------ | ------------ | | RequestId | String | 请求唯一标识 | | Data.Uid | String | 创建的用户 UID | ### 4. 示例代码 ```javascript const app = CloudBase.init({ secretId, secretKey, envId }) async function main() { const res = await app.user.createUser({ name: 'alice', password: 'Abc@12345', type: 'internalUser', nickName: 'Alice', email: 'alice@example.com' }) console.log('新用户 UID:', res.Data.Uid) } ``` --- ## describeUserList ### 1. 接口描述 接口功能:分页查询用户列表,支持按用户名、昵称、手机号、邮箱、UID 列表过滤 接口声明:`app.user.describeUserList(options?): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | -------- | ---- | ------ | ------------------------- | | pageNo | 否 | Number | 页码,从 1 开始,默认 1 | | pageSize | 否 | Number | 每页数量,1~100,默认 20 | | name | 否 | String | 按用户名过滤 | | nickName | 否 | String | 按昵称过滤 | | phone | 否 | String | 按手机号过滤 | | email | 否 | String | 按邮箱过滤 | | uidList | 否 | String[] | 按用户 UID 列表过滤,精确匹配 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | ----------------- | ------ | ------------ | | RequestId | String | 请求唯一标识 | | Data.Total | Number | 用户总数 | | Data.UserList | Array | 用户列表,见下方 TcbUserItem | **TcbUserItem** | 字段 | 类型 | 说明 | | ----------- | ------ | -------------------------------- | | Uid | String | 用户 UID | | Name | String | 用户名 | | Type | String | 用户类型 | | UserStatus | String | 用户状态:`ACTIVE` 或 `BLOCKED` | | NickName | String | 昵称 | | Phone | String | 手机号 | | Email | String | 邮箱 | | AvatarUrl | String | 头像 URL | | Description | String | 描述 | ### 4. 示例代码 ```javascript async function main() { const res = await app.user.describeUserList({ pageNo: 1, pageSize: 20 }) console.log(`共 ${res.Data.Total} 个用户`) res.Data.UserList.forEach(u => console.log(u.Uid, u.Name, u.UserStatus)) } // 按 UID 列表查询 const res2 = await app.user.describeUserList({ uidList: ['uid-001', 'uid-002'] }) console.log(`共 ${res2.Data.Total} 个用户`) ``` --- ## modifyUser ### 1. 接口描述 接口功能:修改用户信息,支持修改密码、用户状态等 接口声明:`app.user.modifyUser(options): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ----------- | ---- | ------ | ------------------------------------------------------- | | uid | 是 | String | 用户 UID | | name | 否 | String | 新用户名(传空字符串不修改) | | type | 否 | String | 新用户类型:`internalUser` 或 `externalUser` | | password | 否 | String | 新密码(传空字符串不修改) | | userStatus | 否 | String | 新状态:`ACTIVE` 或 `BLOCKED` | | nickName | 否 | String | 新昵称(传空字符串表示清空) | | phone | 否 | String | 新手机号(传空字符串表示清空) | | email | 否 | String | 新邮箱(传空字符串表示清空) | | avatarUrl | 否 | String | 新头像 URL(传空字符串表示清空) | | description | 否 | String | 新描述(传空字符串表示清空) | :::info 字段更新规则 - `name`、`password`:传入非空字符串才更新,传空字符串或不传均忽略 - `nickName`、`phone`、`email`、`avatarUrl`、`description`:只要传入该字段(包括空字符串),就下发给后端;传空字符串可清空该字段 ::: ### 3. 返回结果 | 字段 | 类型 | 说明 | | ------------ | ------- | ------------ | | RequestId | String | 请求唯一标识 | | Data.Success | Boolean | 是否修改成功 | ### 4. 示例代码 ```javascript async function main() { // 封禁某用户 await app.user.modifyUser({ uid: 'user-uid-xxx', userStatus: 'BLOCKED' }) // 修改密码 await app.user.modifyUser({ uid: 'user-uid-xxx', password: 'NewPass@2025' }) } ``` --- ## deleteUsers ### 1. 接口描述 接口功能:批量删除用户(不可恢复) 接口声明:`app.user.deleteUsers(options): Promise` :::tip 版本提示 自 v5.0.0 起支持此接口 ::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ---- | ---- | ---------- | ------------------------------- | | uids | 是 | String[] | 要删除的用户 UID 列表,最多 100 个 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | ------------------- | ------ | ---------------- | | RequestId | String | 请求唯一标识 | | Data.SuccessCount | Number | 删除成功数量 | | Data.FailedCount | Number | 删除失败数量 | ### 4. 示例代码 ```javascript async function main() { const res = await app.user.deleteUsers({ uids: ['uid-a', 'uid-b', 'uid-c'] }) console.log(`成功删除 ${res.Data.SuccessCount} 个用户`) } ``` --- :::danger 废弃 以下接口已废弃,建议迁移至上方新版接口。 ::: ## 获取用户列表(废弃) ### 1. 接口描述 接口功能:获取指定云环境下的用户列表 接口声明:`getEndUserList(options: Object): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ------ | ---- | ------ | ------------ | | limit | 是 | Number | 拉取用户数量 | | offset | 是 | Number | 偏移量 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --------- | ------------------------ | ------------ | | Total | Number | 用户总数 | | RequestId | String | 请求唯一标识 | | Users | `Array` | 用户信息列表 | **EndUserInfo** | 字段 | 类型 | 说明 | | ----------- | ------- | -------------- | | UUId | String | 用户唯一 ID | | WXOpenId | String | 微信 ID | | QQOpenId | String | qq ID | | Phone | String | 手机号 | | Email | String | 邮箱 | | NickName | String | 昵称 | | Gender | String | 性别 | | AvatarUrl | String | 头像地址 | | UpdateTime | String | 更新时间 | | CreateTime | String | 创建时间 | | IsAnonymous | Boolean | 是否为匿名用户 | | IsDisabled | Boolean | 是否禁用账户 | | HasPassword | Boolean | 是否设置过密码 | | UserName | String | 用户名 | ### 4. 示例代码 ```javascript const cloudbaseConfig = { secretId: "Your SecretId", secretKey: "Your SecretKey", envId: "Your envId" // 云开发环境ID,可在腾讯云云开发控制台获取 }; const app = new CloudBase(cloudBaseConfig); async function main() { const { Users } = await app.user.getEndUserList({ limit: 20, offset: 0 }); console.log(">>> Users are:", Users); } main(); ``` ## 创建新用户(废弃) ### 1. 接口描述 接口功能:在指定云环境下,创建用户名和密码 接口声明:`createEndUser(options: Object): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | -------- | ---- | ------ | ------ | | username | 是 | String | 用户名 | | password | 是 | String | 密码 | :::caution 密码强度要求 密码长度不小于 8 位,不大于 32 位,需要包含字母和数字。 ::: ### 3. 返回结果 | 字段 | 类型 | 说明 | | --------- | ----------- | ------------ | | RequestId | String | 请求唯一标识 | | User | EndUserInfo | 用户信息 | ### 4. 示例代码 ```javascript const cloudbaseConfig = { secretId: "Your SecretId", secretKey: "Your SecretKey", envId: "Your envId" // 云开发环境ID,可在腾讯云云开发控制台获取 }; const app = new CloudBase(cloudBaseConfig); async function main() { try { const { User } = await app.user.createEndUser({ username: "your username", password: "your password" }); console.log(">>> 新建用户信息:", User); } catch (error) { console.log(">>> 新建用户失败:", error.message); } } main(); ``` ## 修改用户账户信息(废弃) ### 1. 接口描述 接口功能:修改指定云环境下的特定用户的信息 接口声明:`modifyEndUser(options: Object): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | -------- | ---- | ------ | ------------------ | | uuid | 是 | String | 云开发用户唯一标识 | | username | 否 | String | 新用户名 | | password | 否 | String | 新密码 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --------- | ------ | ------------ | | RequestId | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const cloudbaseConfig = { secretId: "Your SecretId", secretKey: "Your SecretKey", envId: "Your envId" // 云开发环境ID,可在腾讯云云开发控制台获取 }; const app = new CloudBase(cloudBaseConfig); async function main() { try { await app.user.modifyEndUser({ uuid: "your user uuid", username: "your new username", password: "your new password" }); console.log(">>> 修改用户账户信息成功"); } catch (error) { console.log(">>> 修改用户账户信息失败:", error.message); } } main(); ``` ## 修改用户信息(废弃) ### 1. 接口描述 接口功能:修改指定云环境下的特定用户的信息 接口声明:`updateEndUser(options: Object): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | ------------------ | | uuid | 是 | String | 云开发用户唯一标识 | | nickName | 是 | String | 新昵称 | | gender | 是 | String | 新性别, `MALE | FEMALE` | | avatarUrl | 是 | String | 新头像 | | country | 是 | String | 新国家 | | province | 是 | String | 新省份 | | city | 是 | String | 新城市 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --------- | ------ | ------------ | | RequestId | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const cloudbaseConfig = { secretId: "Your SecretId", secretKey: "Your SecretKey", envId: "Your envId" // 云开发环境ID,可在腾讯云云开发控制台获取 }; const app = new CloudBase(cloudBaseConfig); async function main() { try { await app.user.updateEndUser({ uuid: "your user uuid", nickName: "your new nickName", gender: "your new gender", avatarUrl: "your new avatarUrl", country: "your new country", province: "your new province", city: "your new city" }); console.log(">>> 修改用户信息成功"); } catch (error) { console.log(">>> 修改用户信息失败:", error.message); } } main(); ``` ## 设置用户状态(废弃) ### 1. 接口描述 接口功能:停用或启用云环境下的特定用户 接口声明:`setEndUserStatus(options: object): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ------ | ---- | ------ | --------------------- | | uuid | 是 | String | 云开发用户唯一标识 | | status | 是 | String | 'DISABLE'或者'ENABLE' | ### 3. 返回结果 | 字段 | 类型 | 说明 | | --------- | ------ | ------------ | | RequestId | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const cloudbaseConfig = { secretId: "Your SecretId", secretKey: "Your SecretKey", envId: "Your envId" // 云开发环境ID,可在腾讯云云开发控制台获取 }; const app = new CloudBase(cloudBaseConfig); async function main() { try { const { RequestId } = await app.user.setEndUserStatus({ uuid: "User uuid", status: "DISABLE" }); console.log(">>> 停用成功"); } catch (error) { console.log(">>> 停用失败", error.message); } } main(); ``` ## 批量删除用户(废弃) ### 1. 接口描述 接口功能:批量删除指定云环境下的用户 接口声明:`deleteEndUsers(options: Object): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | -------- | ---- | ------------------- | -------------- | | userList | 是 | `Array` | 用户 uuid 列表 | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | ------------ | | RequestId | 是 | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const cloudbaseConfig = { secretId: "Your SecretId", secretKey: "Your SecretKey", envId: "Your envId" // 云开发环境ID,可在腾讯云云开发控制台获取 }; const app = new CloudBase(cloudBaseConfig); async function main() { try { const { RequestId } = await app.user.deleteEndUsers({ userList: [ "uuid a", "uuid b", "uuid c" // ...... ] }); console.log(">>> 批量删除成功"); } catch (error) { console.log(">>> 批量删除失败", error.message); } } main(); ``` --- --- # Manager Node SDK/模块管理/权限管理 > 当前文档链接: https://docs.cloudbase.net/api-reference/manager/node/permission ::::tip 版本提示 资源权限管理与角色管理自 v5.0.0 起支持,策略管理自 v5.5.5 起支持。 :::: `@cloudbase/manager-node` 提供 `PermissionService`,通过 `app.permission` 访问,支持三大功能: 1. **策略管理**:查询网关鉴权策略列表、获取和设置 OPA Rego 鉴权策略(v5.5.5+) 2. **角色管理**:创建/查询/修改/删除用户角色(RBAC),并为角色绑定网关访问策略 3. **资源权限管理**:设置和查询云函数、数据库、存储等资源的访问权限(已废弃,请使用策略管理) ::::tip OPA 鉴权策略概念 策略管理基于 OPA Rego 策略引擎。关于 OPA 鉴权策略的工作机制、`input` 上下文结构、`allow` / `deny` 决策矩阵及实战案例,请参考 [OPA 鉴权策略介绍](../../../../envconfig/authz-opa/intro);从旧版网关鉴权迁移到 OPA Rego 的步骤与策略翻译对照表,请参考 [迁移指南](../../../../envconfig/authz-opa/migration)。 :::: --- ## describeResourcePolicyList ### 1. 接口描述 接口功能:查询网关鉴权策略列表 接口声明:`app.permission.describeResourcePolicyList(options?): Promise` ::::tip 版本提示 自 v5.5.5 起新增此接口。 :::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ------------ | ---- | ------ | ------------------------------ | | resourceType | 否 | String | 按资源类型过滤,可选值:`policy` | ### 3. 返回结果 | 字段 | 类型 | 说明 | | ---------------- | ---------------------- | ------------ | | RequestId | String | 请求唯一标识 | | Data.PolicyList | `ResourcePolicyItem[]` | 策略列表 | | Data.Total | String | 策略总数 | **ResourcePolicyItem** | 字段 | 类型 | 说明 | | ------------- | ------- | ----------------- | | PolicyId | String | 策略 ID | | ResourceType | String | 资源类型 | | ResourceId | String | 资源 ID | | ResourceTitle | String | 资源标题 | | ResourceName | String | 资源名称 | | Effect | String | 效果:`allow`/`deny` | | IsAccess | Boolean | 是否可访问 | | RoleId | String | 角色 ID | | RoleIdentity | String | 角色标识 | | RoleName | String | 角色名称 | | SafeRule | String | 安全规则 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' }) async function test() { // 查询全部策略 const result = await app.permission.describeResourcePolicyList() console.log('策略总数:', result.Data.Total) // 按资源类型过滤 const filtered = await app.permission.describeResourcePolicyList({ resourceType: 'policy' }) console.log('策略列表:', filtered.Data.PolicyList) } test() ``` --- ## describeEnvAuthzConfig ### 1. 接口描述 接口功能:获取鉴权策略内容 接口声明:`app.permission.describeEnvAuthzConfig(options): Promise` ::::tip 版本提示 自 v5.5.5 起新增此接口。 :::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ----- | ---- | ------ | -------------------------------------------------------------------------------------- | | key | 是 | String | 策略键名。`authz.user.rego` 获取用户策略,`authz.platform.extension.rego` 获取平台扩展策略 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | ------------ | ------ | ------------ | | RequestId | String | 请求唯一标识 | | Item.Key | String | 策略键名 | | Item.Value | String | 策略内容 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' }) async function test() { // 获取用户策略 const userPolicy = await app.permission.describeEnvAuthzConfig({ key: 'authz.user.rego' }) console.log('用户策略:', userPolicy.Item.Value) // 获取平台扩展策略 const extPolicy = await app.permission.describeEnvAuthzConfig({ key: 'authz.platform.extension.rego' }) console.log('平台扩展策略:', extPolicy.Item.Value) } test() ``` --- ## modifyEnvAuthzConfig ### 1. 接口描述 接口功能:设置鉴权策略 接口声明:`app.permission.modifyEnvAuthzConfig(options): Promise` ::::tip 版本提示 自 v5.5.5 起新增此接口。 :::: ::::caution 重要提示 设置 Rego 策略后,旧网关鉴权将直接失效,请确认策略内容正确后再执行。 :::: ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ----- | ---- | ------ | ----------------------------------------------------------- | | key | 是 | String | 策略键名,固定为 `authz.user.rego` | | value | 是 | String | Rego 策略内容,需以 `package authz.user` 开头 | 关于 `input` 字段的详细说明,请参考 [OPA 鉴权策略介绍](../../../../envconfig/authz-opa/intro)。 ### 3. 返回结果 | 字段 | 类型 | 说明 | | ------------ | ------ | ---------------- | | RequestId | String | 请求唯一标识 | | AffectedRows | Number | 影响行数 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' }) async function test() { const regoContent = `package authz.user default allow := false # 放通匿名用户访问云函数 allow if { input.cloudbase.resource_type == "functions" input.subject.auth_type == "anonymous" }` const result = await app.permission.modifyEnvAuthzConfig({ key: 'authz.user.rego', value: regoContent }) console.log('影响行数:', result.AffectedRows) } test() ``` --- 角色管理适用于企业内部多人协作、网关 API 访问控制等场景,支持为角色绑定腾讯云网关策略。 ## createRole ### 1. 接口描述 接口功能:创建用户角色 接口声明:`app.permission.createRole(options): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ------------- | ---- | ---------------------- | -------------------------------------------------- | | roleName | 是 | String | 角色名称 | | roleIdentity | 是 | String | 角色唯一标识符(英文,创建后不可修改) | | description | 否 | String | 角色描述 | | memberUids | 否 | `string[]` | 初始成员 UID 列表 | | policies | 否 | `PermissionPolicyItem[]` | 绑定的权限策略列表 | **PermissionPolicyItem** 当前 `ResourceType` 仅支持 `gateway`。 | 字段 | 必填 | 类型 | 说明 | | ----------------------- | ---- | ------ | ----------------------------------------------------------------- | | ResourceType | 是 | String | 资源类型,当前仅支持 `gateway` | | Resource | 是 | String | 资源标识符。传预设策略时填预设策略字符串;传自定义策略时与 `GatewayPolicyCode` 保持一致 | | Effect | 是 | String | 效果:`allow`(允许)或 `deny`(拒绝) | | GatewayPolicyCode | 是 | String | 网关策略编码。传预设策略时填预设策略字符串;传自定义策略时与 `Resource` 保持一致 | | GatewayPolicyName | 否 | String | 网关策略名称 | | GatewayPolicyDescription| 否 | String | 网关策略描述 | | GatewayPolicyExpression | 否 | String | 网关策略表达式 JSON 字符串 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | ----------------- | ------------------------ | -------------------- | | RequestId | String | 请求唯一标识 | | Data.RoleId | String | 创建的角色 ID | | Data.MemberUids | `string[]` | 添加的成员 UID 列表 | | Data.Policies | `PermissionPolicyItem[]` | 绑定的策略列表 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' }) async function test() { const result = await app.permission.createRole({ roleName: '只读运营', roleIdentity: 'read-only-operator', description: '运营人员只读角色', policies: [ { ResourceType: 'gateway', Resource: 'FunctionsAccess', Effect: 'allow', GatewayPolicyCode: 'FunctionsAccess' } ] }) console.log('创建角色 ID:', result.RoleId) } test() ``` --- ## describeRoleList ### 1. 接口描述 接口功能:查询角色列表,支持按角色 ID、标识、名称筛选 接口声明:`app.permission.describeRoleList(options?): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ------------- | ---- | ------- | ------------------------------------------------- | | pageNumber | 否 | Number | 页码,默认 1 | | pageSize | 否 | Number | 每页条数,默认 20 | | roleId | 否 | String | 按角色 ID 精确查询 | | roleIdentity | 否 | String | 按角色唯一标识查询 | | roleName | 否 | String | 按角色名称模糊查询 | | loadDetails | 否 | Boolean | 是否返回成员和策略详情,默认 false | ### 3. 返回结果 | 字段 | 类型 | 说明 | | ----------------------- | ------------ | -------------------- | | RequestId | String | 请求唯一标识 | | Data.TotalCount | Number | 系统角色总数 | | Data.CustomTotalCount | Number | 自定义角色总数 | | Data.SystemRoles | `RoleItem[]` | 系统预置角色列表 | | Data.CustomRoles | `RoleItem[]` | 自定义角色列表 | **RoleItem** | 字段 | 类型 | 说明 | | ------------ | ---------- | ------------------------------ | | RoleId | String | 角色 ID | | RoleIdentity | String | 角色唯一标识 | | RoleName | String | 角色名称 | | RoleType | String | 类型:`system` / `custom` | | Description | String | 角色描述 | | Members | `MemberInfo[]` | 成员列表(loadDetails=true 时返回) | | Policies | `PermissionPolicyItem[]` | 策略列表(loadDetails=true 时返回) | --- ## modifyRole ### 1. 接口描述 接口功能:修改角色信息,支持更新名称、描述、成员、策略 接口声明:`app.permission.modifyRole(options): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ---------------- | ---- | ---------- | ---------------------------------- | | roleId | 是 | String | 要修改的角色 ID | | roleName | 否 | String | 新的角色名称 | | description | 否 | String | 新的角色描述 | | addMemberUids | 否 | `string[]` | 新增成员 UID 列表 | | removeMemberUids | 否 | `string[]` | 移除成员 UID 列表 | | addPolicies | 否 | `PermissionPolicyItem[]` | 新增策略列表 | | removePolicies | 否 | `PermissionPolicyItem[]` | 移除策略列表 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | ------------------------- | ------------------------ | ---------------- | | RequestId | String | 请求唯一标识 | | Data.Success | Boolean | 是否修改成功 | | Data.AddedMemberUids | `string[]` | 实际添加的成员 | | Data.RemovedMemberUids | `string[]` | 实际移除的成员 | | Data.AddedPolicies | `PermissionPolicyItem[]` | 实际添加的策略 | | Data.RemovedPolicies | `PermissionPolicyItem[]` | 实际移除的策略 | --- ## deleteRoles ### 1. 接口描述 接口功能:批量删除角色 接口声明:`app.permission.deleteRoles(options): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ------- | ---- | ---------- | ---------------- | | roleIds | 是 | `string[]` | 要删除的角色 ID 列表 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | ------------------ | ------ | -------------- | | RequestId | String | 请求唯一标识 | | Data.SuccessCount | Number | 删除成功数量 | | Data.FailedCount | Number | 删除失败数量 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' }) async function test() { // 查询所有自定义角色 const { CustomRoles } = await app.permission.describeRoleList({ loadDetails: true }) console.log('自定义角色数量:', CustomRoles?.length) // 修改角色:添加新成员 await app.permission.modifyRole({ roleId: 'role-123', addMemberUids: ['uid-456'], roleName: '运营管理员(更新)' }) // 删除角色 const res = await app.permission.deleteRoles({ roleIds: ['role-123', 'role-456'] }) console.log(`删除成功 ${res.SuccessCount} 个角色`) } test() ``` --- ## modifyResourcePermission ::::caution 已废弃 此接口已废弃,网关鉴权策略已由 OPA 接管,请使用 [`modifyEnvAuthzConfig`](#modifyenvauthzconfig) 替代。 :::: ### 1. 接口描述 接口功能:设置指定资源的访问权限 接口声明:`app.permission.modifyResourcePermission(options): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | -------------- | ---- | ---------------------- | ------------------------------------------------------ | | resourceType | 是 | `PermissionResourceType` | 资源类型,取值:`function` / `storage` / `table` / `collection` | | resource | 否 | String | 资源名称(如函数名、集合名)。当 `resourceType` 为 `function` 时可不传 | | permission | 是 | `BasePermission` | 权限类型(见下表) | | securityRule | 否 | String | 自定义安全规则 JSON 字符串,仅 `permission = 'CUSTOM'` 时传入 | **权限类型(BasePermission)** **table / collection** | 值 | 说明 | | ------------ | -------------------------------- | | `READONLY` | 读取全部数据,修改本人数据 | | `PRIVATE` | 读取和修改本人数据 | | `ADMINWRITE` | 读取全部数据,不可修改数据 | | `ADMINONLY` | 无权限 | | `CUSTOM` | 自定义安全规则(仅 `collection` 支持) | **storage** | 值 | 说明 | | ------------ | -------------------------------- | | `READONLY` | 所有用户可读,仅创建者和管理员可写 | | `PRIVATE` | 仅创建者及管理员可读写 | | `ADMINWRITE` | 所有用户可读,仅管理员可写 | | `ADMINONLY` | 仅管理员可读写 | | `CUSTOM` | 自定义安全规则 | **function** | 值 | 说明 | | -------- | -------------- | | `CUSTOM` | 自定义安全规则 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | ------------ | ------- | ---------------- | | RequestId | String | 请求唯一标识 | | Data.Success | Boolean | 是否设置成功 | ### 4. 示例代码 ```javascript import CloudBase from '@cloudbase/manager-node' const app = CloudBase.init({ secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' }) async function test() { // 设置数据库集合为仅管理员可读写 await app.permission.modifyResourcePermission({ resourceType: 'collection', resource: 'my-collection', permission: 'ADMINONLY' }) // 设置自定义安全规则 await app.permission.modifyResourcePermission({ resourceType: 'collection', resource: 'user-data', permission: 'CUSTOM', securityRule: JSON.stringify({ read: 'auth.uid == doc.uid', write: 'auth.uid == doc.uid' }) }) } test() ``` --- ## describeResourcePermission ::::caution 已废弃 此接口已废弃,网关鉴权策略已由 OPA 接管,请使用 [`describeResourcePolicyList`](#describeresourcepolicylist) 或 [`describeEnvAuthzConfig`](#describeenvauthzconfig) 替代。 :::: ### 1. 接口描述 接口功能:查询指定资源的当前访问权限 接口声明:`app.permission.describeResourcePermission(options): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | -------------- | ---- | ------------------------ | ---------------------------------------------- | | resourceType | 是 | `PermissionResourceType` | 资源类型:`function` / `storage` / `table` / `collection` | | resources | 否 | `string[]` | 资源名称列表,云函数不传或传空数组、云存储传存储桶名、数据库表传表名,不能超过100条。 | ### 3. 返回结果 | 字段 | 类型 | 说明 | | ----------------------- | ---------------------- | ------------------ | | RequestId | String | 请求唯一标识 | | Data.TotalCount | Number | 总条数 | | Data.PermissionList | `ResourcePermission[]` | 权限列表 | **ResourcePermission** | 字段 | 类型 | 说明 | | ------------ | ------ | -------------------------- | | ResourceType | String | 资源类型 | | Resource | String | 资源名称 | | Permission | String | 当前权限值 | | SecurityRule | String | 自定义安全规则(CUSTOM 时有值) | --- # Manager Node SDK/模块管理/文档型数据库 > 当前文档链接: https://docs.cloudbase.net/api-reference/manager/node/database ## createCollection ### 1. 接口描述 接口功能:该接口可创建集合 接口声明: `createCollection(collectionName: string): Promise` `createCollectionIfNotExists(collectionName: string): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | -------------- | ---- | ------ | ------ | | CollectionName | 是 | String | 集合名 | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------ | ------------ | | RequestId | 是 | String | 请求唯一标识 | ### 4. 示例代码 ```javascript const cloudbaseConfig = { secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 } let { database } = new CloudBase(cloudbaseConfig) async function test() { let result = await database.createCollection('collectionName') console.log(result) } test() ``` ## checkCollectionExists ### 1. 接口描述 接口功能:检查集合是否存在 接口声明:`checkCollectionExists(collectionName: string): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | ---- | ---- | ------ | ------ | | - | 是 | String | 集合名 | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------- | ---------------- | | RequestId | 是 | String | 请求唯一标识 | | Msg | 否 | String | 错误信息 | | Exists | 是 | Boolean | 集合是否已经存在 | ### 4. 示例代码 ```javascript const cloudbaseConfig = { secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 } let { database } = new CloudBase(cloudbaseConfig) async function test() { let result = await database.checkCollectionExists('collectionAlreadyExists') if (result.Exists) { // 集合存在 } else { // 集合不存在 } } test() ``` ## deleteCollection ### 1. 接口描述 接口功能:删除集合 接口声明:`deleteCollection(collectionName: string): Promise` ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | -------------- | ---- | ------ | ------ | | CollectionName | 是 | String | 集合名 | ### 3. 返回结果 | 字段 | 必填 | 类型 | 说明 | | --------- | ---- | ------- | ------------------------------------ | | RequestId | 是 | String | 请求唯一标识 | | Exists | 否 | Boolean | 存在不返回该字段,不存在则返回 false | ### 4. 示例代码 ```javascript const cloudbaseConfig = { secretId: 'Your SecretId', secretKey: 'Your SecretKey', envId: 'Your envId' // 云开发环境ID,可在腾讯云云开发控制台获取 } let { database } = new CloudBase(cloudbaseConfig) async function test() { let result = await database.deleteCollection('collectionAlreadyExists') if (result.Exists === false) { // 集合不存在 } } test() ``` ## updateCollection ### 1. 接口描述 接口功能:更新集合 接口声明:`updateCollection(collectionName: string, options: array): Promise` 该接口可更新集合,但目前支持更新索引。 > ⚠️ 目前该接口只能更新索引,包括创建和删除。 - 索引创建时如果已经存在,则会先删除再创建索引。 - 因为一次接口调用可同时创建多个索引,所以可能部分索引创建失败,部分创建成功,接口报异常。 ### 2. 输入参数 | 字段 | 必填 | 类型 | 说明 | | -------------- | ---- | -------------------- | -------- | | collectionName | 是 | String | 集合名 | | options | 是 | `Array
` | 按表过滤,可选 `buckets` / `objects`;不传则同时返回两表的策略 | | `--json` | 输出原始 `PGExecuteResult` | > RLS 策略变更通过 `tcb db execute --role postgres` 执行 `CREATE POLICY` / `DROP POLICY`。 --- ### `tcb storage objects upload` 上传本地文件到 Storage bucket。 ```bash tcb storage objects upload -b [options] ``` | 参数 | 说明 | | --- | --- | | `` | 本地文件绝对/相对路径,必须是文件 | | `` | 远端对象名(不能以 `/` 开头) | | `-b, --bucket ` | Bucket ID,可用环境变量 `TCB_BUCKET_ID` 替代 | | `--content-type ` | 显式 MIME,不传则按 `扩展名 → magic bytes` 推断 | | `--cache-control ` | 整数自动转 `max-age=`,字符串原样作为 `Cache-Control` | | `--metadata ` | 自定义 metadata JSON,内部 Base64 编码到 `X-Metadata` | | `--robots-tag ` | `X-Robots-Tag` 头 | | `--upsert` | POST 模式下允许覆盖已存在对象 | | `--use-put` | 使用 PUT 方法(始终覆盖) | | `--json` | 输出 SDK 原始返回(`{ Id, Key }`) | 示例: ```bash tcb storage objects upload ./hello.txt test/hello.txt -b my-bucket tcb storage objects upload ./hello.txt test/hello.txt -b my-bucket --content-type text/plain --upsert ``` --- ### `tcb storage objects download` 从 Storage bucket 下载对象到本地。 ```bash tcb storage objects download [localPath] -b [options] ``` | 参数 | 说明 | | --- | --- | | `` | 对象名 | | `[localPath]` | 本地路径;省略时输出到 stdout;指向已存在目录会自动拼上 `key` 的 basename | | `-b, --bucket ` | Bucket ID | | `-m, --method ` | `GET`(默认)/ `HEAD` | | `--download ` | 触发浏览器下载(`?download=`) | | `--if-none-match ` | `If-None-Match` 头(仅认证下载路径生效) | | `--if-modified-since ` | `If-Modified-Since` 头 | | `--range ` | `Range` 头(断点续传) | | `--overwrite` | 当 `localPath` 已存在时允许覆盖 | | `--json` | 仅返回元信息(`{ status, headers }`),不落盘 | 行为说明: - **已登录** → 走认证下载路径(`downloadAuthenticatedObject`),可访问 private/public bucket; - **未登录** → 回退到 `downloadPublicObject`,仅能访问 public bucket; - `--method HEAD` 或 `--json` 模式下不会写本地文件。 --- ### `tcb storage objects stat` 查询对象元信息。 ```bash tcb storage objects stat -b [options] ``` | 参数 | 说明 | | --- | --- | | `-m, --method ` | `GET`(默认)/ `HEAD`,HEAD 仅认证路径支持 | | `--json` | 输出 `{ status, headers, body }`,`body` 即 `IObjectInfoHttpPayload` | 表格输出按以下字段顺序展示:`id` / `name` / `bucket_id` / `version` / `size` / `content_type` / `cache_control` / `etag` / `last_modified` / `created_at` / `metadata`。 --- ### `tcb storage objects list` 列出 bucket 中的对象,支持前缀过滤、分页与排序。 ```bash tcb storage objects list -b [options] ``` | 参数 | 说明 | | --- | --- | | `--prefix ` | 路径前缀过滤 | | `--limit ` | 分页大小,1~1000,默认 20,超出范围会以 `INVALID_PARAM`(exit 3)拒绝 | | `--cursor ` | 翻页游标(上一次响应的 `nextCursor`) | | `--with-delimiter` | 使用 `/` 分隔文件夹 | | `--sort-by ` | 排序列,可选 `name` / `created_at` / `updated_at` | | `--sort-order ` | 排序方向,可选 `asc`(默认)/ `desc` | | `--json` | 输出 SDK 原始返回(含 `folders` / `objects` / `hasNext` / `nextCursor`) | > `list` 接口需要登录态,未登录时会以 `AUTH_FAILED` 失败。 --- ### `tcb storage objects rm` 删除对象,单次最多 20 个。 ```bash tcb storage objects rm -b [-y] [--json] ``` | 参数 | 说明 | | --- | --- | | `` | 1~20 个对象名;超过 20 个会以 `INVALID_PARAM`(exit 3)拒绝 | | `-y, --yes` | 跳过删除前的确认提示 | | `--json` | 单 key 走 `deleteObject`,输出 `{ message }`;多 key 走 `deleteObjects`,输出 `IDeletedObjectInfo[]` | 表格输出列:`Key` / `Status`,`Status` 为 `DELETED` / `NOT_FOUND`(批量删除中不存在的对象会被服务端静默跳过)。 --- ### `tcb storage objects cp` 复制对象到新位置。 ```bash tcb storage objects cp -b [options] ``` | 参数 | 说明 | | --- | --- | | `-b, --bucket ` | 源 Bucket ID | | `--dst-bucket ` | 目标 Bucket ID,默认与源 Bucket 相同 | | `--copy-metadata ` | 是否复制源对象 metadata,默认 `true`;`false` 时 `--metadata` / `--user-metadata` 才生效 | | `--metadata ` | 系统 metadata JSON,与源 metadata 合并;COS 仅识别 `cacheControl` / `mimetype` / `contentType` | | `--user-metadata ` | 用户自定义 metadata JSON,**整体替换** 目标 user_metadata | | `--upsert` | 允许覆盖目标位置已有对象 | | `--json` | 输出 SDK 原始返回(`ICopyObjectHttpResult`) | 校验细节: - 同 bucket 下源 key 与目标 key 相同且未传 `--upsert` → `INVALID_PARAM`; - `--metadata` / `--user-metadata` 仅在 `--copy-metadata false` 时生效,其他情况下传入会被 CLI 直接拒绝。 --- ### `tcb storage objects mv` 移动(rename)对象到新位置;服务端一次性完成「复制 + 删除源对象」。 ```bash tcb storage objects mv -b [--dst-bucket ] [--json] ``` > 同 bucket 下源 key 与目标 key 完全相同会被本地拦截为 `INVALID_PARAM`。 --- ### `tcb storage objects sign` 为对象生成签名 URL,**默认下载签名**,加 `--upload` 切换为上传签名。 ```bash # 下载签名 tcb storage objects sign -b [--expires-in ] [--json] # 上传签名 tcb storage objects sign --upload -b [--upsert] [--json] ``` | 参数 | 说明 | | --- | --- | | `` | 下载模式 1~500 个;上传模式仅允许 1 个 | | `--upload` | 切换到上传签名模式 | | `--expires-in ` | 过期时间(秒),>= 1,默认 600;**仅下载模式** | | `--upsert` | 允许覆盖(`x-upsert: true`),**仅上传模式** | | `--json` | 下载模式输出 `[{ path, signedURL, error }]`;上传模式输出 `{ url, token }` | 互斥校验: - `--upload` 与 `--expires-in` 互斥; - 下载模式下传 `--upsert` 会被拒绝; - `--upload` 一次只能签 1 个 key。 --- ## 退出码 | 退出码 | 含义 | | --- | --- | | `3 INVALID_INPUT` | 包含 `INVALID_PARAM` / `MISSING_PARAM` / `FLAT_CMD_NOT_AVAILABLE_IN_PG` / `ENV_NOT_PG` 等 | 详见 [exit-codes](./exit-codes.md)。 --- # CloudBase CLI/静态网站托管 > 当前文档链接: https://docs.cloudbase.net/cli-v1/hosting 云开发为开发者提供静态网页托管能力,支持 HTML、CSS、JavaScript、字体等静态资源的分发。底层基于腾讯云对象存储 COS 和全球 CDN 网络,为您的网站提供高性能、高可用的访问体验。 :::tip 适用场景:文件级操作 `tcb hosting` 是**文件维度**的操作工具,适合以下场景: - 手动上传 / 同步静态文件(HTML、CSS、JS、图片、字体等) - 纯静态内容,无构建流程(如文档站产物、设计稿导出页面) - 需要精细控制云端文件路径 **如果你的项目有构建步骤**(React / Vue / Next.js / Vite / Angular / Nuxt 等前端框架),推荐使用 **[应用部署(tcb app deploy)](./app/management.md)**,它会自动完成安装依赖 → 构建 → 上传产物 → 绑定路由的完整流程。 ::: ## 前置条件 在使用 CLI 操作静态网站服务前,请确保: 1. 拥有腾讯云账号并完成实名认证 2. 前往[云开发平台](https://tcb.cloud.tencent.com/dev),创建云开发环境 ## 部署网站 ### 全量部署 使用 `tcb hosting deploy` 命令可以将当前目录下的所有文件部署到静态网站。 ```bash # 进入构建目录 cd docs # 部署当前目录下的所有文件 tcb hosting deploy -e envId ``` ### 指定文件部署 您可以指定特定的文件或文件夹进行部署: ```bash # 基本语法 tcb hosting deploy [cloudPath] -e envId ``` **参数说明:** - `localPath`:本地文件或文件夹路径 - `cloudPath`:云端目标路径(可选,默认为根目录) - `envId`:环境 ID **示例:** ```bash # 将 hosting 目录下的所有文件部署到根目录 tcb hosting deploy hosting -e envId # 将本地 index.html 部署到云端根目录 tcb hosting deploy ./index.html -e envId # 将 static 目录下的 index.js 部署到云端 static/index.js tcb hosting deploy ./static/index.js static/index.js -e envId ``` ### 部署限制 - **文件大小**:单个文件最大支持 50TB - **文件数量**:无限制 - **网络优化**:上传默认开启 2 次自动重试,瞬时 `socket hang up` 会自动恢复。弱网或大文件场景可通过以下方式进一步优化: **方式一:调整上传参数(推荐)** | 参数 | 说明 | 默认值 | |------|------|--------| | `--concurrency ` | 上传并发数,大文件或弱网建议调小(如 5) | 20 | | `--retry-count ` | 上传失败重试次数 | 2 | | `--retry-interval ` | 重试时间间隔(毫秒) | 1000 | ```bash # 大文件 / 弱网:降低并发 + 增加重试次数 tcb hosting deploy ./dist --concurrency 5 --retry-count 3 ``` **方式二:关闭 SDK 长连接** ```bash export COS_SDK_KEEPALIVE=false tcb hosting deploy -e envId ``` ### SPA 应用配置 :::caution Vue History 模式 使用 Vue Router 的 history 模式时,需要在 [静态网站控制台](https://tcb.cloud.tencent.com/dev#/static-hosting?tab=config) 的设置页面配置错误页面为应用的入口页面(通常是 `index.html`)。 ::: ## 管理网站 ### 查看服务信息 查看静态网站的状态、访问域名等详细信息: ```bash tcb hosting detail -e envId ``` ### 查看文件列表 列出静态网站存储空间中的所有文件: ```bash tcb hosting list -e envId ``` ### 删除文件 删除静态网站中的指定文件或文件夹: ```bash # 删除指定文件或文件夹 tcb hosting delete -e envId # 删除所有文件(cloudPath 为空) tcb hosting delete -e envId ``` **命令参数:** | 参数 | 说明 | 必填 | | ---------------------- | ------------------------------------------ | -------------------------- | | `cloudPath` | 云端文件或文件夹路径 | 否(不指定则删除所有文件) | | `-e, --env-id ` | 环境 ID | 是 | | `--dir` | 删除目标是文件夹(递归删除) | 否 | | `--force` | 强制删除,跳过确认提示 | 否 | | `--dry-run` | 模拟运行,仅预览将要删除的文件,不实际执行 | 否 | **示例:** ```bash # 删除根目录下的 index.html tcb hosting delete index.html -e envId # 删除 static 文件夹及其所有内容 tcb hosting delete static --dir -e envId # 预览将要删除的文件(不实际执行) tcb hosting delete static --dir --dry-run -e envId # 清空整个静态网站 tcb hosting delete -e envId # 强制删除,跳过确认 tcb hosting delete static --dir --force -e envId ``` ## 路径说明 ### 路径格式 - **localPath**:本地文件或文件夹路径 - 格式:`目录/文件名` - 示例:`./index.js`、`static/css/index.css` - **cloudPath**:云端文件或文件夹的相对路径 - 格式:`目录/文件名`(相对于根目录) - 示例:`index.js`、`static/css/index.js` ### 跨平台注意事项 :::caution Windows 系统 - **localPath**:使用系统路径格式,通常使用 `\` 分隔符 - **cloudPath**:统一使用 `/` 分隔符,与操作系统无关 ::: ## 常见问题 ### 上传失败处理 如果遇到网络连接问题导致上传失败,可以尝试以下方式: **方式一:调整上传参数(推荐)** ```bash # 大文件 / 弱网:降低并发并增加重试次数 tcb hosting deploy ./dist --concurrency 5 --retry-count 3 # 调整重试间隔(毫秒) tcb hosting deploy ./dist --retry-count 5 --retry-interval 2000 ``` **方式二:关闭 SDK 长连接** ```bash export COS_SDK_KEEPALIVE=false tcb hosting deploy -e envId ``` ### 域名访问 部署完成后,您可以通过以下方式访问网站: - 在控制台查看分配的默认域名 - 配置自定义域名(需要 ICP 备案) --- # CloudBase CLI/HTTP 访问/安全域名管理 > 当前文档链接: https://docs.cloudbase.net/cli-v1/cors :::info v3.0.0+ `tcb cors` 命令自 v3.0.0 起提供,替代原 `tcb service` 的安全域名功能。 ::: 安全域名用于限制哪些域名可以通过 Web SDK 调用云开发资源(如数据库、存储、云函数等)。只有在安全域名白名单中的域名,才能在浏览器端发起请求。 :::tip cors vs domains 的区别 - **`tcb cors`**:管理 Web SDK 安全域名白名单,控制哪些网页域名可以访问云开发资源(CORS 鉴权)。 - **`tcb domains`**:管理 HTTP 网关的自定义域名,需绑定 SSL 证书,与路由规则联动。详见 [自定义域名管理](./domains.md)。 ::: ## 查看安全域名列表 列出当前环境的所有安全域名: ```bash tcb cors list -e ``` **示例输出**: ``` 域名 Id 域名 创建时间 状态 1001 example.com 2025-01-01 10:00:00 启用中 1002 test.example.com 2025-01-02 10:00:00 启用中 ``` ## 添加安全域名 向当前环境添加一个或多个安全域名,多个域名用逗号分隔: ```bash # 添加单个域名 tcb cors add example.com -e # 同时添加多个域名 tcb cors add example.com,test.example.com -e ``` :::caution 注意 - 域名不能重复添加,重复添加会报错并提示先删除再重试 - 域名格式应为合法域名,不含 `http://` 或 `https://` 前缀 ::: ## 删除安全域名 从当前环境删除一个或多个安全域名,多个域名用逗号分隔: ```bash # 删除单个域名 tcb cors rm example.com -e # 同时删除多个域名 tcb cors rm example.com,test.example.com -e ``` :::caution 注意 删除的域名必须已存在于安全域名列表中,否则会报错。 ::: ## 命令速查 | 命令 | 说明 | |------|------| | `tcb cors list` | 列出安全域名列表 | | `tcb cors add ` | 添加安全域名,多个以逗号分隔 | | `tcb cors rm ` | 删除安全域名,多个以逗号分隔 | --- # CloudBase CLI/HTTP 访问/自定义域名管理 > 当前文档链接: https://docs.cloudbase.net/cli-v1/domains :::info v3.0.0+ `tcb domains` 命令自 v3.0.0 起提供,替代原 `tcb service domain` 的自定义域名功能。 ::: `tcb domains` 用于管理 HTTP 网关的自定义域名。绑定自定义域名后,您可以通过自己的域名访问云开发 HTTP 服务,并通过路由规则将流量转发到云函数、云托管、静态托管等上游服务。 :::tip cors vs domains 的区别 - **`tcb cors`**:管理 Web SDK 安全域名白名单(CORS 鉴权),控制哪些网页域名可以访问云开发资源。详见 [安全域名管理](./cors.md)。 - **`tcb domains`**:管理 HTTP 网关的自定义域名,需绑定 SSL 证书,与 `tcb routes` 路由规则联动。 ::: ## 前置条件 绑定自定义域名前,请确保: 1. 域名已完成 **ICP 备案** 2. 已在腾讯云 [SSL 证书控制台](https://console.cloud.tencent.com/ssl) 申请并上传有效的 SSL 证书,获取证书 ID 3. 绑定成功后,需将域名的 DNS 解析 CNAME 记录指向云开发环境域名 ## 查看自定义域名列表 查看当前环境已绑定的自定义域名: ```bash tcb domains ls -e ``` 支持分页和过滤: ```bash # 分页查询 tcb domains ls -e --limit 50 --offset 0 # 按域名过滤 tcb domains ls -e --filter "Domain=api.example.com" # 按接入方式过滤 tcb domains ls -e --filter "AccessType=CDN" # 多条件组合(且关系,&连接) tcb domains ls -e --filter "DomainType=HTTPSERVICE&AccessType=DIRECT" ``` **`--filter` 可过滤字段**: | 字段 | 说明 | 可选值 | |------|------|--------| | `Domain` | 域名 | 任意域名字符串 | | `DomainType` | 域名类型 | `HTTPSERVICE`(默认)、`CBR`、`ANYSERVICE`、`AI_AGENT`、`VM`、`INTEGRATION_CALLBACK` | | `AccessType` | 接入方式 | `DIRECT`、`CDN`、`CUSTOM` | :::tip 默认只展示用户手动绑定的 `HTTPSERVICE` 类型域名。如需查看其他类型域名,使用 `--filter "DomainType=CBR"` 等。 ::: ## 绑定自定义域名 将自定义域名绑定到 HTTP 网关: ```bash # 基本用法(直连接入,默认) tcb domains add api.example.com --certid -e # CDN 接入 tcb domains add api.example.com --certid --access-type CDN -e # 自定义接入(需指定 CNAME 源站) tcb domains add api.example.com --certid --access-type CUSTOM --custom-cname origin.example.com -e # 绑定后禁用(默认启用) tcb domains add api.example.com --certid --disable -e ``` **命令参数**: | 参数 | 说明 | 必填 | |------|------|------| | `` | 要绑定的域名 | 是 | | `--certid ` | SSL 证书 ID,在腾讯云 SSL 证书控制台获取 | 是 | | `--access-type ` | 接入方式:`DIRECT`(直连,默认)、`CDN`(接入云开发 CDN)、`CUSTOM`(自定义) | 否 | | `--custom-cname ` | 自定义 CNAME,仅当 `--access-type CUSTOM` 时可用 | 否 | | `--disable` | 绑定后禁用域名(默认启用) | 否 | :::caution 注意 - 同一域名不能重复绑定 - 绑定成功后,需配置 DNS CNAME 解析才能正常访问 ::: ## 解绑自定义域名 解绑已绑定的自定义域名: ```bash tcb domains rm api.example.com -e ``` :::caution 注意 **如果域名下仍有路由绑定,解绑操作会失败**。需先使用 `tcb routes delete` 删除该域名下的所有路由,再执行解绑操作。 ```bash # 查看域名下的路由 tcb routes list -e --filter "Domain=api.example.com" # 删除路由后再解绑域名 tcb routes delete api.example.com -e -p /api/* tcb domains rm api.example.com -e ``` ::: ## 典型使用流程 ```bash # 1. 绑定自定义域名(需先有 SSL 证书) tcb domains add api.example.com --certid abc123 -e # 2. 为域名添加路由规则(将流量转发到云托管服务) tcb routes add -e --data '{"domain":"api.example.com","routes":[{"path":"/*","upstreamResourceType":"CBR","upstreamResourceName":"my-service"}]}' # 3. 配置 DNS:将 api.example.com 的 CNAME 解析指向云开发环境域名 # 4. 验证访问 curl https://api.example.com/ ``` ## 命令速查 | 命令 | 说明 | |------|------| | `tcb domains ls` | 查看自定义域名列表 | | `tcb domains add --certid ` | 绑定自定义域名 | | `tcb domains rm ` | 解绑自定义域名 | --- # CloudBase CLI/HTTP 访问/HTTP 服务路由管理 > 当前文档链接: https://docs.cloudbase.net/cli-v1/routes :::info v3.0.0+ `tcb routes` 命令自 v3.0.0 起提供。 ::: `tcb routes` 用于管理 HTTP 网关的路由规则。路由规则定义了如何将 HTTP 请求(按域名+路径)转发到云函数、云托管、静态托管等上游服务。 :::tip 路由与域名的关系 路由必须绑定在已有的自定义域名下。如果还没有绑定自定义域名,请先使用 `tcb domains add` 添加,详见 [自定义域名管理](./domains.md)。 ::: ## 查看路由列表 查看当前环境下的所有路由配置: ```bash tcb routes list -e ``` 支持分页和过滤: ```bash # 分页查询 tcb routes list -e --limit 50 --offset 0 # 按域名过滤 tcb routes list -e --filter "Domain=api.example.com" # 按上游服务类型过滤 tcb routes list -e --filter "UpstreamResourceType=CBR" # 多条件组合(且关系,&连接;多值用逗号分隔,或关系) tcb routes list -e --filter "UpstreamResourceType=SCF&Domain=api.example.com,api2.example.com" ``` **`--filter` 可过滤字段**: | 字段 | 说明 | 可选值 | |------|------|--------| | `Domain` | 域名 | 任意域名字符串,多值逗号分隔(或关系) | | `Path` | 路由路径 | 任意路径字符串 | | `UpstreamResourceType` | 上游服务类型 | `SCF`、`CBR`、`STATIC_STORE`、`WEB_SCF`、`LH` | ## 创建路由 为指定域名创建一个或多个路由规则,通过 `--data` 参数传入 JSON 配置: ```bash tcb routes add -e --data '' ``` **JSON 数据格式**: ```json { "domain": "必填,域名", "routes": [ { "path": "必填,路径(支持通配符 /*)", "upstreamResourceType": "必填,上游类型", "upstreamResourceName": "必填,上游服务名", "enable": true, "enableAuth": false, "enableSafeDomain": true, "enablePathTransmission": false, "pathRewrite": { "prefix": "/newpath" }, "qpsPolicy": { "qpsTotal": 500, "qpsPerClient": { "limitBy": "ClientIP", "limitValue": 20 } } } ] } ``` **路由配置字段说明**: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `domain` | string | 是 | 目标域名,必须已通过 `tcb domains add` 绑定 | | `routes[].path` | string | 是 | 路由路径,支持通配符 `/*`,如 `/api/*` | | `routes[].upstreamResourceType` | string | 是 | 上游服务类型:`SCF`(云函数)、`CBR`(云托管)、`STATIC_STORE`(静态托管)、`WEB_SCF`(Web 云函数)、`LH`(轻量应用服务器) | | `routes[].upstreamResourceName` | string | 是 | 上游服务名称 | | `routes[].enable` | boolean | 否 | 是否启用路由,默认 `true` | | `routes[].enableAuth` | boolean | 否 | 是否开启身份认证,默认 `false` | | `routes[].enableSafeDomain` | boolean | 否 | 是否开启安全域名校验,默认 `true` | | `routes[].enablePathTransmission` | boolean | 否 | 是否开启路径透传,默认 `false` | | `routes[].pathRewrite` | object | 否 | 路径重写配置,`{ "prefix": "/newpath" }` | | `routes[].qpsPolicy.qpsTotal` | number | 否 | 资源总 QPS 限制,最大 500 | | `routes[].qpsPolicy.qpsPerClient` | object | 否 | 单客户端限频:`{ "limitBy": "ClientIP" \| "UserID", "limitValue": N }` | **使用示例**: ```bash # 将 /api/* 转发到云托管服务 tcb routes add -e --data '{"domain":"api.example.com","routes":[{"path":"/api/*","upstreamResourceType":"CBR","upstreamResourceName":"my-service"}]}' # 将 /func/* 转发到云函数,并开启身份认证 tcb routes add -e --data '{"domain":"api.example.com","routes":[{"path":"/func/*","upstreamResourceType":"SCF","upstreamResourceName":"my-function","enableAuth":true}]}' # 静态网站根路径 tcb routes add -e --data '{"domain":"www.example.com","routes":[{"path":"/*","upstreamResourceType":"STATIC_STORE","upstreamResourceName":"website"}]}' # 带 QPS 限流的路由 tcb routes add -e --data '{"domain":"api.example.com","routes":[{"path":"/v1/*","upstreamResourceType":"CBR","upstreamResourceName":"backend","qpsPolicy":{"qpsTotal":1000,"qpsPerClient":{"limitBy":"ClientIP","limitValue":50}}}]}' # 一次创建多条路由 tcb routes add -e --data '{"domain":"api.example.com","routes":[{"path":"/v1/*","upstreamResourceType":"SCF","upstreamResourceName":"func-v1"},{"path":"/v2/*","upstreamResourceType":"CBR","upstreamResourceName":"service-v2"}]}' ``` :::caution 注意 - 同一域名下不能有重复路径 - 域名必须已通过 `tcb domains add` 绑定后才能创建路由 ::: ## 修改路由 增量更新已有路由配置。通过「域名 + 路径」定位路由,只更新传入的字段: ```bash tcb routes edit -e --data '' ``` **JSON 数据格式**(与创建相同,路径为必填定位键,其余字段按需传入): ```bash # 更换路由的上游服务 tcb routes edit -e --data '{"domain":"api.example.com","routes":[{"path":"/api/*","upstreamResourceName":"new-service"}]}' # 禁用路由 tcb routes edit -e --data '{"domain":"api.example.com","routes":[{"path":"/api/*","enable":false}]}' # 开启身份认证和路径透传 tcb routes edit -e --data '{"domain":"api.example.com","routes":[{"path":"/api/*","enableAuth":true,"enablePathTransmission":true}]}' # 更新 QPS 限流配置 tcb routes edit -e --data '{"domain":"api.example.com","routes":[{"path":"/v1/*","qpsPolicy":{"qpsTotal":200,"qpsPerClient":{"limitBy":"ClientIP","limitValue":20}}}]}' ``` :::tip 修改是增量更新,只有传入的字段会被更新,未传入的字段保持不变。 ::: ## 删除路由 删除指定域名下的路由规则: ```bash # 删除单条路由 tcb routes delete api.example.com -e -p /api/* # 同时删除多条路由(逗号分隔) tcb routes delete api.example.com -e -p "/api/v1/*,/api/v2/*" ``` **命令参数**: | 参数 | 说明 | 必填 | |------|------|------| | `` | 目标域名 | 是 | | `-p, --path ` | 要删除的路由路径,多个路径用逗号分隔 | 是 | ## 典型使用流程 ```bash # 1. 先绑定自定义域名(详见 domains 文档) tcb domains add api.example.com --certid -e # 2. 创建路由规则 tcb routes add -e --data '{"domain":"api.example.com","routes":[{"path":"/api/*","upstreamResourceType":"CBR","upstreamResourceName":"my-service"}]}' # 3. 查看路由配置 tcb routes list -e --filter "Domain=api.example.com" # 4. 更新路由(如需调整) tcb routes edit -e --data '{"domain":"api.example.com","routes":[{"path":"/api/*","enableAuth":true}]}' # 5. 删除路由(如需清理) tcb routes delete api.example.com -e -p /api/* ``` ## 命令速查 | 命令 | 说明 | |------|------| | `tcb routes list` | 查看路由列表,支持过滤和分页 | | `tcb routes add --data ` | 创建路由规则 | | `tcb routes edit --data ` | 增量更新路由配置 | | `tcb routes delete -p ` | 删除路由 | --- # CloudBase CLI/用户、角色与权限/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/user/management --- title: 用户管理 --- # 用户管理 :::info v3.0.0+ `tcb user` 命令自 v3.0.0 起提供。 ::: `tcb user` 命令用于管理 CloudBase 环境中的终端用户(内部用户/外部用户),支持创建、查询、修改和删除。 :::info 与角色的关系 用户与角色(`tcb role`)相互关联。可在创建/修改用户时绑定角色,也可通过 `tcb role update` 管理角色成员。 ::: ## `tcb user create` 创建用户。 ```bash tcb user create [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `name` | 用户名(必填) | — | | `-e, --env-id ` | 环境 ID | — | | `--uid ` | 自定义用户 ID(可选,不填则自动生成) | — | | `--password ` | 密码(可选) | — | | `--type ` | 用户类型:`internalUser` / `externalUser` | `internalUser` | | `--status ` | 用户状态:`ACTIVE` / `BLOCKED` | `ACTIVE` | | `--nickname ` | 昵称 | — | | `--json` | 输出 JSON | — | ### 示例 ```bash # 创建内部用户 tcb user create alice -e my-env-id # 创建用户并设置密码和昵称 tcb user create alice --password Secret123 --nickname "Alice" -e my-env-id # 创建外部用户(默认封禁) tcb user create external-bob --type externalUser --status BLOCKED -e my-env-id ``` --- ## `tcb user list` 查询用户列表,支持多维度筛选。 ```bash tcb user list [options] ``` ### 参数 | 参数 | 简写 | 说明 | 默认值 | |------|------|------|--------| | `-e, --env-id ` | — | 环境 ID | — | | `--offset ` | — | 跳过条数 | `0` | | `--limit ` | — | 返回条数上限 | `20` | | `--username ` | — | 按用户名筛选 | — | | `--nickname ` | — | 按昵称筛选 | — | | `--phone ` | — | 按手机号筛选 | — | | `--uids ` | — | 按用户 UID 列表筛选,多个 UID 用逗号分隔 | — | | `--json` | — | 输出 JSON | — | ### 示例 ```bash # 查看全部用户(分页) tcb user list -e my-env-id # 按用户名搜索 tcb user list --username alice -e my-env-id # 翻页(第 2 页,每页 20 条) tcb user list --offset 20 --limit 20 -e my-env-id # 按 UID 列表查询 tcb user list --uids 1001,1002 -e my-env-id ``` --- ## `tcb user update` 修改用户信息。 ```bash tcb user update [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `uid` | 用户 ID(必填) | — | | `-e, --env-id ` | 环境 ID | — | | `--username ` | 新用户名 | — | | `--type ` | 用户类型:`internalUser` / `externalUser` | — | | `--password ` | 新密码 | — | | `--status ` | 用户状态:`ACTIVE` / `BLOCKED` | — | | `--nickname ` | 昵称 | — | | `--json` | 输出 JSON | — | ### 示例 ```bash # 封禁用户 tcb user update user-uid-123 --status BLOCKED -e my-env-id # 修改密码 tcb user update user-uid-123 --password NewSecret456 -e my-env-id # 同时修改用户名和昵称 tcb user update user-uid-123 --username bob --nickname "Bob" -e my-env-id ``` --- ## `tcb user delete` 删除一个或多个用户(不可恢复)。 ```bash tcb user delete [options] ``` ### 参数 | 参数 | 说明 | |------|------| | `uid...` | 一个或多个用户 ID,空格分隔 | | `-e, --env-id ` | 环境 ID | | `--json` | 输出 JSON | ### 示例 ```bash # 删除单个用户 tcb user delete user-uid-123 -e my-env-id # 批量删除 tcb user delete user-uid-123 user-uid-456 -e my-env-id ``` :::caution 注意 删除操作不可撤销。建议先通过 `tcb user list` 确认用户 ID 后再执行。 ::: --- # CloudBase CLI/用户、角色与权限/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/role/management --- title: 角色管理 --- # 角色管理 :::info v3.0.0+ `tcb role` 命令自 v3.0.0 起提供。 ::: `tcb role` 命令用于管理 CloudBase 环境中的访问控制角色,包括系统预设角色和自定义角色。 :::info 角色与权限的关系 - **权限(Permission)**:控制资源级别的读写访问(`tcb permission`) - **角色(Role)**:绑定用户与策略,控制网关路由层的访问权限 - 二者各司其职,共同构成 CloudBase 访问控制体系 ::: ## 系统预设策略 创建自定义角色时可引用以下预设策略: | 策略 Code | 效果 | 说明 | |------|------|------| | `AdministratorAccess` | allow | 管理员全量访问 | | `FunctionsAccess` | allow | 云函数访问权 | | `StoragesAccess` | allow | 云存储访问权 | | `CloudrunAccess` | allow | 云托管访问权 | | `FunctionsDeny` | deny | 禁止访问云函数 | | `StoragesDeny` | deny | 禁止访问云存储 | | `CloudrunDeny` | deny | 禁止访问云托管 | > deny 策略优先于 allow,同时存在时以 deny 为准。 --- ## `tcb role list` 查询环境下所有角色(系统角色 + 自定义角色)。 ```bash tcb role list [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID | — | | `--type ` | 过滤类型:`system` / `custom` | 全部 | | `--limit ` | 返回条数上限 | `20` | | `--offset ` | 跳过条数 | `0` | | `--detail` | 同时显示成员列表和策略详情 | — | | `--json` | 输出 JSON | — | ### 示例 ```bash # 查看全部角色 tcb role list -e my-env-id # 只看自定义角色 tcb role list --type custom -e my-env-id # 显示详情(含成员和策略) tcb role list --detail -e my-env-id ``` --- ## `tcb role get` 查询指定角色的详细信息。 ```bash tcb role get --id [options] tcb role get --identity [options] tcb role get --name [options] ``` **三种查询方式选其一(必填):** | 参数 | 说明 | |------|------| | `--id ` | 按角色 ID 精确查询 | | `--identity ` | 按角色标识精确查询 | | `--name ` | 按角色名称模糊查询 | | `--detail` | 显示成员和策略详情 | | `-e, --env-id ` | 环境 ID | ### 示例 ```bash # 按 ID 查询 tcb role get --id 2032299359962361858 -e my-env-id # 按标识查询(含详情) tcb role get --identity administrator --detail -e my-env-id ``` --- ## `tcb role create` 创建自定义角色。 ```bash tcb role create --name --identity [options] ``` ### 参数 | 参数 | 说明 | 约束 | |------|------|------| | `--name ` | 角色名称(必填) | 2–32 字符,中文/字母开头 | | `--identity ` | 角色标识(必填,唯一) | 字母/数字/`_-:@.` | | `--description ` | 角色描述 | 最多 255 字符 | | `--users ` | 初始成员 UID,逗号分隔 | 最多 100 个 | | `--policies ` | 策略 JSON 数组(见下方格式) | 最多 50 条 | | `-e, --env-id ` | 环境 ID | — | ### `--policies` 格式 策略数组支持**预设策略字符串**和**自定义策略对象**混合使用: ```json [ "FunctionsAccess", { "code": "custom_api", "name": "API 路径策略", "description": "仅允许访问 /api 路径", "effect": "allow", "expression": { "version": "1.0", "statement": [ { "action": "functions:/api/*", "resource": "*", "effect": "allow" } ] } } ] ``` **自定义策略对象字段:** | 字段 | 说明 | 必填 | |------|------|------| | `code` | 策略唯一标识 | ✅ | | `effect` | `allow` 或 `deny` | ✅ | | `name` | 策略名称 | 否 | | `description` | 描述 | 否 | | `expression` | 策略表达式(含 `version` + `statement` 数组) | 否 | **`statement` 子语句字段:** | 字段 | 说明 | 示例 | |------|------|------| | `action` | `<功能类型>:<路径>`,`*` 表示全部 | `functions:*` / `functions:/api/*` | | `resource` | 资源范围,`*` 表示全部 | `*` | | `effect` | `allow` 或 `deny` | `allow` | 功能类型枚举:`storages` / `functions` / `cloudrun` / `model` ### 示例 ```bash # 最简创建 tcb role create --name 开发者 --identity developer -e my-env-id # 创建并分配初始成员 tcb role create --name 开发者 --identity developer --users "1003,1005" -e my-env-id # 使用预设策略 tcb role create --name 网关角色 --identity gateway_role \ --policies '["FunctionsAccess","StoragesDeny"]' \ -e my-env-id # 混合使用预设 + 自定义策略 tcb role create \ --name 全栈开发者 \ --identity fullstack_dev \ --policies '[ "FunctionsAccess", { "code": "custom_api", "name": "API路径策略", "effect": "allow", "expression": { "version": "1.0", "statement": [{"action": "functions:/api/*", "resource": "*", "effect": "allow"}] } } ]' \ --users "1003,1005,1007" \ -e my-env-id ``` --- ## `tcb role update` 更新角色信息(名称、描述、成员、策略)。 ```bash tcb role update --id [options] ``` :::caution 系统角色修改限制 - **管理员角色**:只能修改成员,不能修改策略 - **注册用户/组织成员/匿名用户/所有用户**:只能修改策略,不能修改成员 - **所有系统角色**:不可修改角色名称 - **自定义角色**:以上均可修改 ::: ### 参数 | 参数 | 说明 | |------|------| | `--id ` | 角色 ID(必填) | | `--name ` | 修改角色名称(自定义角色专用) | | `--description ` | 修改角色描述 | | `--add-users ` | 添加成员,逗号分隔 UID | | `--remove-users ` | 移除成员,逗号分隔 UID | | `--add-policies ` | 添加策略,格式同 `role create --policies` | | `--remove-policies ` | 移除策略,传策略 code 字符串数组 | | `-e, --env-id ` | 环境 ID | ### 示例 ```bash # 修改角色名称 tcb role update --id 2032299359962361858 --name "高级开发者" -e my-env-id # 同时添加/移除成员 tcb role update --id 2032299359962361858 \ --add-users "1001,1002" \ --remove-users "1003" \ -e my-env-id # 修改策略 tcb role update --id 2032299359962361858 \ --add-policies '["FunctionsAccess"]' \ --remove-policies '["StoragesDeny"]' \ -e my-env-id ``` --- ## `tcb role delete` 删除自定义角色(不可撤销,系统角色不可删除)。 ```bash tcb role delete [options] ``` ### 示例 ```bash # 删除单个角色 tcb role delete 2037415874111574018 -e my-env-id # 批量删除(最多 100 个) tcb role delete 2037415874111574018 2037420918731018242 -e my-env-id ``` --- ## 相关命令 - [`tcb permission`](/cli-v1/permission/management) — 管理资源级访问权限 - [`tcb user`](/cli-v1/user/management) — 管理终端用户 --- # CloudBase CLI/用户、角色与权限/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/permission/management --- title: 权限管理 --- # 权限管理 ::::caution 已废弃 `tcb permission` 命令已下线,运行后**不再执行任何实际操作**,仅输出迁移提示。 请迁移至新命令 [`tcb policy`](/cli-v1/policy/management): - `tcb permission set` → `tcb policy set` - `tcb permission get` → `tcb policy list` / `tcb policy get` 新命令基于 OPA Rego 策略引擎,设置后旧网关鉴权将直接失效。 :::: ::::info v3.0.0+ `tcb permission` 命令自 v3.0.0 起提供,自 `tcb policy`(v3.5.7+)上线后已下线。 :::: `tcb permission` 是旧网关模式下的资源权限管理命令,包含 `set` 与 `get` 两个子命令。两个子命令现均已下线,调用时仅打印迁移指引,不再对资源权限做任何变更或查询。 ## `tcb permission set` 设置指定资源的访问权限(已下线,不再生效)。执行后仅打印迁移提示,不修改任何权限。请改用 [`tcb policy set`](/cli-v1/policy/management#tcb-policy-set) 设置 OPA Rego 鉴权策略。 ```bash tcb permission set : --level [options] ``` 历史支持的资源类型与权限级别见下表(仅供迁移参考,命令不再生效): | 资源类型 | 支持的 `--level` | | -------------------------------- | -------------------------------------------------------------- | | `collection`(文档型数据库集合) | `readonly` / `private` / `adminwrite` / `adminonly` / `custom` | | `storage`(存储桶) | `readonly` / `private` / `adminwrite` / `adminonly` / `custom` | | `table`(SQL 表) | `readonly` / `private` / `adminwrite` / `adminonly` | | `function`(云函数) | `custom`(只支持自定义规则) | ### 迁移到 OPA Rego 旧的 `--level` 级别无法直接映射到 OPA Rego 策略,请在理解 OPA `input` 上下文后用 Rego 重写规则: - OPA 概念与 `input` 结构:[OPA 鉴权策略介绍](../../envconfig/authz-opa/intro) - 旧网关策略 → Rego 翻译对照表:[迁移指南](../../envconfig/authz-opa/migration) --- ## `tcb permission get` 查询资源的当前权限配置(已下线,不再生效)。执行后仅打印迁移提示,不查询任何权限。请改用 [`tcb policy list`](/cli-v1/policy/management#tcb-policy-list) 查看策略列表,或 [`tcb policy get`](/cli-v1/policy/management#tcb-policy-get) 获取策略内容。 ```bash tcb permission get [type[:resource,...]] [options] ``` --- ## 相关命令 - [`tcb policy`](/cli-v1/policy/management) — OPA Rego 策略管理(推荐) - [OPA 鉴权策略介绍](../../envconfig/authz-opa/intro) — OPA 概念、`input` 上下文与实战案例 - [迁移指南](../../envconfig/authz-opa/migration) — 旧网关鉴权迁移到 OPA Rego - [`tcb role`](/cli-v1/role/management) — 管理角色与访问策略 - 控制台配置:[CloudBase 控制台](https://tcb.cloud.tencent.com/dev) --- # CloudBase CLI/用户、角色与权限/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/policy/management --- title: 策略管理 --- # 策略管理 `tcb policy` 命令(v3.5.7+)用于管理 CloudBase 环境的 OPA Rego 鉴权策略,包括查看策略列表、获取策略内容、设置用户策略。设置 Rego 策略后**旧网关鉴权将直接失效**,旧命令 `tcb permission set/get` 已废弃,请迁移至 `tcb policy`。 ::::tip OPA 鉴权策略概念 关于 OPA 鉴权策略的工作机制、`input` 上下文结构、`allow` / `deny` 决策矩阵及实战案例,请参考 [OPA 鉴权策略介绍](../../envconfig/authz-opa/intro);从旧版网关鉴权迁移到 OPA Rego 的步骤与策略翻译对照表,请参考 [迁移指南](../../envconfig/authz-opa/migration)。 :::: ## `tcb policy list` 查看环境下的网关鉴权策略列表。 > PG 环境和 OPA 引擎环境下返回空列表。 ```bash tcb policy list [options] ``` ### 参数 | 参数 | 说明 | 默认值 | | -------------------------- | ------------------------------ | ------ | | `-e, --env-id ` | 环境 ID | — | | `--resource-type ` | 按资源类型过滤,可选值:`policy` | 全部 | | `--json` | 输出 JSON | — | ### 输出字段 | 字段 | 说明 | | -------- | ------------------------------------------ | | 策略ID | PolicyId | | 资源类型 | ResourceType | | 资源ID | ResourceId | | 资源标题 | ResourceTitle | | 资源名称 | ResourceName | | 效果 | Effect(允许/拒绝) | | 可访问 | IsAccess | | 角色标识 | RoleIdentity | | 角色名称 | RoleName | | 安全规则 | SafeRule | ### 示例 ```bash # 查看全部策略 tcb policy list -e my-env-id # 按资源类型过滤 tcb policy list --resource-type policy -e my-env-id # JSON 格式输出 tcb policy list --json -e my-env-id ``` --- ## `tcb policy get` 获取鉴权策略内容(Rego 格式)。默认获取用户配置的策略,加 `--extension` 获取平台为该环境单独配置的策略。 ```bash tcb policy get [options] ``` ### 参数 | 参数 | 说明 | | ---------------------- | ------------------------------------------------------------ | | `-e, --env-id ` | 环境 ID | | `--extension` | 获取为该环境单独配置的策略(默认获取用户配置的策略) | | `--json` | 输出 JSON | ### 示例 ```bash # 获取用户策略 tcb policy get -e my-env-id # 获取平台扩展策略 tcb policy get --extension -e my-env-id # JSON 格式输出 tcb policy get --json -e my-env-id ``` --- ## `tcb policy set` 设置用户 Rego 鉴权策略。保存后旧网关鉴权将直接失效。 ```bash tcb policy set [regoContent] ``` ### 参数 | 参数 | 说明 | | ---------------------- | ------------------- | | `` | Rego 策略内容(必填) | | `-e, --env-id ` | 环境 ID | | `--json` | 输出 JSON | ::::caution 重要提示 设置 Rego 策略后,旧网关鉴权将直接失效,请确认策略内容正确后再执行。 :::: ### Rego 策略格式 策略需要以 `package authz.user` 开头,使用 OPA Rego 语法: ```rego package authz.user default allow := false # 示例:放通匿名用户访问云函数 allow if { input.cloudbase.resource_type == "functions" input.subject.auth_type == "anonymous" } ``` 关于 `input` 字段的详细说明,请参考 [OPA 鉴权策略介绍](../../envconfig/authz-opa/intro)。 ### 示例 ```bash # 直接传入 Rego 内容 tcb policy set 'package authz.user default allow := false # 放通匿名用户访问云函数 allow if { input.cloudbase.resource_type == "functions" input.subject.auth_type == "anonymous" }' -e my-env-id # JSON 格式输出 tcb policy set 'package authz.user default allow := false' --json -e my-env-id ``` --- ## 相关命令 - [`tcb permission`](/cli-v1/permission/management) — 旧网关模式资源权限管理(已废弃) - [`tcb role`](/cli-v1/role/management) — 管理角色与访问策略 - 控制台配置:[CloudBase 控制台](https://tcb.cloud.tencent.com/dev) --- # CloudBase CLI/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/secrets/management --- title: 临时密钥 --- # 临时密钥 :::info v3.0.0+ `tcb secrets` 命令自 v3.0.0 起提供。 ::: `tcb secrets get` 命令用于获取当前 Web 登录态下的临时密钥(secretId / secretKey / token),方便在开发调试时直接初始化 `@cloudbase/manager-node` 或 `@cloudbase/node-sdk`,无需手动配置。 :::caution 安全限制 - **仅支持临时密钥输出**(通过 `tcb login` Web 登录获取) - 通过 `tcb login --key` 输入的永久密钥**不允许输出**,此行为无法绕过 - 永久密钥一旦泄露影响永久,请勿将其用于本命令 ::: ## `tcb secrets get` 获取当前登录态的临时密钥信息。 ```bash tcb secrets get [options] ``` ### 参数 | 参数 | 说明 | |------|------| | `-e, --env-id ` | 绑定环境 ID(附加到输出,用于初始化 SDK) | | `--json` | 输出 JSON(适合脚本消费) | ### 示例 ```bash # 查看当前临时密钥 tcb secrets get # 指定环境 ID(输出中会附带 envId) tcb secrets get -e my-env-id # JSON 格式(适合在脚本中读取并初始化 SDK) tcb secrets get --json -e my-env-id ``` ### JSON 输出格式 ```json { "secretId": "AKID...", "secretKey": "xxx", "token": "yyy", "expiredAt": "2026-03-30 22:00:00", "isTemporary": true, "envId": "my-env-id" } ``` --- ## 使用场景:初始化 SDK 获取临时密钥后,可在本地脚本或工具中直接初始化 CloudBase SDK,用于调试和自动化: ```js // @cloudbase/manager-node const CloudBase = require('@cloudbase/manager-node') const app = new CloudBase({ secretId: '', secretKey: '', token: '', envId: 'my-env-id' }) // @cloudbase/node-sdk const tcb = require('@cloudbase/node-sdk') const app = tcb.init({ secretId: '', secretKey: '', token: '', env: 'my-env-id' }) ``` :::warning 安全提示 请勿将临时密钥提交到代码仓库,或将其粘贴到公开渠道。临时密钥有时效性,过期后自动失效。 ::: --- ## 相关命令 - [`tcb login`](/cli-v1/install#登录认证) — 登录认证 - [`tcb env list`](/cli-v1/envs/basement) — 查看可用环境 --- # CloudBase CLI/AI Agent/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/agent/management --- title: AI Agent 管理 --- # AI Agent 管理 :::info v3.0.0+ `tcb agent` 命令自 v3.0.0 起提供。 ::: `tcb agent` 命令用于管理 CloudBase AI Agent(智能体),支持创建、查询、更新和删除。每个 Agent 底层由云函数提供计算能力,CLI 统一封装了代码上传、配置和生命周期管理。 :::tip 什么是 CloudBase AI Agent CloudBase AI Agent 是运行在云函数之上的智能体,适合需要长连接、长时间运行的 AI 应用场景(默认超时 7200s)。 ::: ## `tcb agent create` 创建 AI Agent。 ```bash tcb agent create --name [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `--name ` | Agent 名称(必填) | — | | `-e, --env-id ` | 环境 ID | — | | `--agent-id ` | 自定义 Agent ID(不填则自动生成) | — | | `--code [path]` | 本地代码路径(不填则取当前目录),自动打包上传 | 当前目录 | | `--runtime ` | 运行时版本 | — | | `--timeout ` | 超时时间(秒) | `7200` | | `--memory-size ` | 内存大小(MB),64 或 128–3072 | `128` | | `--install-dep` | 自动安装依赖(部署时不含 node_modules 时使用) | — | | `--ignore ` | 忽略文件模式,逗号分隔 | `.git,node_modules,.DS_Store` | | `--env ` | 环境变量,格式 `KEY1=VALUE1,KEY2=VALUE2` | — | | `--json` | 输出 JSON | — | ### 示例 ```bash # 最简创建(代码取当前目录) tcb agent create --name my-agent -e my-env-id # 指定代码目录和运行时 tcb agent create --name my-agent --code ./agent-code --runtime Nodejs20.19 -e my-env-id # 传入环境变量(供 Agent 代码读取) tcb agent create --name my-agent \ --env "OPENAI_API_KEY=sk-xxx,MAX_TOKENS=4096" \ -e my-env-id ``` --- ## `tcb agent list` 查看已创建的 Agent 列表。 ```bash tcb agent list [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID | — | | `--limit ` | 每页数量 | `10` | | `--offset ` | 偏移量 | `0` | | `--json` | 输出 JSON | — | ### 示例 ```bash tcb agent list -e my-env-id # 分页查看 tcb agent list --limit 5 --offset 10 -e my-env-id ``` --- ## `tcb agent detail` 查看单个 Agent 的详细信息,包含底层云函数就绪状态、配置等。 ```bash tcb agent detail [options] ``` ### 示例 ```bash tcb agent detail agent-xxx -e my-env-id # 输出 JSON(适合脚本判断就绪状态) tcb agent detail agent-xxx --json -e my-env-id ``` --- ## `tcb agent update` 更新 Agent 代码或配置。 ```bash tcb agent update [options] ``` ### 参数 | 参数 | 说明 | |------|------| | `--code [path]` | 更新代码(默认当前目录) | | `--runtime ` | 更新运行时版本 | | `--timeout ` | 更新超时时间 | | `--memory-size ` | 更新内存大小(MB) | | `--install-dep` | 自动安装依赖 | | `--ignore ` | 更新忽略模式 | | `--env ` | 更新环境变量 | | `-e, --env-id ` | 环境 ID | ### 示例 ```bash # 仅更新代码 tcb agent update agent-xxx --code ./new-agent-code -e my-env-id # 更新运行时和超时 tcb agent update agent-xxx --runtime Nodejs20.19 --timeout 3600 -e my-env-id ``` --- ## `tcb agent delete` 删除 Agent 及其底层云函数/云托管资源(不可恢复)。 ```bash tcb agent delete [options] ``` ### 参数 | 参数 | 说明 | |------|------| | `-y, --yes` | 跳过确认提示(适合脚本/CI 调用) | | `-e, --env-id ` | 环境 ID | | `--dry-run` | 模拟运行,仅预览将要删除的 Agent 和底层资源,不实际执行 | ### 示例 ```bash # 交互确认后删除 tcb agent delete agent-xxx -e my-env-id # 预览将要删除的资源(不实际执行) tcb agent delete agent-xxx --dry-run -e my-env-id # 跳过确认(脚本场景) tcb agent delete agent-xxx --yes -e my-env-id ``` :::caution 注意 删除 Agent 会同时清理底层云函数资源,操作不可撤销。建议先使用 `--dry-run` 预览。 ::: --- # CloudBase CLI/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/app/management --- title: 应用部署 --- # 应用部署 :::info v3.0.0+ `tcb app` 命令自 v3.0.0 起提供。 ::: `tcb app` 命令用于部署和管理 CloudBase 云应用。每次部署会覆盖上一次,并保留历史部署记录(可通过 `tcb app versions` 查看)。 :::tip 适用场景:有构建流程的前端项目 适合以下场景: - 有安装依赖 + 构建步骤的前端项目(React / Vue / Next.js / Vite / Angular / Nuxt 等) - 纯静态项目 - CI/CD 流水线中自动化部署 CLI 会自动完成:**安装依赖 → 构建 → 上传产物 → 绑定路由**,无需手动操作文件。每次部署会覆盖上一次,可通过 `tcb app versions list` 查看历史部署记录。 ::: --- ## `tcb app deploy` 部署应用。CLI 会自动执行:安装依赖 → 构建 → 上传构建产物 → 绑定路由。 以下三种调用方式完全等价: ```bash tcb app deploy [serviceName] [options] # 完整命令 tcb deploy [serviceName] [options] # 顶层别名 tcb [options] # 裸命令(不带任何子命令时自动路由到 app deploy) ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `[serviceName]` | 应用名称(可选,不填则自动从 `package.json` 读取或交互输入) | — | | `-e, --env-id ` | 目标环境 ID | — | | `--framework ` | 框架类型:`react` / `vue` / `vite` / `next` / `nuxt` / `angular` / `static` | — | | `--install-command ` | 安装命令(留空则跳过安装) | `npm install` | | `--build-command ` | 构建命令(留空则跳过构建,适合纯静态项目) | — | | `--output-dir ` | 构建产物目录(默认 `./dist`;无需构建时填 `./`) | 自动检测 | | `--deploy-path ` | 静态托管挂载路径 | `/服务名` | | `--cwd ` | 项目目录(相对路径) | 当前目录 | | `-f, --force` | 应用名称已存在时跳过确认直接覆盖 | — | ### 示例 ```bash # 部署 Vite 项目(在项目根目录执行) tcb app deploy --framework vite -e my-env-id # 使用顶层别名,效果完全相同 tcb deploy --framework vite -e my-env-id # 裸命令:什么都不加,交互式引导部署 tcb # 指定项目子目录(monorepo 场景) tcb app deploy --cwd ./frontend -e my-env-id # 部署 Next.js 项目,指定构建产物目录 tcb app deploy --framework next --output-dir .next -e my-env-id # 纯静态项目(跳过构建步骤) tcb app deploy --framework static --build-command "" --output-dir ./public -e my-env-id # CI 场景:跳过确认,覆盖更新已有应用 tcb app deploy --framework react --force -e my-env-id ``` ### 通过 cloudbaserc.json 配置部署 除了通过 CLI 参数指定部署选项外,也可以在 `cloudbaserc.json` 中配置 `app` 字段,实现零命令行参数的部署体验。 **配置文件结构**: ```json { "version": "2.0", "envId": "my-env-id", "app": { "serviceName": "my-app", "root": "./packages/web", "framework": "react", "installCommand": "npm install", "buildCommand": "npm run build", "outputDir": "build", "deployPath": "/my-app", "envVariables": { "API_URL": "https://api.example.com" } } } ``` **配置字段**: | 字段 | 说明 | 默认值 | |------|------|--------| | `serviceName` | 服务名称 | `package.json` 的 `name` 或目录名 | | `root` | 项目根目录(相对路径),用于 monorepo 场景 | 当前目录 | | `framework` | 框架类型:`react` / `vue` / `vite` / `next` / `nuxt` / `angular` / `static` | 自动检测 | | `installCommand` | 安装命令,空字符串跳过安装 | `npm install` | | `buildCommand` | 构建命令,空字符串跳过构建 | 根据框架自动检测 | | `outputDir` | 构建产物目录 | `./dist`(无构建命令时为 `./`) | | `deployPath` | 部署路径(静态托管挂载路径) | `/${serviceName}` | | `envVariables` | 环境变量,键值对形式,云端构建时注入 | — | | `ignore` | 忽略的文件/目录 glob 模式 | — | **优先级总则**:CLI 参数 > `cloudbaserc.json` 配置 > 自动检测 > 交互式输入 > ⚠️ **注意**:部分参数有特殊规则(如 `envVariables` 无 CLI 参数,`buildCommand` 有框架默认值等),详见下表。 **各参数的优先级顺序**: | 参数 | 优先级 | |------|--------| | `serviceName` | CLI 位置参数 > `cloudbaserc.json` 配置 > 交互式选择 > 自动推断(package.json 或目录名) | | `root` | `--cwd` 选项 > `cloudbaserc.json` 配置 > 当前目录 | | `framework` | `--framework` 选项 > `cloudbaserc.json` 配置 > 自动检测(package.json 依赖) > 未指定 | | `installCommand` | `--install-command` 选项 > `cloudbaserc.json` 配置 > 自动推断(`npm install` 或空) > 交互输入 | | `buildCommand` | `--build-command` 选项 > `cloudbaserc.json` 配置 > 自动检测(框架默认值或 package.json scripts) > 交互输入 > 空字符串 | | `outputDir` | `--output-dir` 选项 > `cloudbaserc.json` 配置 > 自动检测(框架默认值) > 交互输入 > `./dist`(有构建)或 `./`(无构建) | | `deployPath` | `--deploy-path` 选项 > `cloudbaserc.json` 配置 > 交互输入(默认 `/{serviceName}`) | | `envVariables` | 仅从 `cloudbaserc.json` 配置读取(无 CLI 参数) | **特殊说明**: - `--cwd` 与 `config.app.root` 都用于指定项目工作目录,`--cwd` 优先级更高 - `envVariables` 无对应 CLI 参数,仅能通过 `cloudbaserc.json` 配置 - 当指定 `--yes` 或 `--json` 时,会跳过所有交互式输入,改用自动推断值 - 框架自动检测遵循优先级:Next.js > Nuxt > Angular > Vite > Vue CLI > React(CRA)> 静态 HTML **自动保存**:首次部署成功后,若 `cloudbaserc.json` 中不存在 `app` 配置,CLI 会自动将本次部署参数写回配置文件,后续部署无需重复输入。 **更多配置字段的详细说明,请参考 [配置文件文档](/cli-v1/config)。** --- ## `tcb app list` 查看当前环境的应用列表。 ```bash tcb app list [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID | — | | `-l, --limit ` | 返回数量 | `20` | | `-o, --offset ` | 偏移量 | `0` | | `--json` | 输出 JSON | — | ### 示例 ```bash tcb app list -e my-env-id ``` --- ## `tcb app info` 查看指定应用的详情(状态、版本、访问域名等)。 ```bash tcb app info [options] ``` ### 示例 ```bash tcb app info my-app -e my-env-id # JSON 输出(适合脚本读取访问域名) tcb app info my-app --json -e my-env-id ``` --- ## `tcb app versions list` 查看应用版本历史列表。 ```bash tcb app versions list [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `` | 应用名称(必填) | — | | `-l, --limit ` | 返回数量 | `20` | | `-o, --offset ` | 偏移量 | `0` | | `-e, --env-id ` | 环境 ID | — | ### 示例 ```bash tcb app versions list my-app -e my-env-id ``` --- ## `tcb app versions detail` 查看应用指定版本的详细信息,默认显示最新版本。 ```bash tcb app versions detail [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `` | 应用名称(必填) | — | | `--version-name ` | 版本名称(不填则查最新版本) | — | | `-e, --env-id ` | 环境 ID | — | ### 示例 ```bash # 查看最新版本详情 tcb app versions detail my-app -e my-env-id # 查看指定版本详情 tcb app versions detail my-app --version-name my-app-001 --env-id env-xxx ``` --- ## `tcb app delete` 删除应用记录,不会删除底层应用资源文件。 ```bash tcb app delete [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `` | 应用名称(必填) | — | | `-e, --env-id ` | 环境 ID | — | | `--dry-run` | 预览删除操作,不实际执行 | — | ### 示例 ```bash tcb app delete my-app -e my-env-id # 预览删除(不实际执行) tcb app delete my-app -e my-env-id --dry-run ``` :::caution 注意 此操作会删除应用的所有版本和资源,不可恢复。项目资源文件需单独在静态托管桶中手动清除。 ::: --- # CloudBase CLI/可观测性/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/logs/search --- title: 日志检索 --- # 日志检索 :::info v3.0.0+ `tcb logs` 命令自 v3.0.0 起提供。 ::: CloudBase CLI 提供统一的日志检索入口,支持云函数、云托管、数据库、大模型等全类型日志的 CLS 检索语法查询。 :::tip 前置条件 使用日志功能需要当前环境已开通日志服务。若未开通,CLI 会自动提示引导开通(免费,无额外计费)。 ::: ## `tcb logs search` 搜索当前环境的日志。 ```bash tcb logs search [options] ``` ### 参数 | 参数 | 简写 | 说明 | 默认值 | |------|------|------|--------| | `--env-id ` | `-e` | 环境 ID | — | | `--query ` | `-q` | CLS 检索语句 | `*`(全部) | | `--timeRange ` | `-t` | 时间范围,相对或绝对 | `1h` | | `--limit ` | `-l` | 返回条数(1–100) | `20` | | `--sort ` | — | 排序方式:`desc` / `asc` | `desc` | | `--context ` | — | 分页游标,透传上次返回值 | — | | `--service ` | — | 日志服务:`tcb` / `tcbr` | `tcb` | | `--json` | — | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查看最近 1 小时全部日志 tcb logs search -e my-env-id # 按函数名过滤,最近 6 小时 tcb logs search -q 'function_name:"myFunc"' -t 6h -e my-env-id # 查看报错日志(状态码非 200/202) tcb logs search -q 'function_name:"myFunc" | select request_id, max(status_code) as status_code, max(ret_msg) as ret_msg where status_code>200 AND status_code!=202 AND retry_num=0 group by request_id limit 10' -e my-env-id # 按 requestId 精确查单次调用(asc 排序还原执行时序) tcb logs search -q 'request_id:"abc123"' --sort asc -e my-env-id # 查看云托管日志 tcb logs search --service tcbr -q '"error" AND "timeout"' -e my-env-id # 指定绝对时间范围 tcb logs search -t "2026-03-10 14:00:00,2026-03-10 15:00:00" -e my-env-id # 获取下一页 tcb logs search -q 'function_name:"myFunc"' --context "eyJUb2tlb..." -e my-env-id # 输出 JSON,适合脚本或 AI 消费 tcb logs search --json -e my-env-id ``` ### CLS 检索语句格式 #### 云函数日志可用字段 | 字段 | 说明 | 示例 | |------|------|------| | `function_name` | 函数名称 | `function_name:"myFunc"` | | `request_id` | 本次调用唯一 ID | `request_id:"abc123"` | | `status_code` | 状态码,200=成功,202=中间态(需过滤)| `status_code>200 AND status_code!=202` | | `src` | 日志来源:`system`=系统日志,`app`=用户代码日志 | `src:app` | | `log` | 日志正文 | `log:"Error"` | | `namespace` | 环境 ID | `namespace:"my-env"` | | `qualifier` | 函数版本 | `qualifier:"$LATEST"` | | `retry_num` | 重试次数(过滤重试用 `retry_num=0`) | `retry_num=0` | #### 云托管日志可用字段(`--service tcbr`) | 字段 | 说明 | 示例 | |------|------|------| | `__CONTENT__` | 日志内容全文检索 | `"error" AND "timeout"` | | `__TAG__.container_name` | 服务名+实例,支持正则 | `/servername-[0-9]+/` | | `__TAG__.pod_name` | Pod 名称 | `__TAG__.pod_name:"xxx"` | #### 平台日志(`module:xxx` 筛选) | 模块 | 查询语句 | 适用场景 | |------|------|------| | 文档型数据库 | `module:database` | 数据库操作日志,`eventType:MongoSlowQuery` 筛慢查询 | | SQL 数据库 | `module:rdb` | `eventType:MysqlSlowQuery / MysqlFreeze / MysqlRecover` | | 数据模型 | `module:model` | — | | 审批流 | `module:workflow` | — | | 用户鉴权 | `module:auth` | 登录、鉴权事件 | | 大模型 | `module:llm AND logType:llm-tracelog` | AI 调用追踪 | | 应用部署 | `module:app` | `eventType:AppProdPub / AppProdDel` | | 网关访问 | `logType:accesslog` | HTTP 访问日志 | #### SQL 分析语法 CLS 支持通过管道符 `|` 追加 SQL 分析语句: ``` <检索条件> | select <字段> [where <条件>] [group by <字段>] [limit N] ``` **示例:统计各函数的失败次数** ``` src:app | select function_name, count(*) as fail_count where status_code>200 AND status_code!=202 group by function_name order by fail_count desc limit 20 ``` ### 时间范围格式 | 格式 | 示例 | 说明 | |------|------|------| | 相对时间 | `30m` / `6h` / `1d` | 最近 N 分钟/小时/天 | | 绝对时间段 | `"2026-03-10 14:00:00,2026-03-10 15:00:00"` | 起止时间,逗号分隔 | --- ## 典型查询场景 **场景 1:排查某函数今天的报错** ```bash tcb logs search \ -q 'function_name:"myFunc" AND status_code>200 AND status_code!=202' \ -t 24h \ --sort desc \ -e my-env-id ``` **场景 2:追踪一次调用的完整日志** ```bash # 先用 request_id 查,asc 排序还原时序 tcb logs search \ -q 'request_id:"abc-request-id-123"' \ --sort asc \ --limit 100 \ -e my-env-id ``` **场景 3:查数据库慢查询** ```bash tcb logs search \ -q 'module:database AND eventType:MongoSlowQuery' \ -t 24h \ -e my-env-id ``` **场景 4:查云托管服务异常** ```bash tcb logs search \ --service tcbr \ -q '"ERROR" AND __TAG__.container_name:"my-service"' \ -t 6h \ -e my-env-id ``` --- ## 与 AI 协作 `tcb logs search` 的 `--json` 输出经过结构化处理,适合大模型直接消费。推荐工作流: ```bash # 1. 让 AI 先通过 tcb docs 了解日志命令语法 tcb docs read "日志" # 2. 执行查询,输出给 AI 分析 tcb logs search -q 'function_name:"myFunc"' -t 6h --json -e my-env-id | \ # 将结果传给 AI 分析 ``` :::tip AI 生成命令的最佳实践 如果让大模型生成 `tcb` 命令,建议先通过 `tcb docs search <关键词>` 或 `tcb docs read <模块>` 让模型获取真实的命令签名,避免模型基于训练数据"猜测"命令而产生幻觉。 ::: --- # CloudBase CLI/可观测性/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/monitor/management --- title: 监控指标查询 --- # 监控指标查询 ::::info v3.5.8+ `tcb monitor` 命令自 v3.5.8 起提供。 :::: CloudBase CLI 提供统一的监控指标查询入口,支持云函数、文档型数据库、MySQL、云托管、静态网站托管、身份认证、大模型、知识库、网关、用户登录、DB Connector、环境 QPS 等全类型资源的监控数据查询。 --- ## 查看可用指标 Schema {#schema} 查看各资源类型支持的监控指标列表。 ```bash tcb monitor schema [resource] ``` - 不传 `resource` 则展示所有资源类型的可用指标 - 传 `resource` 则只展示该类型的指标 ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `resource` | 资源类型(可选),见下表 | — | ### 支持的资源类型 | 资源类型 | 说明 | |----------|------| | `function` | 云函数 | | `database` | 文档型数据库 | | `mysql` | MySQL 数据库 | | `cloudrun` | 云托管 | | `hosting` | 静态网站托管 | | `auth` | 身份认证 | | `ai` | 大模型 | | `knowledgebase` | 知识库 | | `gateway` | 网关 | | `userlogin` | 用户登录 | | `dbconnector` | DB Connector | | `env` | 环境 QPS | ### 示例 ```bash # 查看所有可用指标 tcb monitor schema # 查看云函数可用指标 tcb monitor schema function # 查看网关可用指标 tcb monitor schema gateway # 查看环境 QPS 可用指标 tcb monitor schema env ``` --- ## 云函数监控 {#function} 查询云函数的监控指标。 ```bash tcb monitor function [function-name] [options] ``` - 不传 `function-name` 则查询所有函数的概览指标 ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `function-name` | 云函数名称(可选) | — | | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询所有函数的概览指标 tcb monitor function # 查询 helloWorld 函数的调用次数 tcb monitor function helloWorld --metric-name FunctionInvocation # 查询最近 1 小时内 helloWorld 的多个指标 tcb monitor function helloWorld --since 1h --metric-name FunctionInvocation --metric-name FunctionFlux # 指定 1 小时统计周期,查询最近 7 天 tcb monitor function --since 7d --period 3600 # JSON 格式输出 tcb monitor function helloWorld --metric-name FunctionInvocation --json ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `FunctionCU` | 资源用量 | | `FunctionInvocation` | 调用次数 | | `FunctionFlux` | 外网出流量(KB) | | `FunctionThrottle` | 受限次数 | | `FunctionConcurrentExecutions` | 并发执行个数 | | `FunctionTimeout` | 函数执行超时次数 | | `FunctionGBs` | 资源用量(Mb*Ms) | | `FunctionError` | 错误次数 | | `FunctionDuration` | 运行时间(ms) | | `FunctionConcurrencyMemoryMB` | 并发执行内存量 | | `FunctionMemOverFlow` | 内存超限次数 | | `FunctionIdleProvisioned` | 预置并发闲置量 | | `FunctionProvisionedConcurrency` | 预置并发个数 | --- ## 文档型数据库监控 {#database} 查询文档型数据库(NoSQL)的监控指标。 ```bash tcb monitor database [collection-name] [options] ``` - 不传 `collection-name` 则查询所有集合的概览指标 ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `collection-name` | 集合名称(可选) | — | | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询所有集合的监控概览 tcb monitor database # 查询 users 集合的读请求数 tcb monitor database users --metric-name DbRead # 查询最近 1 小时内 users 集合的多个指标 tcb monitor database users --since 1h --metric-name DbRead --metric-name DbWrite # 指定 1 小时统计周期 tcb monitor database --since 7d --period 3600 ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `DbRead` | 读请求数 | | `DbWrite` | 写请求数 | | `DbCostTime10ms` | 耗时 10ms-50ms 请求数 | | `DbCostTime50ms` | 耗时 50ms-100ms 请求数 | | `DbCostTime100ms` | 耗时 100ms 以上请求数 | | `DbSizepkg` | 容量(MB) | --- ## MySQL 数据库监控 {#mysql} 查询 MySQL 数据库的监控指标。 ```bash tcb monitor mysql [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询 MySQL 所有监控指标 tcb monitor mysql # 查询 MySQL 存储容量 tcb monitor mysql --metric-name MysqlStorageUsage # 查询最近 1 天的 CCU tcb monitor mysql --metric-name MysqlCCU --since 1d # 查询多个指标 tcb monitor mysql --metric-name MysqlQps --metric-name MysqlSlowQueries # 指定 1 小时统计周期 tcb monitor mysql --since 7d --period 3600 ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `MysqlStorageUsage` | 容量(MB) | | `MysqlCCU` | CCU | | `MysqlCpuUsageRate` | CPU 利用率 | | `MysqlDbConnections` | 连接数 | | `MysqlMemoryUse` | 内存使用量(MB) | | `MysqlSlowQueries` | 慢查询数 | | `MysqlTps` | 提交数 | | `MysqlQps` | QPS | --- ## 云托管监控 {#cloudrun} 查询云托管服务的监控指标。 ```bash tcb monitor cloudrun [options] ``` - `service-name` 为必填参数,指定云托管服务名称 ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `service-name` | 云托管服务名称(必填) | — | | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--service-version ` | 指定服务版本,查询该版本的监控数据 | — | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询 my-service 服务的所有监控指标 tcb monitor cloudrun my-service # 查询服务的 QPS tcb monitor cloudrun my-service --metric-name TkeQPSService # 查询最近 1 天的 CPU 使用量 tcb monitor cloudrun my-service --metric-name TkeCpuUsedService --since 1d # 查询多个指标 tcb monitor cloudrun my-service --metric-name TkeQPSService --metric-name TkeRspTimeService # 指定版本查询 tcb monitor cloudrun my-service --service-version my-service-001 # 指定 1 小时统计周期 tcb monitor cloudrun my-service --since 7d --period 3600 ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `TkeRspTimeService` | 响应时间(ms) | | `TkeCpuUsedService` | CPU 使用量 | | `TkeMemUsedService` | 内存使用量 | | `TkeQPSService` | QPS | | `TkePodNumService` | 实例个数 | | `TkeHttpServiceNatPkg` | 外网出流量(byte) | | `TkeCUUsedService` | 内存使用量(CU) | | `TkeInvokeNumService` | 调用量 | | `TkeHttpErrorService` | 错误响应(404、500 等) | --- ## 静态网站托管监控 {#hosting} 查询静态网站托管的监控指标。 ```bash tcb monitor hosting [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询静态托管所有监控指标 tcb monitor hosting # 查询流量指标 tcb monitor hosting --metric-name StaticFsFluxPkg # 查询存储容量 tcb monitor hosting --metric-name StaticFsSizePkg # 查询最近 7 天的数据 tcb monitor hosting --since 7d --period 86400 ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `StaticFsFluxPkg` | 流量(byte) | | `StaticFsSizePkg` | 存储容量(MB) | --- ## 身份认证监控 {#auth} 查询身份认证的监控指标。 ```bash tcb monitor auth [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询身份认证调用次数 tcb monitor auth # 查询最近 7 天的调用次数 tcb monitor auth --since 7d --period 86400 # JSON 格式输出 tcb monitor auth --json ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `AuthInvocationNumPkg` | 调用次数 | --- ## 大模型监控 {#ai} 查询大模型(AI)的监控指标。 ```bash tcb monitor ai [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询大模型所有监控指标 tcb monitor ai # 查询输入 Token 用量 tcb monitor ai --metric-name AIPromptTokenNumPkg # 查询输出 Token 用量 tcb monitor ai --metric-name AICompletionTokenNumPkg # 查询最近 7 天的 Token 用量 tcb monitor ai --since 7d --period 86400 ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `AIPromptTokenNumPkg` | 输入 Token | | `AICompletionTokenNumPkg` | 输出 Token | | `AITotalTokenNumPkg` | 总 Token | --- ## 知识库监控 {#knowledgebase} 查询知识库的监控指标。 ```bash tcb monitor knowledgebase [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询知识库容量 tcb monitor knowledgebase # 查询最近 7 天的容量变化 tcb monitor knowledgebase --since 7d --period 86400 # JSON 格式输出 tcb monitor knowledgebase --json ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `KnowledgeBaseCapacity` | 容量(bytes) | --- ## 网关监控 {#gateway} 查询 HTTP 网关层的服务调用监控指标。通过位置参数 `dimension` 区分查询维度:`api`(API 调用链路)和 `resource`(HTTP 请求资源链路)。 ```bash tcb monitor gateway [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `dimension` | 查询维度(必填),可选值:`api` / `resource` | — | | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询当前维度所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--api-type ` | 当 `dimension=api` 时必填,API 类型:`wx`(小程序 API)或 `http_openapi`(云开发 API)。不支持与 `--resource-id` 同时使用 | — | | `--resource-id ` | 当 `dimension=resource` 时必填,资源标识符,格式为 `域名\|:\|路由\|:\|资源对象`。不支持与 `--api-type` 同时使用 | — | | `--json` | 输出 JSON,适合脚本消费 | — | ::::warning 维度参数约束 - 当 `dimension=api` 时,`--api-type` 必填,不支持 `--resource-id` - 当 `dimension=resource` 时,`--resource-id` 必填,不支持 `--api-type` :::: ### 示例 ```bash # 查询 API 调用链路(api 维度)— 小程序 API tcb monitor gateway api --api-type wx # 查询 API 调用链路 — 云开发 API tcb monitor gateway api --api-type http_openapi # 查询最近 6 小时的 API 调用链路 tcb monitor gateway api --api-type http_openapi --since 6h # 查询资源链路(resource 维度) tcb monitor gateway resource --resource-id "example.com|:|/api/users|:|cloudfunction" # JSON 格式输出 tcb monitor gateway api --api-type wx --json ``` ### 可用指标(api 维度) | 指标名 | 说明 | |--------|------| | `GatewayShowAPIInvokeNum` | 调用次数 | | `GatewayShowAPICostTotal` | 请求平均耗时 | | `GatewayShowAPIHTTP2XX` | HTTP 2xx 请求数 | | `GatewayShowAPIHTTPError` | HTTP ≥400 请求数 | ### 可用指标(resource 维度) | 指标名 | 说明 | |--------|------| | `GatewayShowHttpInvokeNum` | 调用次数 | | `GatewayShowHttpCostTotal` | 请求平均耗时 | | `GatewayShowHttpHTTP2XX` | HTTP 2xx 请求数 | | `GatewayShowHttpHTTPError` | HTTP ≥400 请求数 | --- ## 用户登录监控 {#userlogin} 查询用户登录的监控指标。 ```bash tcb monitor userlogin [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询用户登录所有监控指标 tcb monitor userlogin # 查询全部用户登录日活 tcb monitor userlogin --metric-name DayActiveLoginAllUser # 查询最近 1 天的匿名用户登录日活 tcb monitor userlogin --metric-name DayActiveLoginAnonymousUser --since 1d # JSON 格式输出 tcb monitor userlogin --json ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `DayActiveLoginAnonymousUser` | 匿名用户登录日活 | | `DayActiveLoginAllUser` | 全部用户登录日活 | | `DayActiveLoginExternalUser` | 外部用户登录日活 | | `DayActiveLoginInternalUser` | 内部用户登录日活 | --- ## DB Connector 监控 {#dbconnector} 查询 DB Connector(数据库连接器)的监控指标。 ```bash tcb monitor dbconnector [options] ``` - `connector-id` 为必填参数,指定连接器 ID ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `connector-id` | 连接器 ID(必填) | — | | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询 my-connector 的所有监控指标 tcb monitor dbconnector my-connector-id # 查询 my-connector 的读请求数 tcb monitor dbconnector my-connector-id --metric-name MongoConnectorRead # 查询最近 30 分钟的调用次数 tcb monitor dbconnector my-connector-id --metric-name MongoConnectorInvokeNum --since 30m # JSON 格式输出 tcb monitor dbconnector my-connector-id --json ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `MongoConnectorRead` | 读请求数 | | `MongoConnectorWrite` | 写请求数 | | `MongoConnectorCostTime10ms` | 耗时 10ms-50ms 请求数 | | `MongoConnectorCostTime50ms` | 耗时 50ms-100ms 请求数 | | `MongoConnectorCostTime100ms` | 耗时 100ms 以上请求数 | | `MongoConnectorInvokeNum` | 调用次数 | --- ## 环境 QPS 监控 {#env} 查询环境 QPS 的监控指标。 ```bash tcb monitor env [options] ``` ### 参数 | 参数 | 说明 | 默认值 | |------|------|--------| | `-e, --env-id ` | 环境 ID(不传则交互式选择) | — | | `--since ` | 起始时间,支持相对时间格式(如 `30s`、`30m`、`1h`、`2d`) | `1h` | | `--until ` | 结束时间,支持相对时间格式(如 `now`、`5m`) | `now` | | `--metric-name ` | 指定查询的指标名称,不传则查询所有指标。多个指标可用逗号分隔或重复指定 | 全部指标 | | `--period ` | 统计周期(秒):`300`(5分钟)、`3600`(1小时)、`86400`(1天),不传则自动选择 | 自动 | | `--json` | 输出 JSON,适合脚本消费 | — | ### 示例 ```bash # 查询环境 QPS tcb monitor env # 查询环境总 QPS tcb monitor env --metric-name EnvQPSAll # 查询最近 1 小时的环境 QPS tcb monitor env --since 1h # 指定 1 小时统计周期 tcb monitor env --since 7d --period 3600 # JSON 格式输出 tcb monitor env --json ``` ### 可用指标 | 指标名 | 说明 | |--------|------| | `EnvQPSAll` | 环境总 QPS | --- ## 相关命令 - [`tcb logs search`](../logs/search) — 日志检索 - [`tcb env list`](../envs/basement) — 查看可用环境 --- # CloudBase CLI/可观测性/未命名文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/monitor/alert --- title: 平台告警 --- # 平台告警 CloudBase 平台告警功能基于腾讯云可观测平台(Tencent Cloud Observability Platform, TCOP)的告警策略体系,当云开发环境的资源出现异常(如云函数错误率升高、数据库慢查询激增、云托管 5xx 错误等)时自动触发告警通知。 ## 通用云 API 调用 平台告警相关的高级操作(如创建/修改/删除告警策略、查询告警历史等)可通过腾讯云可观测平台 API 完成。 请参考 [通用云 API 调用](../api) 文档,使用 `tcb api monitor ` 调用以下接口: - [创建告警策略](https://cloud.tencent.com/document/api/248/51287)(`CreateAlarmPolicy`) - 其他告警相关接口可在 [腾讯云可观测平台 API 文档](https://cloud.tencent.com/document/api/248/87588) 中查找 ### 示例:创建云服务器告警策略 以下示例创建一个云服务器监控告警策略,包含指标触发条件和事件触发条件,并绑定通知规则和触发任务: ```bash tcb api monitor CreateAlarmPolicy \ --body '{ "Module": "monitor", "PolicyName": "云服务器告警策略", "Remark": "这是一句备注", "MonitorType": "MT_QCE", "Enable": 1, "ProjectId": 0, "Namespace": "cvm_device", "Condition": { "IsUnionRule": 1, "Rules": [ { "MetricName": "CvmDiskUsage", "Period": 60, "Operator": "ge", "Value": "99.9", "ContinuePeriod": 1, "NoticeFrequency": 3600, "IsPowerNotice": 0 } ] }, "EventCondition": { "Rules": [ { "MetricName": "ping_unreach" } ] }, "NoticeIds": [ "notice-bv9b4ghqbg", "notice-gj2n9wnt29" ], "TriggerTasks": [ { "Type": "AS", "TaskConfig": "{\"Region\":\"ap-guangzhou\",\"Group\":\"asg-0zhspjx\",\"Policy\":\"asp-ganig28\"}" } ] }' ``` 完整参数定义请参考 [CreateAlarmPolicy 接口文档](https://cloud.tencent.com/document/api/248/51287)。 --- # CloudBase CLI/AI ✨ /CloudBase AI CLI 使用文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/ai/introduce ## 简介 CloudBase AI CLI 是一个集成多种主流 AI 编程工具的统一命令行工具,支持内置模型和自定义模型 让你能够通过一个简单的命令使用 CodeBuddy Code、Claude Code、OpenAI Codex、aider、Qwen Code 等 AI 编程助手 同时内置[CloudBase MCP](https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/),支持从开发到部署的完整流程,支持在任意环境中运行 ![](http://static.cloudbase.net/imgs/cli-better.gif) ## 核心优势 ```mermaid graph LR A["统一管理"] --> B["多模型支持"] B --> C["一键开发部署"] C --> D["无处不在"] E["CodeBuddy Code
Claude Code
OpenAI Codex
aider
Qwen Code"] -.-> A style A fill:#e3f2fd style B fill:#e3f2fd style C fill:#e3f2fd style D fill:#e3f2fd style E fill:#fff3e0 ``` - 🏗️ **统一管理** - 一个命令管理多种 AI 编程 CLI 工具,无需在多个工具间切换 - 🤖 **多模型支持** - 支持内置和自定义各种大模型,包括 Kimi K2、智谱 GLM-4.5 等 - 🚀 **一键开发部署** - 从代码生成到云端部署的完整流程,支持 Web 应用、小程序、后端服务 - 🌍 **无处不在** - 可在任意环境中运行,包括小程序开发者工具、VS Code、GitHub Actions 等 ## 应用场景 ```mermaid graph LR subgraph "调用场景" A1[小程序开发者工具] A2[VS Code 终端] A3[GitHub Actions] A4[服务器环境] A5[本地开发环境] end subgraph "统一入口" B[tcb ai 命令] end subgraph "支持开发部署的应用" C1[Web 应用] C2[微信小程序] C3[后端服务] C4[UniApp应用] end A1 --> B A2 --> B A3 --> B A4 --> B A5 --> B B --> C1 B --> C2 B --> C3 B --> C4 style A1 fill:#e3f2fd style A2 fill:#e3f2fd style A3 fill:#e3f2fd style A4 fill:#e3f2fd style A5 fill:#e3f2fd style B fill:#fff3e0 style C1 fill:#e8f5e8 style C2 fill:#e8f5e8 style C3 fill:#e8f5e8 style C4 fill:#e8f5e8 ``` ## 快速开始 ### 1. 安装 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; **使用 npm/yarn/pnpm 安装:** ```sh npm i -g @cloudbase/cli ``` 或者使用 Yarn 1.x : ```sh yarn global add @cloudbase/cli ``` 或者 Pnpm: ```sh pnpm add -g @cloudbase/cli ``` 如果访问官方 npm 源有问题,一般安装过程中报类似 "ETIMEOUT"、"npm ERR! network request to https://registry.npmjs.org/@cloudbase%2fcli failed" 这种错误,这个时候需要临时指定一个国内 npm 镜像源: ```sh npm i -g @cloudbase/cli --registry=http://mirrors.cloud.tencent.com/npm/ ``` ```sh yarn global add @cloudbase/cli --registry=http://mirrors.cloud.tencent.com/npm/ ``` ### 2. 初始化 AI 配置 使用终端运行 CloudBase AI CLI: ```bash # 启动配置向导 tcb ai ``` 首次启动,配置向导会引导你完成: - 选择 AI 工具 - 完成 AI 工具的配置 完成配置后,即可开始使用 AI 工具进行辅助开发,后续可以运行 `tcb ai --setup` 来切换工具和模型。 ### 3. 开始体验 ```bash tcb ai # 或预设提示词 tcb ai -- "帮我创建一个订单管理系统" # 或使用非交互式模式 tcb ai -- -p "你好" # 查看所有可用命令 tcb ai --help ``` ## 详细开发指引 参考详细教程: [用 CloudBase AI CLI 开发邻里闲置物品循环利用小程序](/practices/ai-cli-mini-program) :::tip 立即体验 **到处皆可运行,从代码生成到云端部署!** 无需 API Key,新环境赠送 100 万 token,支持 DeepSeek、Kimi K2 等主流模型快速体验。 ::: ## 工作原理 ```mermaid graph TB A[开发者] --> B[tcb ai 命令] B --> I[AI 模型层
云开发内置模型 + 自定义模型] I --> C[统一管理层] C --> D[CodeBuddy Code] C --> E[Claude Code] C --> F[OpenAI Codex] C --> G[aider] C --> H[Qwen Code] C --> I[其他 AI CLI] D --> J[代码生成] E --> J F --> J G --> J H --> J I --> J C --> L[其他 AI CLI 工具] J --> K[云开发平台] K --> M[Web 应用] K --> N[微信小程序] K --> O[后端服务] style A fill:#e1f5fe style B fill:#fff3e0 style I fill:#f3e5f5 style C fill:#f3e5f5 style K fill:#e8f5e8 ``` ## 详细使用说明 ### 启动 AI 工具 ```bash # 使用默认配置的 AI 工具 tcb ai # 指定 AI 工具 tcb ai -a codebuddy tcb ai -a claude tcb ai -a codex tcb ai -a aider tcb ai -a qwen ``` ### 参数透传 使用 `--` 分隔符将参数直接传递给目标 AI 工具: ```bash # 传递参数给 CodeBuddy tcb ai -a codebuddy -- --mcp-server "config.json" # 传递参数给 Claude tcb ai -a claude -- -p hi # 相当于运行 codebuddy --mcp-server "config.json" 或 claude -p hi ``` ### 指定模板 在 AI 开发时直接指定模板: ```bash # 使用 AI 命令并下载模板 tcb ai --template rules tcb ai --template miniprogram tcb ai --template react ``` 支持的模板 - `miniprogram` - 微信小程序 + CloudBase - `react` - Web 应用 - React + CloudBase - `vue` - Web 应用 - Vue + CloudBase - `uniapp` - 跨端应用 - UniApp + CloudBase - `rules` - AI 规则和配置 - `none` - 不下载任何模板 ### 查看当前配置 ```bash tcb ai --config ``` ### 重新配置 ```bash # 重置所有配置 tcb ai --reset # 运行配置向导 tcb ai --setup ``` ## 支持的 AI 工具 | AI 工具 | 命令参数 | |---------|----------| | [CodeBuddy Code](https://codebuddy.co/) | `-a codebuddy` | | [Claude Code](https://www.anthropic.com/claude-code) | `-a claude` | | [Openai Codex](https://github.com/openai/codex) | `-a codex` | | [aider](https://aider.chat/) | `-a aider` | | [Qwen Code](https://github.com/QwenLM/qwen-code) | `-a qwen` | ## 获取帮助 ### 命令行帮助 ```bash # 查看命令帮助 tcb ai --help ``` ### 常见问题 遇到问题?查看 [CloudBase CLI 常见问题解答](../faq.md#ai-cli-相关) 获取详细帮助。 ### 技术支持 - **帮助中心**:[https://tcb.cloud.tencent.com/help-center](https://tcb.cloud.tencent.com/help-center) - 在线咨询、工单系统、技术文档 - **官方文档**:[https://docs.cloudbase.net](https://docs.cloudbase.net) - 完整的产品文档和使用指南 - **GitHub 仓库**:[CloudBase-AI-ToolKit](https://github.com/TencentCloudBase/CloudBase-AI-ToolKit) - 开源代码和问题反馈 ### 🔥 微信交流群 遇到问题或想要交流经验?加入我们的技术社区!
微信群二维码
扫码加入微信技术交流群
## 为什么选择 CLI? CLI 让 AI 编程能力变成可编程的基础设施,可以被脚本、自动化流程、CI/CD 系统调用,无需在多个 GUI 工具间切换,可以集成到现有开发工作流中 同时支持云原生开发、容器化部署、持续集成/持续部署(CI/CD),推动 AI 从"生成代码"到"全流程协作"的进化 ```mermaid graph TD A[AI IDE] --> B[独立应用程序] B --> C[GUI 界面] C --> D[手动操作] E[AI CLI] --> F[可编程基础设施] F --> G[脚本调用] G --> H[自动化流程] H --> I[CI/CD 集成] style A fill:#ffebee style E fill:#e8f5e8 style B fill:#ffebee style F fill:#e8f5e8 ``` ## 配置说明 当前 CloudBase AI CLI 支持以下 AI 工具的配置: - CodeBuddy Code - Claude Code - Openai Codex - aider - Qwen Code 所有工具都支持两种配置方式: - 配置自定义的 LLM:通常需要配置自己的 API KEY 和对应的 Base URL - 配置云开发环境中的 LLM:无需自行配置 API KEY,只需按照命令行提示登录并选择云开发环境即可 | AI 工具 | 自定义配置要求 | 云开发环境配置 | 相关文档 | |---------|--------------|--------------| ---- | | CodeBuddy Code | - API KEY
- 支持 OAuth 身份验证
- MCP 服务器配置
- 环境变量配置 | ✨ 一键登录,自动配置 | [配置文档](./codebuddy) | | Claude Code | - Base URL
- API KEY
- 需要配置为 Anthropic 兼容的模型
- 支持配置 Kimi-K2、智谱 GLM-4.5 | ✨ 一键登录,自动配置 | [配置文档](./claude) | | OpenAI Codex | - Base URL
- API KEY
- 模型名称
- 需要配置为 OpenAI 兼容的模型 | ✨ 一键登录,自动配置 | [配置文档](./codex) | | aider | - Base URL
- API KEY
- 模型名称
- 需要配置为 OpenAI 兼容的模型 | ✨ 一键登录,自动配置 | [配置文档](./aider) | | Qwen Code | - Base URL
- API KEY
- 模型名称
- 需要配置为 OpenAI 兼容的模型 | ✨ 一键登录,自动配置 | [配置文档](./qwen) | --- # CloudBase CLI/AI ✨ /支持的 AI CLI/CodeBuddy Code 配置文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/ai/codebuddy ## 概述 本文将说明如何使用 CloudBase AI CLI 配置 CodeBuddy Code 。 [Cloudbase AI CLI 介绍文档](./introduce) CloudBase AI CLI 支持以两种方式配置 CodeBuddy Code: - CodeBuddy 账号登录:使用 CodeBuddy 账号登录,支持 OAuth 身份验证 - API key配置:使用 CodeBuddy API key 登录 ## CodeBuddy 配置 运行: ```shell tcb ai --setup ``` 命令运行后,将会进入配置向导。 1. 选择当前要配置的 AI 编程助手,选择 `CodeBuddy Code` 2. 选择配置方式,选择 `使用 CodeBuddy 账号登录` 3. 根据提示登录您的 CodeBuddy 账号 4. 最后可选择默认要使用的 AI 编程助手,后续运行 `tcb ai` 时即可默认打开 ## 启动 CodeBuddy Code ```shell tcb ai -a codebuddy ``` :::tip 如果配置了默认使用 `CodeBuddy Code`,可以直接执行 `tcb ai` ::: ## 详细配置指引 ### 自定义配置 #### 配置 CodeBuddy API Key - API Key 填入您的 CodeBuddy API Key ### 环境变量配置 CodeBuddy Code 支持通过环境变量进行配置: ```bash export CODEBUDDY_API_KEY="your-api-key-here" ``` ### MCP 服务器配置 CodeBuddy Code 支持通过参数透传实现 MCP 命令管理: ```shell # 通过 CloudBase AI CLI 透传参数 tcb ai -a codebuddy -- --mcp-server "your-mcp-server-config" ``` ## 配置验证 CodeBuddy Code 的配置会自动进行验证,包括: - API Key 格式验证 - 网络连通性测试 - MCP 服务器配置检查 ## 故障排除 ### 常见问题 1. **API Key 无效** - 检查 API Key 是否正确 - 确认 API Key 是否有足够的使用权限 - 查看 CodeBuddy 账户状态 2. **网络连接问题** - 检查防火墙设置 - 确认网络能够访问 CodeBuddy 服务 - 尝试更换网络环境 3. **MCP 服务器配置错误** - 验证 MCP 服务器配置格式 - 检查服务器地址是否可访问 - 确认服务器认证信息 ### 日志查看 启用详细日志来排查问题: ```shell tcb ai -a codebuddy -- --verbose ``` ## 最佳实践 1. **API Key 安全管理** - 使用环境变量而不是硬编码 API Key - 定期轮换 API Key - 限制 API Key 的权限范围 2. **MCP 服务器管理** - 使用配置文件管理多个 MCP 服务器 - 定期检查服务器状态 - 备份重要的服务器配置 3. **性能优化** - 根据项目规模选择合适的模型 - 合理配置并发请求数 - 监控 API 使用量和费用 --- # CloudBase CLI/AI ✨ /支持的 AI CLI/Claude Code 配置文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/ai/claude ## 概述 本文将说明如何使用 CloudBase AI CLI 配置 Claude Code 。 [Cloudbase AI CLI 介绍文档](./introduce) CloudBase AI CLI 支持以两种方式配置 Claude Code: - 云开发配置:一键登录云开发,并选择云开发环境,使用云开发环境中的 Deepseek/Kimi 大模型 - 自定义配置:配置您的 Anthropic 兼容的 Base URL、API Key ## 云开发配置 运行: ```shell tcb ai --setup ``` 命令运行后,将会进入配置向导。 1. 选择当前要配置的 AI 编程助手,选择 `Claude Code` 2. 选择配置方式,选择 `使用 CloudBase 服务,一键登录,无需配置` 3. 根据提示,按需登录账号、选择云开发环境 4. 选择要使用的大模型供应商、模型名称,配置完成后会直接接入到在云开发平台上配置好的大模型,详情可参考 [大模型接入指南](../../ai/model/overview) 5. 最后可选择默认要使用的 AI 编程助手,后续运行 `tcb ai` 时即可默认打开 全部输入后即可完成配置。 ## 自定义配置 运行: ```shell tcb ai --setup ``` 命令运行后,将会进入配置向导。 1. 选择当前要配置的 AI 编程助手,选择 `Claude Code` 2. 选择配置方式,选择 `自配置 API KEY 和 Base URL` 3. 输入 Base URL、API KEY 4. 最后可选择默认要使用的 AI 编程助手,后续运行 `tcb ai` 时即可默认打开 全部输入后即可完成配置。 ## 启动 Claude Code ```shell tcb ai -a claude ``` :::tip 如果配置了默认使用 `Claude Code`,可以直接执行 `tcb ai` ::: ## 详细配置指引 ### 自定义配置 #### 配置 Kimi K2 参考 [自定义配置](#自定义配置) 小节,其中: - Base URL 选择预设的 Kimi Base URL - API KEY 填入您的 Kimi API KEY,可前往 [Kimi](https://platform.moonshot.cn/console/api-keys) 获取 #### 配置 智谱 glm-4.5 参考 [自定义配置](#自定义配置) 小节,其中: - Base URL 选择预设的智谱 Base URL - API KEY 填入您的 智谱 API KEY,可前往前往 [智谱 BigModel](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) 获取 ##### 参考文档 - https://docs.bigmodel.cn/cn/guide/develop/claude#claude-code #### 配置 Claude 参考 [自定义配置](#自定义配置) 小节,其中: - Base URL 选择自定义 URL,输入 `https://api.anthropic.com` - API KEY 填入您的 Anthropic API KEY,可前往前往 [Anthropic](https://console.anthropic.com/) 获取 ##### 参考文档 - https://docs.anthropic.com/en/docs/claude-code/iam --- # CloudBase CLI/AI ✨ /支持的 AI CLI/Codex 配置文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/ai/codex ## 概述 本文将说明如何使用 CloudBase AI CLI 配置 OpenAI Codex 。 [Cloudbase AI CLI 介绍文档](./introduce) CloudBase AI CLI 支持以两种方式配置 OpenAI Codex: - 云开发配置:一键登录云开发,并选择云开发环境,使用云开发环境中的 Deepseek/Kimi 大模型 - 自定义配置:配置您的 OpenAI 兼容的 Base URL、API Key ## 云开发配置 运行: ```shell tcb ai --setup ``` 命令运行后,将会进入配置向导。 1. 选择当前要配置的 AI 编程助手,选择 `OpenAI Codex` 2. 选择配置方式,选择 `使用 CloudBase 服务,一键登录,无需配置` 3. 根据提示,按需登录账号、选择云开发环境 4. 选择要使用的大模型供应商、模型名称,配置完成后会直接接入到在云开发平台上配置好的大模型,详情可参考 [大模型接入指南](../../ai/model/overview) 5. 最后可选择默认要使用的 AI 编程助手,后续运行 `tcb ai` 时即可默认打开 全部输入后即可完成配置。 ## 自定义配置 运行: ```shell tcb ai --setup ``` 命令运行后,将会进入配置向导。 1. 选择当前要配置的 AI 编程助手,选择 `OpenAI Codex` 2. 选择配置方式,选择 `自配置 API KEY 和 Base URL` 3. 输入 Base URL、API KEY、模型名称 4. 最后可选择默认要使用的 AI 编程助手,后续运行 `tcb ai` 时即可默认打开 全部输入后即可完成配置。 ## 启动 OpenAI Codex ```shell tcb ai -a codex ``` :::tip 如果配置了默认使用 `OpenAI Codex`,可以直接执行 `tcb ai` ::: ## 详细配置指引 ### 自定义配置 #### 配置 Kimi K2 参考 [自定义配置](#自定义配置) 小节,其中: - Base URL 选择预设的 Kimi Base URL - API KEY 填入您的 Kimi API KEY,可前往 [Kimi](https://platform.moonshot.cn/console/api-keys) 获取 - 模型名称填入 `kimi-k2-0711-preview` #### 配置 智谱 glm-4.5 参考 [自定义配置](#自定义配置) 小节,其中: - Base URL 选择预设的智谱 Base URL - API KEY 填入您的 智谱 API KEY,可前往前往 [智谱 BigModel](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) 获取 - 模型名称填入 `glm-4.5` --- # CloudBase CLI/AI ✨ /支持的 AI CLI/aider 配置文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/ai/aider ## 概述 本文将说明如何使用 CloudBase AI CLI 配置 aider 。 [Cloudbase AI CLI 介绍文档](./introduce) CloudBase AI CLI 支持以两种方式配置 aider: - 云开发配置:一键登录云开发,并选择云开发环境,使用云开发环境中的 Deepseek/Kimi 大模型 - 自定义配置:配置您的 OpenAI 兼容的 Base URL、API Key ## 云开发配置 运行: ```shell tcb ai --setup ``` 命令运行后,将会进入配置向导。 1. 选择当前要配置的 AI 编程助手,选择 `aider` 2. 选择配置方式,选择 `使用 CloudBase 服务,一键登录,无需配置` 3. 根据提示,按需登录账号、选择云开发环境 4. 选择要使用的大模型供应商、模型名称,配置完成后会直接接入到在云开发平台上配置好的大模型,详情可参考 [大模型接入指南](../../ai/model/overview) 5. 最后可选择默认要使用的 AI 编程助手,后续运行 `tcb ai` 时即可默认打开 全部输入后即可完成配置。 ## 自定义配置 运行: ```shell tcb ai --setup ``` 命令运行后,将会进入配置向导。 1. 选择当前要配置的 AI 编程助手,选择 `aider` 2. 选择配置方式,选择 `自配置 API KEY 和 Base URL` 3. 输入 Base URL、API KEY、模型名称 4. 最后可选择默认要使用的 AI 编程助手,后续运行 `tcb ai` 时即可默认打开 全部输入后即可完成配置。 ## 启动 aider ```shell tcb ai -a aider ``` :::tip 如果配置了默认使用 `aider`,可以直接执行 `tcb ai` ::: ## 详细配置指引 ### 自定义配置 #### 配置 Kimi K2 参考 [自定义配置](#自定义配置) 小节,其中: - Base URL 选择预设的 Kimi Base URL - API KEY 填入您的 Kimi API KEY,可前往 [Kimi](https://platform.moonshot.cn/console/api-keys) 获取 - 模型名称填入 `kimi-k2-0711-preview` #### 配置 智谱 glm-4.5 参考 [自定义配置](#自定义配置) 小节,其中: - Base URL 选择预设的智谱 Base URL - API KEY 填入您的 智谱 API KEY,可前往前往 [智谱 BigModel](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) 获取 - 模型名称填入 `glm-4.5` --- # CloudBase CLI/AI ✨ /支持的 AI CLI/Cursor CLI 配置文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/ai/cursor ## 概述 本文将说明如何使用 CloudBase AI CLI 配置 Cursor CLI 。 [Cloudbase AI CLI 介绍文档](./introduce) CloudBase AI CLI 目前只支持以 Cursor 内置鉴权方式配置 Cursor CLI: ## 配置 运行: ```shell tcb ai --setup ``` 1. 命令运行后,将会进入配置向导。 2. 选择当前要配置的 AI 编程助手,选择 `Cursor CLI` 即可 ## 启动 Cursor CLI ```shell tcb ai -a cursor ``` :::tip 如果配置了默认使用 `cursor`,可以直接执行 `tcb ai` ::: --- # CloudBase CLI/AI ✨ /支持的 AI CLI/Qwen Code 配置文档 > 当前文档链接: https://docs.cloudbase.net/cli-v1/ai/qwen ## 概述 本文将说明如何使用 CloudBase AI CLI 配置 Qwen Code 。 [Cloudbase AI CLI 介绍文档](./introduce) CloudBase AI CLI 支持以两种方式配置 Qwen Code: - 云开发配置:一键登录云开发,并选择云开发环境,使用云开发环境中的 Deepseek/Kimi 大模型 - 自定义配置:配置您的 OpenAI 兼容的 Base URL、API Key ## 云开发配置 运行: ```shell tcb ai --setup ``` 命令运行后,将会进入配置向导。 1. 选择当前要配置的 AI 编程助手,选择 `Qwen Code` 2. 选择配置方式,选择 `使用 CloudBase 服务,一键登录,无需配置` 3. 根据提示,按需登录账号、选择云开发环境 4. 选择要使用的大模型供应商、模型名称,配置完成后会直接接入到在云开发平台上配置好的大模型,详情可参考 [大模型接入指南](../../ai/model/overview) 5. 最后可选择默认要使用的 AI 编程助手,后续运行 `tcb ai` 时即可默认打开 全部输入后即可完成配置。 ## 自定义配置 运行: ```shell tcb ai --setup ``` 命令运行后,将会进入配置向导。 1. 选择当前要配置的 AI 编程助手,选择 `Qwen Code` 2. 选择配置方式,选择 `自配置 API KEY 和 Base URL` 3. 输入 Base URL、API KEY、模型名称 4. 最后可选择默认要使用的 AI 编程助手,后续运行 `tcb ai` 时即可默认打开 全部输入后即可完成配置。 ## 启动 Qwen Code ```shell tcb ai -a qwen ``` :::tip 如果配置了默认使用 `Qwen Code`,可以直接执行 `tcb ai` ::: ## 详细配置指引 ### 自定义配置 #### 配置 Qwen 参考 [自定义配置](#自定义配置) 小节,其中: - Base URL 使用默认配置即可 - API KEY 填入您的 百炼 API KEY,可前往 [阿里云百炼](https://bailian.console.aliyun.com/) 获取 - 输入要使用的模型名称 ##### 参考文档 - https://github.com/QwenLM/qwen-code?tab=readme-ov-file --- # CloudBase CLI/其他参考/全局选项 > 当前文档链接: https://docs.cloudbase.net/cli-v1/global-options 全局选项适用于所有 CloudBase CLI 命令,在 `bin/tcb.js` 中通过 `program.option()` 注册,无需在各命令中重复声明。 ## 选项列表 ### 帮助信息 ```bash -h, --help ``` 查看当前命令的帮助信息。 v3 版本的 `--help` 输出相比旧版更完整,也更适合作为命令发现入口。实际使用时,通常可以按“顶层命令 → 子命令 → 具体动作”的路径逐级下钻,快速确认真实可用的命令结构。 从输出结构上看,新版帮助信息通常会同时覆盖几类关键信息: - 用法(Usage) - 参数说明(Options) - 示例(Examples) - 相关命令(Related Commands) - 对应文档链接(Docs) 这意味着 `--help` 不再只是“参数速查”,而更接近命令的自解释说明。它的价值不只在于信息更多,而在于输出顺序本身已经带有引导性:先告诉您命令怎么写,再告诉您关键参数怎么选,接着补充示例、相关命令和文档,减少在帮助、文档、搜索结果之间来回切换。对于以下场景尤其有价值: - 首次使用某个命令时快速确认正确语法 - 在终端里直接查示例,减少来回翻文档 - 在脚本或 CI 编写前先确认参数签名 - 让 AI 在生成命令前先读取真实帮助信息,降低命令幻觉 **示例**: ```bash # 先看顶层命令结构 tcb --help # 再下钻到具体子命令 tcb fn deploy --help # 最后查看具体动作的参数和示例 tcb logs search --help ``` 例如 `tcb fn deploy --help` 这类输出,会把命令写法、关键参数、示例、相关命令和文档链接放在一起。开发者可以直接确认“这条命令怎么写”,AI 也可以先读帮助再生成命令,减少猜命令和试错。 ```bash $ tcb fn deploy --help Usage: tcb fn deploy [options] [name] Options: --force --dir --runtime --json 示例: tcb fn deploy hello tcb fn deploy hello --force tcb fn deploy --all 相关命令: tcb fn invoke tcb fn log 文档: https://docs.cloudbase.net/cli-v1/functions/deploy ``` :::tip AI 协作建议 如果您会让大模型帮您生成 `tcb` 命令,建议先执行一次对应命令的 `--help`,或者配合 `tcb docs` 一起使用。相比仅依赖模型记忆,读取实时帮助信息更可靠。 ::: --- ### 环境 ID ```bash -e, --env-id ``` 指定云开发环境 ID。 **优先级**(高到低): 1. 命令行参数 `--env-id` 2. 配置文件 `cloudbaserc.json` 中的 `envId` 字段 3. 交互式选择(如适用) **示例**: ```bash # 部署函数到指定环境 tcb fn deploy app --env-id my-env-123 # 查询环境下的数据库集合 tcb db list --env-id my-env-123 ``` :::tip 兼容性说明 旧版参数 `--envId` 已废弃,但仍然可用(在帮助中隐藏)。推荐使用 `--env-id`。 ::: --- ### 地域 ```bash -r, --region ``` 指定环境所在地域。支持的地域:`ap-shanghai`、`ap-beijing`、`ap-guangzhou`、`ap-singapore`。 **示例**: ```bash # 查询广州地域的环境列表 tcb env list --region ap-guangzhou # 部署函数到北京地域环境 tcb fn deploy app --region ap-beijing --env-id bj-env-123 ``` **多地域错误提示**: 当环境不在默认地域(上海)时,CLI 会提示切换地域并自动重试。 --- ### JSON 输出 ```bash --json ``` 以 JSON 格式输出结果,便于脚本和 AI 解析。 **行为**: - 抑制所有非 JSON 内容(版本提示、进度条、彩色输出等) - 确保 `stdout` 仅包含可解析的 JSON - 错误信息也以 JSON 格式输出:`{ "error": { "code": "...", "message": "...", "exit_code": N } }` **示例**: ```bash # 获取函数列表(JSON 格式) tcb fn list --json # 查询环境信息并用 jq 解析 tcb env list --json | jq '.data[0].EnvId' ``` --- ### 非交互模式 ```bash -y, --yes ``` 跳过所有确认提示,对所有询问自动选择默认选项(通常为"是")。 **适用场景**: - CI/CD 流水线 - 自动化脚本 - 批量操作 **示例**: ```bash # 强制删除函数,跳过确认 tcb fn delete app --yes # 批量部署,不询问 tcb fn deploy --yes ``` --- ### 详细输出 ```bash --verbose ``` 打印内部运行信息,包括: - 详细的错误栈 - API 请求参数 - 中间步骤日志 **用途**: - 调试 CLI 行为 - 排查意外错误 - 提交 Bug 报告 **示例**: ```bash # 调试函数部署失败原因 tcb fn deploy app --verbose ``` --- ### 配置文件路径 ```bash --config-file ``` 指定配置文件路径,默认为当前目录下的 `cloudbaserc.json`。 **示例**: ```bash # 使用自定义配置文件 tcb fn deploy --config-file ./configs/prod.json # 使用上级目录的配置 tcb hosting deploy --config-file ../cloudbaserc.json ``` --- ### 环境模式 ```bash --mode ``` 指定加载 `.env` 文件的环境模式(如 `development`、`production`)。 **用途**: - 根据环境加载不同的配置 - 与前端构建工具(Vite、Next.js 等)配合使用 **示例**: ```bash # 开发模式部署 tcb app deploy --mode development # 生产模式部署 tcb app deploy --mode production ``` --- ## 组合使用 全局选项可以自由组合: ```bash # 非交互 + JSON 输出(适合 CI/CD) tcb fn list --yes --json --env-id my-env # 详细日志 + 指定地域(排查问题) tcb fn deploy app --verbose --region ap-guangzhou --env-id gz-env-123 # 自定义配置 + 环境模式(本地开发) tcb app deploy --config-file ./dev-config.json --mode development ``` --- ## CI/CD 最佳实践 ### 1. 使用 `--json` + `--yes` 确保幂等性 ```bash # GitHub Actions 示例 - name: Deploy function run: | tcb fn deploy app --env-id ${{ secrets.ENV_ID }} --json --yes ``` ### 2. 解析 JSON 输出提取信息 ```bash # 提取函数列表中的第一个函数名 FUNC_NAME=$(tcb fn list --json | jq -r '.data[0].FunctionName') echo "Latest function: $FUNC_NAME" ``` ### 3. 错误处理 ```bash # 捕获错误码 tcb fn deploy app --json || EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then echo "Deployment failed with exit code $EXIT_CODE" exit $EXIT_CODE fi ``` --- ## 常见问题 ### Q: `--env-id` 和 `--envId` 有什么区别? A: 功能完全相同。`--envId` 是旧版参数,已废弃但仍可用。推荐使用 `--env-id`(符合 kebab-case 命名规范)。 ### Q: 如何在 CI/CD 中避免交互式提示? A: 使用 `--yes` 参数跳过所有确认提示: ```bash tcb fn delete old-func --yes ``` ### Q: `--json` 输出的错误格式是什么? A: 错误信息以 JSON 格式输出: ```json { "error": { "code": "FUNCTION_NOT_FOUND", "message": "函数 app 不存在", "exit_code": 4 } } ``` ### Q: 如何指定多个全局选项? A: 全局选项的顺序无关紧要,可以任意组合: ```bash tcb fn deploy app --json --yes --verbose --env-id my-env --region ap-guangzhou ``` --- ## 参考资料 - [CLI 安装与快速开始](./install) - [退出码](./exit-codes.md) - [多地域使用](./region.md) - [配置文件](./config.md) --- # CloudBase CLI/其他参考/退出码 > 当前文档链接: https://docs.cloudbase.net/cli-v1/exit-codes CloudBase CLI 使用结构化退出码(Exit Codes)来表示命令执行结果,便于 CI/CD 流水线、脚本和自动化工具进行错误处理和重试决策。 ## 为什么需要退出码? 退出码是 Unix/Linux 进程的标准机制,用于告诉调用者命令是否成功以及失败原因的分类: ### 1. CI/CD 流程控制 ```yaml # GitHub Actions 示例 - name: Deploy function run: tcb fn deploy app --env-id ${{ secrets.ENV_ID }} continue-on-error: false # 非 0 退出码会中断流水线 ``` ### 2. Shell 脚本错误处理 ```bash #!/bin/bash set -e # 遇到非 0 退出码立即退出 tcb fn deploy app --env-id my-env if [ $? -eq 2 ]; then echo "认证失败,请重新登录" tcb login fi ``` ### 3. 条件执行和重试 ```bash # 仅当函数部署成功时,才部署静态资源 tcb fn deploy && tcb hosting deploy # 认证失败时自动重试登录 tcb fn list || (tcb login && tcb fn list) ``` ### 4. 自动化测试 ```bash # 测试脚本验证 CLI 行为 tcb fn delete non-existent-func EXIT_CODE=$? if [ $EXIT_CODE -eq 4 ]; then echo "✅ 资源不存在错误码正确" else echo "❌ 期望退出码 4,实际 $EXIT_CODE" exit 1 fi ``` --- ## 退出码定义 | 退出码 | 语义 | 触发场景 | 建议处理方式 | |--------|------|----------|-------------| | **0** | 成功 | 命令正常执行完成 | 继续下一步 | | **1** | 通用错误 | 未分类的错误(兜底) | 查看错误信息,手动排查 | | **2** | 认证失败 | 未登录、Token 过期、无权限 | 执行 `tcb login` 重新登录 | | **3** | 参数错误 | 缺少必填参数、格式错误、枚举值非法 | 检查命令参数,参考 `--help` | | **4** | 资源不存在 | 函数名/环境 ID 无效、集合不存在等 | 确认资源名称是否正确 | | **5** | 云 API 错误 | CloudBase API 返回错误、网络超时 | 检查网络连接,查看 `--verbose` 日志 | | **6** | 本地文件错误 | `cloudbaserc.json` 缺失/损坏、路径不存在 | 检查配置文件和路径 | --- ## 使用场景 ### 场景 1: 条件执行 ```bash # 仅当函数部署成功时,才部署静态资源 tcb fn deploy app && tcb hosting deploy ``` ### 场景 2: 错误捕获与处理 ```bash #!/bin/bash tcb fn deploy app --env-id my-env EXIT_CODE=$? case $EXIT_CODE in 0) echo "✅ 部署成功" ;; 2) echo "❌ 认证失败,请重新登录" tcb login ;; 3) echo "❌ 参数错误,请检查命令" exit 1 ;; 4) echo "❌ 资源不存在" exit 1 ;; 5) echo "⚠️ 云 API 错误,5 秒后重试..." sleep 5 tcb fn deploy app --env-id my-env ;; *) echo "❌ 未知错误(退出码 $EXIT_CODE)" exit 1 ;; esac ``` ### 场景 3: 重试机制 ```bash #!/bin/bash MAX_RETRIES=3 RETRY_DELAY=5 for i in $(seq 1 $MAX_RETRIES); do tcb fn deploy app --env-id my-env EXIT_CODE=$? if [ $EXIT_CODE -eq 0 ]; then echo "✅ 部署成功" exit 0 elif [ $EXIT_CODE -eq 5 ]; then echo "⚠️ 云 API 错误,$RETRY_DELAY 秒后重试(第 $i/$MAX_RETRIES 次)" sleep $RETRY_DELAY else echo "❌ 不可重试的错误(退出码 $EXIT_CODE)" exit $EXIT_CODE fi done echo "❌ 重试 $MAX_RETRIES 次后仍失败" exit 1 ``` ### 场景 4: 批量操作 ```bash #!/bin/bash FUNCTIONS=("func1" "func2" "func3") FAILED=() for func in "${FUNCTIONS[@]}"; do tcb fn deploy "$func" --yes if [ $? -ne 0 ]; then FAILED+=("$func") fi done if [ ${#FAILED[@]} -gt 0 ]; then echo "❌ 以下函数部署失败:" printf '%s\n' "${FAILED[@]}" exit 1 else echo "✅ 所有函数部署成功" fi ``` ### 场景 5: Makefile ```makefile .PHONY: deploy test clean deploy: @echo "部署函数..." @tcb fn deploy app --env-id $(ENV_ID) @echo "部署静态资源..." @tcb hosting deploy --env-id $(ENV_ID) test: @echo "运行测试..." @tcb fn invoke app --env-id $(ENV_ID) --data '{"test": true}' @if [ $$? -eq 0 ]; then \ echo "✅ 测试通过"; \ else \ echo "❌ 测试失败"; \ exit 1; \ fi clean: @echo "清理资源..." @tcb fn delete app --yes --env-id $(ENV_ID) || true ``` --- ## CI/CD 最佳实践 ### GitHub Actions ```yaml name: Deploy to CloudBase on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install CloudBase CLI run: npm install -g @cloudbase/cli - name: Login to CloudBase run: | echo "${{ secrets.CLOUDBASE_SECRET_ID }}" > /tmp/secret_id echo "${{ secrets.CLOUDBASE_SECRET_KEY }}" > /tmp/secret_key tcb login --key --secretId $(cat /tmp/secret_id) --secretKey $(cat /tmp/secret_key) - name: Deploy Function id: deploy run: | tcb fn deploy app --env-id ${{ secrets.ENV_ID }} --json --yes continue-on-error: true - name: Handle Deployment Result if: failure() run: | EXIT_CODE=${{ steps.deploy.outcome }} if [ "$EXIT_CODE" = "2" ]; then echo "认证失败,检查密钥配置" exit 1 elif [ "$EXIT_CODE" = "5" ]; then echo "云 API 错误,5 秒后重试" sleep 5 tcb fn deploy app --env-id ${{ secrets.ENV_ID }} --json --yes else echo "部署失败(退出码 $EXIT_CODE)" exit 1 fi ``` ### GitLab CI ```yaml deploy: stage: deploy image: node:18 script: - npm install -g @cloudbase/cli - tcb login --key --secretId $SECRET_ID --secretKey $SECRET_KEY - tcb fn deploy app --env-id $ENV_ID --json --yes retry: max: 2 when: - script_failure # 仅在脚本失败时重试 only: - main ``` ### Jenkins ```groovy pipeline { agent any stages { stage('Deploy') { steps { script { def result = sh( script: 'tcb fn deploy app --env-id ${ENV_ID} --json --yes', returnStatus: true ) if (result == 0) { echo '✅ 部署成功' } else if (result == 2) { error '❌ 认证失败,检查密钥配置' } else if (result == 5) { echo '⚠️ 云 API 错误,重试中...' sleep 5 sh 'tcb fn deploy app --env-id ${ENV_ID} --json --yes' } else { error "❌ 部署失败(退出码 ${result})" } } } } } } ``` --- ## 调试方法 ### 1. 查看退出码 ```bash tcb fn deploy app echo "退出码: $?" ``` ### 2. 使用 `--verbose` 查看详细日志 ```bash tcb fn deploy app --verbose ``` ### 3. 使用 `--json` 获取结构化错误信息 ```bash tcb fn deploy app --json ``` **输出示例**: ```json { "error": { "code": "FUNCTION_NOT_FOUND", "message": "函数 app 不存在", "exit_code": 4 } } ``` --- ## 注意事项 1. **退出码不是 HTTP 状态码** 退出码 `4` 表示"资源不存在",不是 HTTP 404。退出码 `5` 表示"云 API 错误",不是 HTTP 500。 2. **`--yes` 不影响退出码** `--yes` 只跳过确认提示,不改变命令的执行结果和退出码。 3. **管道操作需要 `set -e`** 在 Shell 脚本中,使用 `set -e` 确保管道中的任何命令失败都会中断执行: ```bash #!/bin/bash set -e # 遇到非 0 退出码立即退出 tcb fn deploy app tcb hosting deploy ``` 4. **CI/CD 中建议使用 `--json`** `--json` 输出结构化数据,便于解析错误信息和提取字段。 --- ## 参考资料 - [全局选项](./global-options.md) - [CloudBase CLI 快速开始](./quick-start.md) - [CI/CD 集成指南](https://docs.cloudbase.net/cli-v1/faq#cicd) - [源码: src/exit-codes.ts](https://github.com/TencentCloudBase/cloudbase-cli/blob/master/src/exit-codes.ts) --- # CloudBase CLI/其他参考/从 v0 升级到 v1 > 当前文档链接: https://docs.cloudbase.net/cli-v1/migrate CloudBase CLI v1 版本已经规划了许久,在 v1 版本中,我们完全改变了命令的使用方法,简化了部分命令的长度,并向更为标准的规范对齐,这也是后期我们努力的发展发向:为用户提供更简单、更强大的 CLI 工具。 同时,CloudBase CLI v1 版本完全兼容了 CloudBase v.0 中的命令用法,您不用担心升级所可能带来的问题,这并不是一次破坏性的、不兼容的升级。尽管如此,我们仍建议您使用新的命令,这是未来的发展方向。 :::tip 升级到 v3? 如果您正在从 v1/v2 升级到 v3,请参考 [从 v1/v2 升级到 v3](./migrate-v3.md)。 ::: ## 升级操作 1. 卸载旧版本 ```bash npm uninstall -g @cloudbase/cli ``` 2. 安装新版本 ```bash npm i -g @cloudbase/cli ``` ## 升级说明 ### tcb 命令 在 v1 版本中,我们引入了 `tcb` 命令,可以一键创建、部署云开发项目。 ### 命令格式变更 在 v1 版本中,所有的命令由 `env:list` 格式变成了 `env list` 格式,即命令中所有的 `:` 符号修改为空格使用。这有助于我们合并同类型的命令,提供更简单易懂的命令 help 提示。同时也符合大部分的命令使用习惯,降低学习成本。 ### 命令名变更 1. 原 `functions` 命令改为了简短的 `fn` 命令,如 `tcb fn list` 2. 原云接入 `service` 命令改为了 `access` 命令 ### 移除编程式 API 在 v1 版本中,我们彻底移除了 CLI 的编程式使用,如果你需要以管理员身份操作云开发的资源,请使用 [`@cloudbase/manager-node`](https://docs.cloudbase.net/api-reference/manager/node/introduction.html) SDK --- # CloudBase CLI/其他参考/从 v1/v2 升级到 v3 > 当前文档链接: https://docs.cloudbase.net/cli-v1/migrate-v3 CloudBase CLI v3.0 在 v1/v2 基础上进行了功能扩充和命令体系梳理,同时兼容 v1/v2 中已有的主要命令格式。升级后,建议优先关注全局使用方式、能力收敛方式,以及已废弃命令的迁移路径。 ## 升级操作 ```bash npm install -g @cloudbase/cli ``` 查看当前版本: ```bash tcb --version ``` --- ## v3.0 重要变更 ### 1. 全局使用体验升级 v3.0 先做的是 CLI 基础体验统一,这部分会直接影响您后续使用所有命令的方式。 - 全局环境参数统一为 `--env-id`,旧参数 `--envId` 仍兼容,但不再推荐使用。 - 新增 `tcb env use`,支持设置全局默认环境;环境解析优先级为:`tcb env use` < 项目 `cloudbaserc.json` < 命令行 `--env-id`。 - 更多命令支持 `--json` 输出,便于脚本、CI/CD 和 AI 工具稳定解析结果。 - 更多命令支持 `--yes`,可跳过交互确认,适合自动化场景。 - 新版 `--help` 的自解释能力明显增强,支持从顶层命令逐级下钻到具体子命令,并按“用法、参数、示例、相关命令、文档”组织输出。 - v3.0 引入结构化退出码(Exit Code),可区分认证失败、参数错误、资源不存在、云 API 错误、本地文件错误等场景,方便流水线做重试和错误处理。 :::info 其中新版 `--help` 值得关注。它不再只是参数速查,而是命令发现入口:先用顶层帮助定位命令,再进入子命令帮助确认真实签名。像 `tcb fn deploy --help` 这样的输出,会把命令写法、关键参数、示例、相关命令和文档链接放在一起,既方便人工检索,也适合 AI 先读帮助再生成命令,减少幻觉和试错。 ::: ```bash $ tcb fn deploy --help Usage: tcb fn deploy [options] [name] Options: --force --dir --runtime --json 描述: 部署云函数(首次部署即创建新函数)... 示例: tcb fn deploy hello tcb fn deploy hello --force tcb fn deploy --all 相关命令: tcb fn invoke tcb fn log 文档: https://docs.cloudbase.net/cli-v1/functions/deploy ``` ### 2. 新增通用能力 v3.0 补齐了几类跨场景的通用能力,减少“必须跳到控制台”或“自己封装脚本”的情况。 | 命令 | 作用 | |------|------| | `tcb docs` | 在终端内检索和阅读官方文档,无需登录 | | `tcb api` | 直接调用腾讯云 API,无需自行处理鉴权和请求封装 | | `tcb secrets` | 获取当前 Web 登录态下的临时密钥,便于本地调试 SDK | 这部分能力更偏工具层增强,适合排障、自动化和 AI 协作场景。 ### 3. 原有命令能力增强与收敛 v3.0 不只是加新命令,也对原有命令做了能力补齐和职责收敛。 #### 云存储增强 - 新增 `tcb storage rm`,替代旧的 `tcb storage delete`,支持递归删除、`--force`、`--dry-run`。 - 新增 `tcb storage cp`,支持云端复制和移动文件。 - 新增 `tcb storage search`,支持按模式、目录、后缀和正则搜索文件。 #### 日志能力统一 - 新增统一日志入口 `tcb logs search`,支持云函数、云托管、数据库、大模型等日志统一检索。 - 原先分散在资源模块中的日志查询方式开始收敛,例如云函数日志文档已改为引导使用 `tcb logs`。 #### 权限能力统一 - 资源权限统一收敛到 `tcb permission`,用于管理数据库集合、存储桶、SQL 表、云函数的权限。 - 原有存储 ACL / rules 相关命令在 v3.0 起废弃,迁移到统一权限模型。 ### 4. 新增能力 除通用能力外,v3.0 还新增了一批面向具体资源和业务场景的命令模块: | 命令 | 说明 | |------|------| | `tcb cors` | 安全域名管理(Web SDK 白名单) | | `tcb domains` | 自定义域名管理 | | `tcb routes` | HTTP 服务路由管理 | | `tcb app` | CloudBase 云应用部署管理 | | `tcb agent` | AI Agent 管理 | | `tcb user` | 终端用户管理 | | `tcb role` | 角色管理 | | `tcb db` | MySQL / NoSQL 数据库管理 | 其中比较值得关注的新增方向有: - 环境管理从“查看/重命名”扩展到完整生命周期,新增 `tcb env use/create/modify/renew/delete/usage/info`。 - HTTP 访问服务从旧的 `tcb service` 拆分为 `tcb cors`、`tcb domains`、`tcb routes` 三个更清晰的模块。 - 云托管新增 `tcb cloudrun traffic`,支持灰度流量分配、全量发布和回滚。 - `tcb db` 不再只承载旧的数据模型命令,还扩展出 MySQL 执行、实例管理、备份回档、慢查询监控,以及 NoSQL 原生命令和回档能力。 ### 5. 已移除能力 以下能力在 v3.0 中已移除,不再支持: | 已移除命令 | 替代方式 | |------------|----------| | `tcb fun list/deploy/run` | 迁移到 `tcb cloudrun list/deploy/run` | | `tcb run standalonegateway ...` | 无 CLI 替代命令,相关操作请参考控制台 | ### 6. 废弃命令说明 以下命令在 v3.0 中仍兼容保留,但已标记为废弃,建议尽快迁移: | 废弃项 | 推荐替代 | |--------|----------| | `tcb functions:*` | `tcb fn ...` | | `tcb storage:*` | `tcb storage ...` | | `tcb hosting:*` | `tcb hosting ...` | | `tcb service ...` | `tcb cors` / `tcb domains` / `tcb routes` | | `tcb db list/pull/push` | `tcb db model list/pull/push` | | `tcb run list/delete` | `tcb cloudrun list/delete` | | `tcb storage get-acl/set-acl/rules ...` | `tcb permission` | --- # CloudBase CLI/其他参考/设备码授权与接入/设备码授权概览 > 当前文档链接: https://docs.cloudbase.net/cli-v1/device-flow-protocol/index > 本文说明什么是设备码授权。 如果你需要进一步了解如何用 CloudBase CLI 与参考实现完成具体对接,请继续阅读 [企业自建设备码授权服务对接 CloudBase CLI](./integration.md)。 --- ## 1. 为什么要用设备码授权 设备码授权解决的核心问题是:发起登录请求的终端,与完成网页登录确认的浏览器,往往不在同一台机器,也不在同一个交互界面里。 典型场景包括: 1. 用户在远程服务器、跳板机、容器或云端开发机里发起 CloudBase 登录,但浏览器在本地电脑上。 2. 用户通过聊天工具或类 OpenClaw 的对话式 AI 工具触发 CloudBase 登录,请求是由机器人或远端执行器发起的,浏览器确认却发生在用户自己的设备上。 这类场景下,传统“终端直接拉起浏览器并等待回调”的登录方式往往不可用。设备码授权把流程拆成两段: 1. 终端先申请一组 `device_code` 和 `user_code`。 2. 用户再去浏览器里确认“是否允许这次 CloudBase 登录”。 这样做的价值在于: 1. 终端侧只负责发起请求和轮询结果,不要求能完成浏览器回调。 2. 浏览器侧只负责身份认证和授权确认,不要求和使用 CLI/MCP 发起 CloudBase 登录的客户端在同一个进程或同一台机器上。 3. 同一套流程既能支持普通终端,也能支持聊天工具、代理执行器、远程任务这类异步交互场景。 --- ## 2. 协议约定 一套设备码授权服务通常需要实现 3 个核心接口: | 接口 | 方法 | 作用 | | --- | --- | --- | | `/auth/device/code` | `POST` | 终端申请设备码,获取 `device_code`、`user_code`、`verification_uri` | | `/auth/device/verify` | `POST` | 浏览器侧确认授权,把设备码状态从 `pending` 更新为 `authorized` | | `/auth/token` | `POST` | 负责首次换取凭证、刷新凭证、撤销会话 | 其中 `/auth/token` 通过 `grant_type` 区分 3 种动作: | `grant_type` | 作用 | | --- | --- | | `urn:ietf:params:oauth:grant-type:device_code` | 使用 `device_code` 首次换取凭证 | | `refresh_token` | 使用 `refreshToken` 刷新临时凭证,并轮换新的 `refreshToken` | | `revoke_token` | 撤销 `refreshToken` 对应会话,供 `tcb logout` 使用 | ### 2.1 核心请求与响应 #### `POST /auth/device/code` 请求: ```json { "client_id": "cloudbase-cli" } ``` 成功响应: ```json { "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS8A", "user_code": "QWER-ASDF", "verification_uri": "https://auth.example.com/cli-auth", "expires_in": 600, "interval": 3 } ``` #### `POST /auth/device/verify` 这是浏览器侧接口,路径可以由企业自行设计,但职责必须一致: 1. 校验当前用户已经完成企业身份认证。 2. 接收 `user_code`。 3. 找到对应的 `device_code` 记录。 4. 把授权状态从 `pending` 更新为 `authorized`。 5. 绑定当前用户身份,供后续签发临时凭证使用。 参考请求: ```json { "user_code": "QWER-ASDF" } ``` #### `POST /auth/token` with `grant_type=device_code` 终端轮询请求: ```json { "grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS8A", "client_id": "cloudbase-cli", "device_info": { "os": "darwin", "mac": "AA:BB:CC:DD:EE:FF", "hash": "a1b2c3d4e5f6..." } } ``` 成功响应需要返回一组可供客户端保存和后续使用的登录凭证,核心字段如下: | 字段 | 说明 | | --- | --- | | `refreshToken` | 长期令牌;如果实现自动续期,必须返回非空值 | | `expired` | `refreshToken` 的过期时间戳(毫秒) | | `tmpSecretId` | 腾讯云临时密钥 SecretId | | `tmpSecretKey` | 腾讯云临时密钥 SecretKey | | `tmpToken` | 腾讯云临时安全令牌 | | `tmpExpired` | 临时密钥过期时间戳(毫秒) | | `uin` | 当前登录用户标识 | | `tokenId` | 会话或令牌 ID | | `envIds` | 可选 | 企业自建授权服务在自定义 endpoint 场景下可返回的环境 ID 列表 | | `envList` | 可选 | 企业自建授权服务在自定义 endpoint 场景下可返回的环境详情列表 | | `envBillingInfoList` | 可选 | 企业自建授权服务在自定义 endpoint 场景下可返回的环境计费信息列表 | 如果你的企业自建授权服务需要在自定义 endpoint 场景下返回这 3 个字段,可以按下面的结构组织: `envIds`: ```json [ "cloud1-123456", "cloud1-abcdef" ] ``` `envList`: | 字段 | 类型 | 说明 | | --- | --- | --- | | `EnvId` | string | 环境 ID | | `Alias` | string | 环境别名 | | `Status` | string | 环境状态 | | `Source` | string | 环境来源,例如小程序或腾讯云 | | `CreateTime` | string | 创建时间 | | `UpdateTime` | string | 最后更新时间 | | `PackageId` | string | 套餐 ID | | `PackageName` | string | 套餐名称 | | `PayMode` | string | 支付方式 | | `IsDefault` | boolean | 是否默认环境 | | `Region` | string | 环境地域 | `envBillingInfoList`: | 字段 | 类型 | 说明 | | --- | --- | --- | | `EnvId` | string | 环境 ID | | `PackageId` | string | 套餐 ID | | `IsAutoRenew` | boolean | 是否自动续费 | | `Status` | string | 计费状态 | | `PayMode` | string | 计费模式 | | `IsolatedTime` | string | 隔离时间 | | `ExpireTime` | string | 过期时间 | | `CreateTime` | string | 首次接入计费时间 | | `UpdateTime` | string | 最近更新时间 | | `IsAlwaysFree` | boolean | 是否从未升级过付费版 | | `PaymentChannel` | string | 支付渠道 | | `FreeQuota` | string | 免费额度信息 | | `EnableOverrun` | boolean | 是否开启超额按量付费 | | `ExtPackageType` | string | 环境套餐类型 | 如果你需要进一步实现这两个对象数组,可参考调用腾讯云云 API 获取对应返回结构: 1. `DescribeEnvs`:`https://cloud.tencent.com/document/product/876/34820` 2. `DescribeBillingInfo`:`https://cloud.tencent.com/document/product/876/94390` #### `POST /auth/token` with `grant_type=refresh_token` ```json { "grant_type": "refresh_token", "refresh_token": "", "client_id": "cloudbase-cli" } ``` 推荐行为: 1. 校验当前 refresh token。 2. 重新签发临时凭证。 3. 轮换新的 `refreshToken`。 4. 让旧 `refreshToken` 立即失效。 #### `POST /auth/token` with `grant_type=revoke_token` ```json { "grant_type": "revoke_token", "refresh_token": "", "client_id": "cloudbase-cli" } ``` 推荐行为:删除或失效对应会话,并返回 `{}`。这个接口应保持幂等。 ### 2.2 建议错误码 | 错误码 | 典型场景 | | --- | --- | | `authorization_pending` | 用户还没有在浏览器中确认授权 | | `slow_down` | 客户端轮询过快 | | `expired_token` | `device_code` 已过期 | | `invalid_client` | `client_id` 缺失、非法或不匹配 | | `invalid_grant` | `device_code`、`refresh_token` 或授权状态异常 | | `unsupported_grant_type` | 不支持的 `grant_type` | | `already_consumed` | 设备码已成功使用过 | | `server_error` | 服务端签发临时凭证失败 | --- # CloudBase CLI/其他参考/设备码授权与接入/企业自建设备码授权服务对接 CloudBase CLI > 当前文档链接: https://docs.cloudbase.net/cli-v1/device-flow-protocol/integration > 本文说明企业如何使用 CloudBase CLI 与自建授权服务完成具体对接,并基于参考实现落地整套流程。 如果你想先了解设备码授权的背景和协议约定,请先阅读 [设备码授权概览](./index.md)。 自建授权服务参考实现仓库:[cloudbase-cli-auth-endpoint](https://cnb.cool/tencent/cloud/cloudbase/cloudbase-cli-auth-endpoint) --- ## 1. CloudBase CLI 如何配合这套流程 ### 1.1 配置授权服务地址 企业接入这套流程时,先把授权服务地址写入 CLI 配置。例如: ```bash tcb config set customOAuthEndpoint https://auth.example.com/auth ``` 其中 `https://auth.example.com/auth` 需要替换为您自行配置的授权服务路径。 然后再执行: ```bash tcb login ``` 这样做的原因是: 1. 首次登录时,CLI 会用这个地址发起设备码授权。 2. 登录成功后,如果本地保存了 `refreshToken`,CLI 续期时还需要知道同一个授权服务地址。 3. 用户执行 `tcb logout` 时,CLI 也需要回到这个地址发起 `revoke_token`。 ### 1.2 同步登录与分步登录 CloudBase CLI 对这套流程提供两种使用方式: | 方式 | 命令 | 适用场景 | | --- | --- | --- | | 同步登录 | `tcb login` | 用户直接在终端操作,允许当前命令持续等待授权完成 | | 分步登录 | `tcb login start` + `tcb login complete --session-id ` | 聊天工具、OpenClaw 类工具、机器人或远程执行器场景,需要先把授权信息返回给上层系统 | #### 同步登录 ```bash tcb login ``` **CLI 行为** 1. 申请设备码。 2. 展示并尝试打开企业自建授权页。 3. 轮询 `/auth/token`。 4. 保存本地登录态。 #### 分步登录 ```bash tcb login start tcb login complete --session-id ``` **CLI 行为** 1. `tcb login start` 负责申请设备码并输出授权信息,同时在本地创建一个登录会话,返回 `sessionId`。 2. `tcb login complete` 负责根据 `sessionId` 找回本地保存的设备码会话,在用户完成浏览器确认后继续轮询并完成登录。 ### 1.3 工作流程 ```mermaid sequenceDiagram participant CLI as CloudBase CLI participant Auth as 企业授权服务 participant Browser as 企业自建授权页 participant STS as 腾讯云 STS CLI->>Auth: 1. POST /auth/device/code Auth-->>CLI: 2. 返回 device_code、user_code、verification_uri CLI->>Browser: 3. 展示/打开 verification_uri Browser->>Auth: 4. POST /auth/device/verify Auth->>Auth: 5. 记录用户身份并将状态改为 authorized loop 轮询 CLI->>Auth: 6. POST /auth/token (device_code) alt 尚未确认 Auth-->>CLI: 7. authorization_pending else 已确认 Auth->>STS: 8. 获取临时凭证 STS-->>Auth: 9. 返回 tmpSecretId/tmpSecretKey/tmpToken Auth-->>CLI: 10. 返回 refreshToken、临时凭证、环境信息 end end opt 自动续期 CLI->>Auth: 11. POST /auth/token (refresh_token) Auth-->>CLI: 12. 返回新的 refreshToken 和临时凭证 end opt 退出登录 CLI->>Auth: 13. POST /auth/token (revoke_token) Auth-->>CLI: 14. 返回 {} end ``` --- ## 2. 参考架构:基于参考实现的实现方式 这一节开始引入参考实现。它的作用不是介绍仓库本身,而是把前面的协议和流程落到代码结构上。 参考实现仓库:[cloudbase-cli-auth-endpoint](https://cnb.cool/tencent/cloud/cloudbase/cloudbase-cli-auth-endpoint) ### 2.1 参考架构 ```mermaid flowchart TB subgraph Client[客户端] A[CloudBase CLI] B[企业自建授权页] end subgraph Service[企业授权服务] C["/auth/device/code"] D["/auth/device/verify"] E["/auth/token"] F[(设备码存储)] G[(Refresh Token 存储)] end subgraph Cloud[腾讯云能力] H[STS GetFederationToken] I[CloudBase 环境查询] end A --> C A --> E B --> D C --> F D --> F E --> F E --> G E --> H E --> I ``` ### 2.2 目录结构 ```text cloudbase-cli-auth-endpoint/ ├── docs/ │ └── protocol.md # 参考实现内部使用的协议说明 ├── public/ │ └── cli-auth.html # 企业自建授权页,输入 user_code 并确认授权 ├── src/ │ ├── server.ts # HTTP 入口,定义 /auth/device/code、/auth/device/verify、/auth/token │ ├── config.ts # 端口、过期时间、grant_type、STS 时长等配置 │ ├── types.ts # 设备码记录、refresh token 记录、请求体类型定义 │ └── utils/ │ ├── device-store.ts # 设备码和 refresh token 的过期清理逻辑 │ ├── oauth.ts # 设备码、用户码生成与 OAuth 错误结构 │ ├── sts.ts # 调用腾讯云 STS 获取临时凭证 │ ├── tcb.ts # 查询 envIds、envList、envBillingInfoList │ └── public-dir.ts # 静态页面目录解析 ├── .env.example # 启动示例服务需要的环境变量 └── README.md # 本地运行和手工联调示例 ``` ### 2.3 参考实现流程 #### 启动与依赖 参考实现启动前,至少需要准备以下环境变量: ```bash PORT=3000 BASE_URL=http://localhost:3000 TENCENTCLOUD_SECRET_ID=xxx TENCENTCLOUD_SECRET_KEY=xxx STS_TOKEN_DURATION=1800 ``` 其中: 1. `BASE_URL` 用来生成返回给 CLI 的 `verification_uri`。 2. `TENCENTCLOUD_SECRET_ID` 与 `TENCENTCLOUD_SECRET_KEY` 用于调用腾讯云 STS 获取临时凭证。 3. `STS_TOKEN_DURATION` 用于控制返回给 CLI 的临时凭证有效期。 准备完成后执行: ```bash npm install npm run dev ``` 服务启动后,就可以配合前文的 CLI 登录流程进行联调。 #### 首次登录流程 ```mermaid flowchart TD A[CLI 调用 /auth/device/code] --> B[校验 client_id] B --> C[生成 device_code 和 user_code] C --> D[写入 pending 状态] D --> E[返回 verification_uri] E --> F[浏览器打开授权页] F --> G[提交 user_code 到 /auth/device/verify] G --> H[状态改为 authorized] H --> I[CLI 轮询 /auth/token] I --> J{是否已授权} J -->|否| K[返回 authorization_pending] J -->|是| L[调用 STS 获取临时凭证] L --> M[查询 envIds envList envBillingInfoList] M --> N[生成 refreshToken 并保存] N --> O[返回登录凭证给 CLI] ``` #### 续期与退出流程 ```mermaid flowchart TD A[本地临时凭证过期] --> B[CLI 调用 refresh_token] B --> C[校验旧 refreshToken] C --> D[重新签发临时凭证] D --> E[轮换新的 refreshToken] E --> F[返回新的登录凭证] G[用户执行 tcb logout] --> H[CLI 调用 revoke_token] H --> I[使 refreshToken 对应会话失效] I --> J[返回空对象] ``` --- ## 3. 企业可在参考实现基础上继续扩展的方向 如果前面的流程已经跑通,后续企业通常会继续扩展这些能力: 1. 把内存存储换成 Redis、数据库或中心化会话服务,避免服务重启后设备码和 refresh token 丢失。 2. 把浏览器侧的简化用户信息,替换成企业自己的 SSO、IdP 或审批流结果。 3. 把 STS 权限策略收敛成按用户、组织、环境隔离的最小权限策略。 4. 再逐步补齐审计、限流、告警和可观测性。 --- # CloudBase CLI/其他参考/常见问题 > 当前文档链接: https://docs.cloudbase.net/cli-v1/faq ## 基础使用 ### 无法登录,CLI 工具没有响应 请检查您的终端能否访问网络。 ## 云函数 ### 函数部署失败的常见原因 - `package.json` 中依赖不存在 ## AI CLI 相关 ### 什么是 CLI?为什么这很重要? CLI(命令行界面)是一种通过文本命令与计算机交互的接口方式。与图形用户界面(GUI)相比,CLI 提供了更直接、更高效的交互方式。 **GUI vs CLI 的核心差异**: - **GUI**:基于图形元素和鼠标操作,适合直观交互和可视化操作 - **CLI**:基于文本命令和键盘输入,适合自动化、批处理和远程操作 CLI 的重要性在于: - **可编程性**:让 AI 编程能力可以被脚本、CI/CD、自动化流程调用 - **可集成性**:可以嵌入到现有的开发工具链和工作流中 - **可扩展性**:不受 GUI 限制,可以在任何环境中运行 - **效率提升**:支持批量处理、远程操作和自动化工作流 ### CloudBase AI CLI 的价格是多少? CloudBase AI CLI 本身完全免费。新用户可获得丰富的免费体验额度: **云开发环境**: - 云函数调用次数、数据库存储空间等基础服务免费额度 - 详细定价请参考腾讯云开发官方定价页面 **AI 模型体验**: - 新环境赠送 100 万 token,支持 DeepSeek、Kimi K2 等主流模型快速体验 - 支持用户自定义配置模型和 API Key - 可按需选择不同模型,灵活控制成本 **付费服务**: - 超出免费额度后按实际使用量计费 - 支持多种付费方式,价格透明 ### CloudBase AI CLI 是如何工作的? CloudBase AI CLI 是一个统一的集成工具,不是独立的 AI 编程工具。它通过统一的命令行界面集成多种 AI 编程 CLI 工具,如 Claude Code、OpenAI Codex、aider、Qwen Code 等。开发者可以通过 `tcb ai -a [工具名]` 选择不同的 AI 工具,支持交互式对话和非交互式脚本模式。CLI 集成了腾讯云开发平台,支持一键开发部署生成的代码到云端,让 AI 编程能力真正成为可编程的基础设施。 ```mermaid sequenceDiagram participant D as 开发者 participant CLI as CloudBase AI CLI participant AI as AI 编程工具 participant Cloud as 云开发平台 D->>CLI: tcb ai -a claude CLI->>AI: 调用 Claude Code AI->>CLI: 返回代码 CLI->>Cloud: 部署到云端 Cloud->>CLI: 部署完成 CLI->>D: 返回结果 Note over D,Cloud: 支持多种应用场景 Note over CLI: 统一管理多种 AI 工具 ``` **应用场景包括:** - **小程序开发者工具**:在微信开发者工具中直接运行,实现小程序开发调试部署一体化 - **VS Code 终端**:在 VS Code 集成终端中使用,与现有开发工作流无缝集成 - **GitHub Actions**:在 CI/CD 流水线中自动执行,实现代码生成和部署的自动化 - **任意终端环境**:支持在服务器、容器、本地开发环境等任何地方运行 ### 为什么现在 AI 编程工具都在推出 CLI 版本? 这是一个重要的技术趋势,原因包括: 1. **从"工具"到"基础设施"的转变**: - 传统 AI 编程工具只是独立的应用程序 - CLI 让 AI 编程能力变成可编程的基础设施 - 可以被脚本、自动化流程、CI/CD 系统调用 2. **提高开发效率**: - 无需在多个 GUI 工具间切换 - 支持批量处理和自动化 - 可以集成到现有开发工作流中 - 大幅减少编码量 3. **适应现代开发环境**: - 云原生开发、容器化部署 - 远程开发、服务器环境 - 持续集成/持续部署(CI/CD) - 推动 AI 从"生成代码"到"全流程协作"的进化 ```mermaid graph TD A[AI IDE] --> B[独立应用程序] B --> C[GUI 界面] C --> D[手动操作] E[AI CLI] --> F[可编程基础设施] F --> G[脚本调用] G --> H[自动化流程] H --> I[CI/CD 集成] style A fill:#ffebee style E fill:#e8f5e8 style B fill:#ffebee style F fill:#e8f5e8 ``` ### CLI 和 GUI 有什么区别?我应该选择哪个? 两者各有优势,最佳实践是结合使用: **GUI(图形界面)的优势**: - 直观的交互体验,适合探索和调试 - 丰富的视觉反馈,便于理解复杂任务 - 提高一次成功率,适合学习和实验 **CLI(命令行界面)的价值**: - 可编程和自动化,适合批处理和 CI/CD - 不受环境限制,可在任何地方运行 - 结构化输出,便于集成到现有工作流 **最佳实践**:**GUI 探索方案,CLI 固化流程**。先用 GUI 工具探索和调试,找到最佳方案后,用 CLI 将流程固化和自动化。 ```mermaid graph LR A[需求分析] --> B[GUI 探索] B --> C[方案验证] C --> D[CLI 固化] D --> E[自动化部署] F[开发阶段] --> B G[生产阶段] --> D style A fill:#e3f2fd style B fill:#fff3e0 style C fill:#fff3e0 style D fill:#e8f5e8 style E fill:#e8f5e8 style F fill:#e3f2fd style G fill:#e8f5e8 ``` ### 我需要学习命令行才能使用 CloudBase AI CLI 吗? CloudBase AI CLI 设计为渐进式学习曲线: **基础使用**(最小学习成本): ```bash # 安装 npm i @cloudbase/cli -g # 启动 AI 助手 tcb ai # 直接描述需求 tcb ai -- -p "帮我创建一个用户管理系统" ``` 详细安装说明请参考 [CloudBase CLI 安装文档](../install)。 **高级功能**(按需学习): - 自动化脚本编写 - CI/CD 集成 - 批量处理 - 自定义工作流 我们提供详细的技术文档和最佳实践指南,支持从入门到精通的完整学习路径。 ### 我如何获得帮助和客户支持? 我们提供多种支持渠道: - 官方文档:https://docs.cloudbase.net/cli-v1/ai/introduce - GitHub 仓库:https://github.com/TencentCloudBase/CloudBase-AI-ToolKit - 技术交流群:微信扫码加入官方技术群 - 工单支持:https://tcb.cloud.tencent.com/help-center ### 我在哪里可以下载 CloudBase AI CLI? CloudBase AI CLI 是免费工具,可以通过以下方式获取: ```bash npm i @cloudbase/cli -g ``` 详细安装方式和说明请参考: - [CloudBase CLI 安装文档](./install) - 完整的安装指南 - [AI CLI 使用文档](./ai/introduce) - AI 功能使用说明 ### CloudBase AI CLI 与 CloudBase AI Toolkit 的关系 CloudBase AI CLI 内置了 CloudBase AI Toolkit 的全部功能,并提供了 CLI 形态的产品体验。如果你之前使用过 CloudBase AI Toolkit,可以继续使用,同时通过安装 CloudBase AI CLI 获得更强大的功能。CloudBase AI Toolkit 将继续维护,为需要轻量级集成的用户提供服务。 详细介绍可参考 [CloudBase AI Toolkit 文档](/ai/cloudbase-ai-toolkit/) --- # CloudBase CLI/其他参考/未命名文档 > 当前文档链接: https://docs.cloudbase.net/faq/knowledge/multiple-cli-version --- tags: - CLI - 环境变量 - PATH - 版本冲突 - 安装问题 --- import MultipleTcbPaths from "@site/src/components/MultipleTcbPaths"; # 检测到多个 tcb 命令 当系统检测到多个 `tcb` 命令时,可能会导致版本混淆问题。建议删除不必要的版本: ## 建议解决方案 **手动删除多余的版本**: 1. 根据检测到的路径,手动删除不需要的 `tcb` 和 `cloudbase` 可执行文件 :::warning 注意事项 - 删除前请确认该路径下的 tcb 版本不是您正在使用的版本 - 建议保留最新版本或您常用的版本 - 如果路径在 node_modules 中,可以通过卸载对应的包来移除 2. 检查当前存在的版本,确认删除成功。执行命令: Windows: `where.exe tcb` macOS/Linux: `which -a tcb` 通过 `tcb -v` 确认当前版本 --- # CloudBase CLI/⚠️ 废弃命令(兼容保留)/HTTP 网关 > 当前文档链接: https://docs.cloudbase.net/cli-v1/service :::danger 已废弃(v3.0.0+) `tcb service` 命令自 **v3.0.0** 起已废弃,相关能力已由以下命令取代: | 旧能力 | 新命令 | 文档 | |--------|--------|------| | 安全域名白名单 | `tcb cors add/list/rm` | [安全域名管理](./cors.md) | | 绑定/解绑自定义域名 | `tcb domains add/ls/rm` | [自定义域名管理](./domains.md) | | 创建/管理路由规则 | `tcb routes add/list/edit/delete` | [HTTP 服务路由管理](./routes.md) | 旧命令保留向后兼容,但**不再维护**,建议尽快迁移。 ::: HTTP 网关支持通过 HTTP 链接访问云开发资源。 可以通过 CLI 工具、控制台管理访问域名和云函数访问路径。 ## 创建 您可以使用下面的命令,通过 CLI 命令行交互的方式创建 HTTP 网关地址 ```sh tcb service create -e envId ``` 您也可以指定指定云函数名称 `functionName` 和触发路径 `servicePath` 创建 HTTP 网关地址 ``` tcb service create -p servicePath -f functionName ``` ## 删除 HTTP 网关链接 您可以使用下面的命令,通过 CLI 命令行交互的方式删除云函数 HTTP 网关链接 ```sh tcb service delete -e envId ``` 您也可以通过参数指定需要删除的 HTTP 网关信息: ```sh # 指定 HTTP 网关绑定的路径 tcb service delete -p servicePath # 指定 HTTP 网关 Id tcb service delete -i serviceId # 通过函数名删除该函数的所有 HTTP 网关 tcb service delete --name functionName ``` :::tip 参数说明 使用 `--name` 或 `-n` 参数时,确保格式正确:`--name value` 或 `-n value`。错误格式 `-name value` 会被解析成 `-n ame`。 ::: ## 开关 HTTP 网关 您可以通过下面的命令开启或关闭 HTTP 网关: ```sh tcb service switch -e envId ``` ## 鉴权开关 您可以通过下面的命令开启或关闭 HTTP 网关鉴权: ```sh tcb service auth switch -e envId ``` ## 查询 HTTP 网关信息 您可以通过下面的命令列出所有的 HTTP 网关链接,查看它们的基本信息: ```sh tcb service list ``` 您会得到类似下面的输出: ``` | 服务 ID | 触发路径 | 关联资源 | 触发类型 | 创建时间 | |--------------------------------------|--------------|-------------------|----------|---------------------| | ab2c6749-b6ef-45ca-893e-c7bd2adb5ea2 | /http-test | simple-event-func | 云函数 | 2026-03-13 14:45:10 | | 94eb95da-2a91-456c-b6f2-5bdd10833a79 | /image-test | image-http-fun | 云托管 | 2026-03-16 21:33:39 | | 341f0a9f-7b5e-4505-b111-7c73f678a353 | /test | httpTest | 云托管 | 2026-03-13 14:39:08 | ``` ### 设置更多的查询约束条件 ```sh -d domain 指定域名 -p servicePath 指定 HTTP 网关路径 -i serviceId 指定 HTTP 网关 Id ``` ## 绑定 HTTP 网关自定义域名 :::caution 注意事项 **绑定自定义域名之前,请先设置您的域名的 CNAME 记录值为[默认域名](https://tcb.cloud.tencent.com/dev#/env/http-access),CNAME 记录不存在时会导致域名绑定失败!** ::: 您可以通过下面的命令绑定 HTTP 网关域名: ```sh tcb service domain bind domain ``` ## 解绑 HTTP 网关自定义域名 您可以通过下面的命令解绑 HTTP 网关域名: ```sh tcb service domain unbind domain ``` ## 查询 HTTP 网关自定义域名 您可以通过下面的命令列出所有的 HTTP 网关域名,查看它们的基本信息: ```sh tcb service domain list ``` 您会得到类似下面的输出: ![](https://main.qcloudimg.com/raw/32b243c13c3f5d6083e7ae502996c83b.png) ### 设置更多的查询约束条件 ```sh -d domain 指定域名 ``` --- # ai Documentation > 云开发AI服务,包含智能体、AI构建器、编程助手、模型服务等完整AI解决方案 # AI/简介 > 当前文档链接: https://docs.cloudbase.net/ai/introduce 云开发 AI 提供统一的大模型接入和 Agent 开发能力,帮助开发者在小程序、Web、公众号、企微客服等场景快速接入 AI 能力。 ## 核心能力 | 能力 | 说明 | | --- | --- | | **调用大模型** | 直接调用 DeepSeek、混元等大模型,支持文本生成、流式输出、工具调用 | | **Agent 开发** | 使用 LangChain、LangGraph、CrewAI 等框架开发智能体 | | **AI 工具** | 在 Cursor、微信开发者工具等 IDE 中直接使用 AI 编码能力 | | **多端接入** | 小程序、Web、公众号、小程序客服、企微客服、服务号客服 | | **Agent UI 组件** | 开箱即用的聊天界面组件,支持小程序、React、微搭 | ## 准备工作 1. 拥有腾讯云账号,[开通云开发环境](../quick-start/create-env)并获取环境 ID 2. 在[控制台 → AI → 生文模型](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel)中开启所需模型 ## 快速开始 根据您的需求选择合适的接入方式: | 场景 | 推荐方式 | SDK / 协议 | 文档 | | --- | --- | --- | --- | | 快速体验 AI 对话 | 快速开始 | 小程序 `wx.cloud.extend.AI`
Web `@cloudbase/js-sdk` | [小程序](./quickstart/miniprogram) / [Web](./quickstart/web) | | 在 IDE 中使用 AI 编码 | AI 工具 | OpenAI 兼容、Anthropic 兼容 | [查看文档](./quickstart/ai-tools) | | 直接调用大模型 API | 调用大模型 | 多种 SDK 及 HTTP API | [查看文档](./model/overview) | | 开发自定义 Agent | Agent 开发 | LangChain / LangGraph / CrewAI | [查看文档](./agent-development/) | | 快速搭建聊天界面 | Agent UI 组件 | 小程序源码组件 / React 组件 / 微搭组件 | [小程序](./agent-ui/agent-ui-mp) / [React](./agent-ui/agent-ui-react) / [微搭](./agent-ui) | ## 调用大模型 云开发 AI 支持通过资源点套餐接入 DeepSeek、混元等多种大模型,支持多种调用方式: | 调用方式 | 适用场景 | 文档 | | --- | --- | --- | | 小程序 SDK | 微信小程序 / 小游戏 | [小程序调用](./model/miniprogram-access) | | Web SDK | 浏览器端 Web 应用 | [Web 调用](./model/web-access) | | Node SDK | 云函数、云托管、服务端 | [Node 调用](./model/nodejs-access) | | cURL / HTTP API | 后端服务、脚本 | [cURL 调用](./model/curl-access) | | OpenAI SDK | 兼容 OpenAI SDK 的项目 | [OpenAI SDK 调用](./model/openai-sdk-access) | 更多信息请查看 [调用大模型](./model/overview)。 ## Agent 开发 使用开源框架开发 Agent,并部署到云开发: ### 支持的框架 | 框架 | 语言 | 特点 | 文档 | | --- | --- | --- | --- | | LangChain | TypeScript / Python | 成熟稳定、生态丰富 | [开发指南](./agent-development/frameworks/langchain) | | LangGraph | TypeScript / Python | 图结构工作流、精细控制 | [开发指南](./agent-development/frameworks/langgraph) | | CrewAI | Python | 多 Agent 协作 | [开发指南](./agent-development/frameworks/crewai) | ### 部署方式 - [HTTP 云函数部署](./agent-development/deployment/cloud-function) - 快速部署、按量计费 - [云托管部署](./agent-development/deployment/cloud-run) - 长连接支持、高并发场景 更多信息请查看 [Agent 开发概述](./agent-development/)。 ## Agent UI 组件 开箱即用的聊天界面组件,快速搭建 AI 对话应用: | 组件 | 适用场景 | 文档 | | --- | --- | --- | | 小程序源码组件 | 微信小程序原生开发 | [使用指南](./agent-ui/agent-ui-mp) | | React 组件 | React Web 应用 | [使用指南](./agent-ui/agent-ui-react) | | 微搭可视化组件 | 微搭低代码平台 | [使用指南](./agent-ui/agent-ui) | 更多功能: - [自定义输出结构](./agent/custom-output) - [多 Agent 示例](./agent-ui/multi-agent-example) ## 上线与发布 将 Agent 发布到各端应用: ### 应用发布 | 发布渠道 | 说明 | 文档 | | --- | --- | --- | | 小程序 | 发布为微信小程序 | [发布指南](./release/release-mp) | | PC / H5 | 发布为 Web 应用 | [发布指南](./release/release) | ### 客服接入 | 渠道 | 说明 | 文档 | | --- | --- | --- | | 公众号客服 | 对接公众号(订阅号)客服 | [接入指南](./agent/integration-pa) | | 小程序客服 | 对接小程序客服 | [接入指南](./agent/integration-mp) | | 企微客服 | 对接企业微信客服 | [接入指南](./agent/integration-workwx) | | 服务号客服 | 对接服务号客服 | [接入指南](./agent/integration-server) | ## AI 小程序成长计划 参与 [AI 小程序成长计划](./ai-inspire-plan),可免费获得: - 1 亿 Token 资源包 - 1 万张生图资源包 - 6 个月云开发个人版环境 详情请查看[参与指南](./ai-inspire-plan-guide)。 ## 更多资源 - [常见问题 FAQ](./FAQ) - [更新日志](./CHANGELOG) 如有问题,欢迎加入社群交流: --- # AI/快速开始/小程序 > 当前文档链接: https://docs.cloudbase.net/ai/quickstart/miniprogram 本文介绍 **小程序** 如何快速接入 **云开发 AI** 能力 ## 准备工作 - 注册一个微信小程序账号,并且创建本地小程序工程项目 - 小程序基础库需要在 3.15.1 及以上版本 - 已创建云开发环境(旧套餐可升级),并在[控制台 → AI → 生文模型](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel)中开启所需模型 ## 指引 - 指引一:[直接调用大模型,实现文本生成](#指引一调用大模型实现文本生成) - 指引二:[通过 Agent(智能体)实现智能对话](#指引二通过-agent智能体实现智能对话) ## 指引一:调用大模型,实现文本生成 在小程序中,直接调用大模型的文本生成能力,实现最简单的文本生成。 这里以一个“七言绝句”生成器的简单 Demo 为例: ### 第 1 步:初始化云开发环境 在小程序代码中,通过以下代码进行云开发环境初始化: ```js wx.cloud.init({ env: "<云开发环境ID>", }); ``` 其中 `"<云开发环境ID>"` 需替换为实际云开发环境 ID。初始化成功后,就可使用 `wx.cloud.extend.AI` 调用 AI 能力。 ### 第 2 步: 创建 AI 模型,并调用生成文本 在小程序基础库 3.15.1 及以上,以调用 deepseek-v4-flash 模型为例,小程序端的代码如下: ```js // 创建模型实例,这里我们使用 DeepSeek 大模型 const model = wx.cloud.extend.AI.createModel("cloudbase"); // 我们先设定好 AI 的系统提示词,这里以七言绝句生成为例 const systemPrompt = "请严格按照七言绝句或七言律诗的格律要求创作,平仄需符合规则,押韵要和谐自然,韵脚字需在同一韵部。创作内容围绕用户给定的主题,七言绝句共四句,每句七个字;七言律诗共八句,每句七个字,颔联和颈联需对仗工整。同时,要融入生动的意象、丰富的情感与优美的意境,展现出古诗词的韵味与美感。"; // 用户的自然语言输入,如‘帮我写一首赞美玉龙雪山的诗’ const userInput = "帮我写一首赞美玉龙雪山的诗"; // 将系统提示词和用户输入,传入大模型 const res = await model.streamText({ data: { model: "hy3", // 指定具体的模型 messages: [ { role: "system", content: systemPrompt }, { role: "user", content: userInput }, ], }, }); // 接收大模型的响应 // 由于大模型的返回结果是流式的,所以我们这里需要循环接收完整的响应文本。 for await (let str of res.textStream) { console.log(str); } // 输出结果: // "# 咏玉龙雪山\n" // "皑皑峻岭入云巅,玉骨冰肌傲九天。\n" // "雪影岚光添胜景,神山圣境韵绵绵。\n" ``` 可见,仅需几行小程序代码,就可以通过云开发直接调用大模型的文本生成能力。 ## 指引二:通过 Agent(智能体)实现智能对话 通过调用大模型的文本生成接口,可以快速实现一问一答的场景。但对于一个完整的对话功能来说,仅仅有一个大模型的输入、输出还不够,还需要把大模型变为完整的 Agent,才能更好地与用户进行对话。 云开发的 AI 能力不仅提供了原始的大模型接入,还提供了 Agent 接入的能力,开发者可以在云开发上定义自己的 Agent,然后通过小程序直接调用 Agent 进行对话。 ### 第 1 步:初始化云开发环境 在小程序代码中,通过以下代码进行云开发环境初始化: ```js wx.cloud.init({ env: "<云开发环境ID>", }); ``` 其中 `"<云开发环境 ID>"` 需替换为实际云开发环境 ID。初始化成功后,就可使用 `wx.cloud.extend.AI` 调用 AI 能力。 ### 第 2 步:创建一个 Agent 进入[云开发平台-AI](https://tcb.cloud.tencent.com/dev?#/ai?tab=agent),选择模板并创建一个 Agent。 您可以通过 LangChain、LangGraph 等开源框架创建 Agent,也可以对接腾讯元器、智能体开发平台、Dify 等第三方 Agent。 ![image](https://qcloudimg.tencent-cloud.cn/raw/35d6467a0399523ae4f680a0cf9b1fe6.png) 复制页面上的 AgentID,即 Agent 的唯一标识,在下面的代码中会用到。 ![image](https://qcloudimg.tencent-cloud.cn/raw/088d3d13fccb335728d687053a9fec78.png) ### 第 3 步:在小程序中实现与 Agent 的对话 刚才创建了一个“小程序开发专家”的 Agent 智能体,下面来试试与它进行对话,看他能不能处理云开发常见的报错问题。 在小程序中,使用以下代码直接调用刚刚我们创建的 Agent,进行对话: ```js // 用户的输入,这里我们以某个报错信息为例 const userInput = "我的小程序这个报错是什么意思:FunctionName parameter could not be found"; const res = await wx.cloud.extend.AI.bot.sendMessage({ data: { botId: "", // 第2步中获取的Agent唯一标识 msg: userInput, }, }); for await (let x of res.textStream) { console.log(x); } // 输出结果: // "### 报错解释\n" // "**错误信息:** `FunctionName \n" // "parameter could not be found` \n // "这个错误通常表示在调用某个函数时,\n" // "指定的函数名参数没有找到。具体来说,\n" // "可能是以下几种情况之一:\n" // …… ``` --- # AI/快速开始/Web 应用 > 当前文档链接: https://docs.cloudbase.net/ai/quickstart/web 本文介绍 **Web 应用** 如何快速接入 **云开发 AI** 能力 ## 准备工作 - 创建一个 Web 项目(React、Vue 或原生 HTML 均可) - 已创建云开发环境(旧套餐可升级),并获取环境 ID - 在[控制台 → AI → 生文模型](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel)中开启所需模型 - 获取 Publishable Key([CloudBase 控制台 → 环境配置 → API Key](https://tcb.cloud.tencent.com/dev#/env/apikey)) ## 指引 - 指引一:[直接调用大模型,实现文本生成](#指引一调用大模型实现文本生成) - 指引二:[通过 Agent(智能体)实现智能对话](#指引二通过-agent智能体实现智能对话) ## 指引一:调用大模型,实现文本生成 在 Web 应用中,直接调用大模型的文本生成能力,实现最简单的文本生成。 这里以一个"七言绝句"生成器的简单 Demo 为例: ### 第 1 步:安装 SDK ```bash npm install @cloudbase/js-sdk ``` ### 第 2 步:初始化云开发环境 在代码中,通过以下代码进行云开发环境初始化: ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "<云开发环境ID>", accessKey: "" }); ``` 其中 `"<云开发环境ID>"` 需替换为实际云开发环境 ID,`""` 需替换为实际的 Publishable Key。初始化成功后,就可使用 `app.ai()` 调用 AI 能力。 ### 第 3 步:创建 AI 模型,并调用生成文本 ```js // 获取 AI 实例 const ai = app.ai(); // 创建模型实例,这里我们使用 DeepSeek 大模型 const model = ai.createModel("cloudbase"); // 我们先设定好 AI 的系统提示词,这里以七言绝句生成为例 const systemPrompt = "请严格按照七言绝句或七言律诗的格律要求创作,平仄需符合规则,押韵要和谐自然,韵脚字需在同一韵部。创作内容围绕用户给定的主题,七言绝句共四句,每句七个字;七言律诗共八句,每句七个字,颔联和颈联需对仗工整。同时,要融入生动的意象、丰富的情感与优美的意境,展现出古诗词的韵味与美感。"; // 用户的自然语言输入,如'帮我写一首赞美玉龙雪山的诗' const userInput = "帮我写一首赞美玉龙雪山的诗"; // 将系统提示词和用户输入,传入大模型 const res = await model.streamText({ model: "hy3", // 指定具体的模型 messages: [ { role: "system", content: systemPrompt }, { role: "user", content: userInput }, ], }); // 接收大模型的响应 // 由于大模型的返回结果是流式的,所以我们这里需要循环接收完整的响应文本。 for await (let str of res.textStream) { console.log(str); } // 输出结果: // "# 咏玉龙雪山\n" // "皑皑峻岭入云巅,玉骨冰肌傲九天。\n" // "雪影岚光添胜景,神山圣境韵绵绵。\n" ``` 可见,仅需几行代码,就可以通过云开发直接调用大模型的文本生成能力。 ## 指引二:通过 Agent(智能体)实现智能对话 通过调用大模型的文本生成接口,可以快速实现一问一答的场景。但对于一个完整的对话功能来说,仅仅有一个大模型的输入、输出还不够,还需要把大模型变为完整的 Agent,才能更好地与用户进行对话。 云开发的 AI 能力不仅提供了原始的大模型接入,还提供了 Agent 接入的能力,开发者可以在云开发上定义自己的 Agent,然后通过 Web 应用直接调用 Agent 进行对话。 ### 第 1 步:安装并初始化 SDK ```bash npm install @cloudbase/js-sdk ``` ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "<云开发环境ID>", accessKey: "" }); ``` 其中 `"<云开发环境ID>"` 需替换为实际云开发环境 ID。初始化成功后,就可使用 `app.ai()` 调用 AI 能力。 ### 第 2 步:创建一个 Agent 进入[云开发平台-AI](https://tcb.cloud.tencent.com/dev?#/ai?tab=agent),选择模板并创建一个 Agent。 您可以通过 LangChain、LangGraph 等开源框架创建 Agent,也可以对接腾讯元器、智能体开发平台、Dify 等第三方 Agent。 ![image](https://qcloudimg.tencent-cloud.cn/raw/35d6467a0399523ae4f680a0cf9b1fe6.png) 复制页面上的 AgentID,即 Agent 的唯一标识,在下面的代码中会用到。 ![image](https://qcloudimg.tencent-cloud.cn/raw/088d3d13fccb335728d687053a9fec78.png) ### 第 3 步:在 Web 应用中实现与 Agent 的对话 刚才创建了一个"小程序开发专家"的 Agent 智能体,下面来试试与它进行对话,看他能不能处理云开发常见的报错问题。 在 Web 应用中,使用以下代码直接调用刚刚我们创建的 Agent,进行对话: ```js // 获取 AI 实例 const ai = app.ai(); // 用户的输入,这里我们以某个报错信息为例 const userInput = "我的小程序这个报错是什么意思:FunctionName parameter could not be found"; const res = await ai.bot.sendMessage({ botId: "", // 第2步中获取的Agent唯一标识 msg: userInput, history: [], }); for await (let x of res.textStream) { console.log(x); } // 输出结果: // "### 报错解释\n" // "**错误信息:** `FunctionName \n" // "parameter could not be found` \n // "这个错误通常表示在调用某个函数时,\n" // "指定的函数名参数没有找到。具体来说,\n" // "可能是以下几种情况之一:\n" // …… ``` 我们也可以把对话内容记录下来,放在 history 数组中,重复调用 Agent 的接口,从而实现多轮对话: ```js const res = await ai.bot.sendMessage({ botId: "", // 第2步中获取的Agent唯一标识 msg: "请介绍一下云开发", history: [ { role: "user", content: "你好" }, { role: "assistant", content: "好的,我有什么可以帮到你" }, ], }); ``` --- # AI/快速开始/AI 工具 > 当前文档链接: https://docs.cloudbase.net/ai/quickstart/ai-tools 云开发 AI 同时兼容 OpenAI Chat Completions 和 Anthropic Messages API 协议,凡是支持自定义接口的 AI 工具(如 CodeBuddy、Cursor、Cline、Claude Code 等),都可以直接接入云开发的大模型。 ## 准备工作 1. 已创建云开发环境(旧套餐可升级),获取**环境 ID**(`ENV_ID`) 2. 在[控制台 → AI → 生文模型](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel)中开启所需模型 3. 已创建 **API Key**([控制台 → 环境配置 → API Key](https://tcb.cloud.tencent.com/dev#/env/apikey)) ## 接口配置 在 AI 工具中填写以下配置: | 配置项 | 值 | | --- | --- | | **Base URL** | `https://.api.tcloudbasegateway.com/v1/ai/cloudbase` | | **API Key** | 云开发环境的 API Key | | **模型** | `hy3`(需在控制台开启,支持其他[已开启的模型](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel)) | 将 `` 替换为实际的云开发环境 ID。 :::tip 关于 Provider 上方 Base URL 中的 `cloudbase` 是云开发统一的 provider,适用于通过**资源点套餐**支持的所有模型(DeepSeek、混元、Kimi、GLM 等)。完整的模型列表见[支持的模型列表](https://cloud.tencent.com/document/product/876/75213)。 ::: ## 在各工具中配置 ### CodeBuddy 1. 打开 CodeBuddy 设置 → **模型配置** 2. 选择「添加自定义模型」,Provider 选择 `OpenAI Compatible` 3. 填写 Base URL 和 API Key 4. 模型名称填写 `hy3` ### Cursor 1. 打开 Cursor 设置 → **Models** 2. 在 OpenAI API Key 处填写 CloudBase API Key 3. 在 Override OpenAI Base URL 处填写 Base URL 4. 在模型列表中输入 `hy3` 并启用 ### Cline / Roo Code 1. 打开插件设置 → **API Provider**,选择 `OpenAI Compatible` 2. 填写 Base URL 和 API Key 3. 填写 Model ID:`hy3` ### Claude Code Claude Code 支持通过环境变量配置自定义 API 地址,接入云开发后消耗的是云开发资源点套餐内的 Token。 设置以下环境变量后启动 Claude Code: ```bash export ANTHROPIC_BASE_URL="https://.api.tcloudbasegateway.com/v1/ai/cloudbase" export ANTHROPIC_AUTH_TOKEN="" claude ``` 也可以将环境变量写入 shell 配置文件(如 `~/.zshrc` 或 `~/.bashrc`)以永久生效: ```bash echo 'export ANTHROPIC_BASE_URL="https://.api.tcloudbasegateway.com/v1/ai/cloudbase"' >> ~/.zshrc echo 'export ANTHROPIC_AUTH_TOKEN=""' >> ~/.zshrc source ~/.zshrc ``` ### 其他支持 OpenAI 协议的工具 在工具的模型设置中,将 OpenAI 的接口地址替换为上方的 Base URL,并填写 CloudBase API Key 即可。 --- # AI/快速开始/接入 wxa-skill-eval 评测 > 当前文档链接: https://docs.cloudbase.net/ai/quickstart/wxa-skill-eval [wxa-skill-eval](https://github.com/wechat-miniprogram/ai-mode-skills) 是微信官方提供的小程序 AI Skill **端到端评测工具**,能自动模拟真实用户对话,对 Skill 的意图理解、轨迹生成和最终答案质量进行全方位评估,并输出多维度评测报告。 评测工具本身**不内置**大模型服务,需要开发者自行提供模型接入配置。云开发大模型兼容 OpenAI Chat Completions 协议,可以直接用于 wxa-skill-eval,无需额外注册其他模型服务商。 ## 准备工作 1. 已创建云开发环境(旧套餐可升级),获取**环境 ID**(`ENV_ID`) 2. 在[控制台 → AI → 生文模型](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel)中开启所需模型(推荐使用 `hy3` 或其他高智能度模型,以获得更准确的评测结果) 3. 已创建 **API Key**([控制台 → 环境配置 → API Key](https://tcb.cloud.tencent.com/dev#/env/apikey)) ## 安装 wxa-skill-eval 从 GitHub 仓库下载 [ai-mode-skills](https://github.com/wechat-miniprogram/ai-mode-skills),进入 `wxa-skills-eval` 目录安装依赖: ```bash cd wxa-skills-eval pnpm install ``` ## 配置 .env {#config-env} 在 `wxa-skills-eval` 目录下创建 `.env` 文件,填入云开发大模型的配置: ```dotenv BASE_URL=https://.api.tcloudbasegateway.com/v1/ai/cloudbase API_KEY= MODEL=hy3 ``` 将 `` 替换为云开发环境 ID,`` 替换为对应的 API Key。 :::tip 关于模型选择 `MODEL` 字段填写在[控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel)中已开启的模型名称。评测工具需要调用大模型来模拟用户对话,建议选用**参数量较大、智能度较高**的模型,以获得更准确的评测效果。 资源点套餐当前支持以下模型: | 模型 ID | 提供商 | | --- | --- | | `hy3` | 腾讯混元 | | `deepseek-v4-flash-202605` | DeepSeek(原厂直供) | | `deepseek-v4-pro-202606` | DeepSeek(原厂直供) | | `deepseek-v4-flash` | DeepSeek | | `deepseek-v4-pro` | DeepSeek | | `deepseek-v3.2` | DeepSeek | | `glm-5.1` | 智谱清言 | | `glm-5v-turbo` | 智谱清言 | | `glm-5-turbo` | 智谱清言 | | `glm-5` | 智谱清言 | | `kimi-k2.6` | Moonshot | | `kimi-k2.5` | Moonshot | | `minimax-m3` | MiniMax | | `minimax-m2.7` | MiniMax | | `minimax-m2.5` | MiniMax | | `qwen3.5-flash` | 阿里 | | `qwen3.5-plus` | 阿里 | 使用前需在控制台开启对应模型,并确保已开通资源点套餐。 ::: :::tip 关于 BASE_URL URL 中的 `cloudbase` 是云开发统一的 provider,适用于通过**资源点套餐**支持的所有模型(DeepSeek、混元、Kimi、GLM 等)。 ::: ## 运行评测 根据需要选择以下任意一种方式启动评测: **Web UI 模式**(推荐,可视化操作): ```bash pnpm dev:web ``` **CLI 模式**: ```bash pnpm dev ``` ## 评测报告 评测完成后,工具会生成 `eval_report.html` 报告文件,包含以下维度的评估结果: | 维度 | 说明 | | --- | --- | | 意图理解 | Skill 对用户指令的理解准确度 | | 轨迹生成 | 操作路径的合理性与完整性 | | 最终答案质量 | 输出结果的正确性与质量 | | 接口覆盖率 | 原子接口与组件的测试覆盖情况 | 建议每个 Skill **至少运行 30 个测试用例**以保证充分覆盖。 :::note wxa-skill-eval 仅供**开发阶段**自测使用,评测结果不作为微信小程序审核的依据。微信官方的上线评测标准将另行公布。 ::: ## 相关文档 - [接入大模型](../model/overview) - [支持的模型列表](https://cloud.tencent.com/document/product/876/75213) - [wxa-skill-eval GitHub 仓库](https://github.com/wechat-miniprogram/ai-mode-skills) - [微信小程序 AI Skill 评测指南](https://developers.weixin.qq.com/miniprogram/dev/ai/evaluation-guide.html) --- # AI/快速开始/第三方模型 > 当前文档链接: https://docs.cloudbase.net/ai/quickstart/third-party-model 本文介绍如何在云开发中调用控制台上配置的**第三方模型**(如 DeepSeek、智谱、自建 OpenAI 兼容服务、第三方生图服务等)。 ## 准备工作 1. 进入 [云开发 AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=ai-model) → 选择**第三方模型**。 2. 点击「添加模型提供商」,填写 **模型提供商标识**、BaseURL、API Key 等信息。 3. 保存后,在调用 SDK 时使用控制台填写的「模型提供商标识」作为入参。 :::tip 配置自定义模型的完整步骤请参考 [接入大模型](../model/overview)。 ::: ## 生文模型 调用 `createModel()` 时,将参数替换为控制台上的「模型提供商标识」。下面以 `my-deepseek` 为例: ```js // 入参 'my-deepseek' 即控制台上配置的「模型提供商标识」 const model = ai.createModel("my-deepseek"); const res = await model.generateText({ model: "deepseek-chat", // 实际的模型名 messages: [ { role: "user", content: "介绍一下李白" }, ], }); console.log(res.text); ``` 流式调用使用 `streamText()`: ```js const model = ai.createModel("my-deepseek"); const res = await model.streamText({ model: "deepseek-chat", messages: [{ role: "user", content: "写一首七言绝句" }], }); for await (const chunk of res.textStream) { console.log(chunk); } ``` 更多用法详见 [Node SDK 调用](../model/nodejs-access)。 ## 生图模型 调用 `createImageModel()` 时,将参数替换为控制台上的「模型提供商标识」。下面以 `my-image-provider` 为例: ```js const imageModel = ai.createImageModel("my-image-provider"); const res = await imageModel.generateImage({ model: "your-model-name", // 实际的模型名 prompt: "一只可爱的猫咪在草地上玩耍", }); console.log(res.data[0].url); ``` ### 自定义 API 子路径 {#custom-sub-url} SDK 默认使用生成路径 `images/ar/generations`。如果第三方服务的图片生成接口路径不同,可通过 `generateImageSubUrlConfig` 显式指定: ```js const imageModel = ai.createImageModel("my-image-provider"); // 第一层 key 为「模型提供商标识」,第二层 key 为「模型名称」 imageModel.generateImageSubUrlConfig["my-image-provider"] = [[/^custom-model$/, 'images/custom/generations']]; const res = await imageModel.generateImage({ model: "custom-model", prompt: "一只可爱的猫咪在草地上玩耍", }); ``` 最终请求地址为: ``` https://.api.tcloudbasegateway.com/v1/ai/<模型提供商标识>/<子路径> ``` 更多生图能力详见 [Node SDK 调用](../image-model/node-sdk)。 ## 相关文档 - [接入大模型](../model/overview) - [SDK 参考 → createModel](../sdk-reference/api#createmodel) - [SDK 参考 → createImageModel](../sdk-reference/api#createimagemodel) --- # AI/文本生成/概述 > 当前文档链接: https://docs.cloudbase.net/ai/model/overview 云开发 AI 提供开箱即用的文本生成能力,支持 DeepSeek、混元、MiniMax、Kimi、GLM 等主流大模型,覆盖以下核心功能: | 功能 | 说明 | | ---- | ---- | | **多种调用方式** | 小程序 SDK、wx-server-sdk、Web SDK、Node SDK、cURL、OpenAI SDK、Anthropic SDK | | **[多轮对话](./multi-turn)** | 携带历史消息实现连续对话 | | **[流式输出](./streaming)** | 逐 token 返回,提升用户体验 | | **[深度思考](./deep-thinking)** | 调用支持推理的模型获取思考过程 | | **[多模态理解](./multimodal)** | 输入图片、视频、文件等让模型理解 | | **[上下文缓存](./cache)** | 缓存长 prompt 前缀,降低重复调用成本 | | **[上下文管理](./context-management)** | 自动截断或压缩超长对话历史 | | **[工具调用](./tool-calling)** | 让模型调用自定义函数/工具 | ## 接入方式 {#access} 云开发提供三种大模型接入方式: | 方式 | 说明 | 适用套餐/场景 | | ---- | ---- | -------- | | **资源点套餐** | 使用套餐内资源点抵扣 Token,支持 DeepSeek、MiniMax、混元、Kimi、GLM 等模型 | 资源点套餐 | | **体验模型** | 免费使用平台内置混元模型 | [小程序成长计划](../ai-inspire-plan.md) | | **自定义模型** | 配置任意兼容 OpenAI / Anthropic 协议的模型 | 标准版及以上 | ## 资源点套餐 {#资源点套餐} :::info Token 资源包已于 2026 年 6 月 18 日停售。如当前套餐为非资源点套餐,需先前往 [云开发控制台 → 套餐管理](https://tcb.cloud.tencent.com/dev#/env/package-usage/basic) 切换为资源点套餐。 ::: 资源点套餐内的资源点可用于抵扣大模型 Token 用量,支持 DeepSeek、MiniMax、混元、Kimi、GLM 等最新模型,详见[支持的模型列表](https://cloud.tencent.com/document/product/876/75213)。 [前往开通资源点套餐](https://tcb.cloud.tencent.com/dev#/env/package-usage/basic) ## 体验模型 体验模型**仅供[小程序成长计划](../ai-inspire-plan.md)**使用。目前仅支持**混元**,如需使用 DeepSeek 等其他模型,请切换为资源点套餐。 目前提供以下混元模型: | 模型 | 说明 | | ---- | ---- | | `hy3` | 混元指令模型 | ## 自定义模型 **云开发标准版及以上套餐**支持配置第三方模型,配置后可在云开发 JS SDK 和小程序 SDK 中直接调用。 :::tip 支持的协议 自定义模型支持兼容以下协议的大模型接口,详见[云开发 LLM API 支持协议介绍](../ai-tools/protocol): - **Chat Completions API**(OpenAI 兼容,最通用) - **Responses API** - **Anthropic Messages API** ::: ### 所需参数 | 参数 | 说明 | | ---- | ---- | | **BaseURL** | 大模型 API 的请求地址 | | **APIKey** | 访问大模型 API 的密钥 | | **模型名称** | 具体的模型标识符,如 `deepseek-v4-flash`、`deepseek-v3` 等 | ### 配置步骤 1. **获取 BaseURL 和 API Key** 以下提供混元、DeepSeek 的接入参考,其他模型请查阅对应服务商文档: | 模型 | 模型提供商 | BaseURL | 模型名称 | 计费相关 | | ---- | ---------- | ------- | -------- | -------- | | hunyuan | [腾讯混元大模型](https://cloud.tencent.com/document/product/1729/111007) | `https://api.hunyuan.cloud.tencent.com/v1` | 参考 [混元模型列表](https://cloud.tencent.com/document/product/1729/104753#5302f6cb-c29a-4e77-a579-2c2053122c1e) | 参考 [计费文档](https://cloud.tencent.com/document/product/1729/106126) | | DeepSeek | [DeepSeek 官方](https://api-docs.deepseek.com/zh-cn/) | `https://api.deepseek.com/v1` | 参考 [DeepSeek 模型列表](https://api-docs.deepseek.com/zh-cn/quick_start/pricing) | 参考 [DeepSeek 文档](https://api-docs.deepseek.com/zh-cn/) | 2. **在平台中配置** 进入 [云开发 AI+](https://tcb.cloud.tencent.com/dev#/ai?tab=ai-model) 控制台,有两种方式: - **已支持的平台**:直接选择对应平台,填写 API Key 即可 ![选择平台配置 API Key](https://qcloudimg.tencent-cloud.cn/raw/f356fcea13d6868a3e3660e15bb241e7.png) - **其他模型**:点击「添加模型提供商」按钮,填写 BaseURL、API Key 和模型名称 ![添加模型提供商](https://qcloudimg.tencent-cloud.cn/raw/522714aa072542a20e9dea9240616190.png) ## 常见问题 ### 如何判断大模型是否兼容支持的协议? 云开发支持 Chat Completions、Responses API、Anthropic Messages API 三种协议。绝大多数主流大模型(DeepSeek、GLM、Kimi、通义千问等)都兼容 Chat Completions 协议,通常在其 API 文档中会明确说明"兼容 OpenAI 接口"。 ### 配置后无法正常使用大模型怎么办? 请检查以下几点: - API Key 是否正确且未过期 - BaseURL 是否准确 - 模型名称是否与提供商文档中的一致 - 是否已在第三方平台开启付费或完成充值 ### 大模型支持多少并发? - 使用**体验模型**时,一个环境支持 5 个并发。 - 配置了**自定义模型**后,平台不再限制并发,实际并发上限取决于第三方模型提供商的限制。 --- # AI/文本生成/功能介绍/多轮对话 > 当前文档链接: https://docs.cloudbase.net/ai/model/multi-turn 多轮对话是大模型最常见的使用场景之一。本文介绍如何构建多轮上下文、消息格式规范以及生产环境中的历史管理策略。 ## 工作原理 {#how-it-works} 大模型 API 是**无状态**的——服务端不保存任何对话历史。每次请求都是独立的,模型不会"记住"之前的对话内容。 要实现多轮对话,需要在每次请求时将**完整的历史消息**作为输入传给模型: ``` 第 1 轮:messages = [用户消息1] 第 2 轮:messages = [用户消息1, 助手回复1, 用户消息2] 第 3 轮:messages = [用户消息1, 助手回复1, 用户消息2, 助手回复2, 用户消息3] ``` 每一轮的 `messages` 数组都包含了之前所有的对话记录,模型根据完整上下文生成回复。 :::note 这意味着随着对话轮次增加,输入的 Token 数量会持续增长,直接影响调用成本和响应速度。后文的[历史管理策略](#history-management)会介绍如何解决这个问题。 ::: ## 消息格式 {#message-format} 消息列表 `messages` 是一个数组,每条消息包含 `role`(角色)和 `content`(内容)两个核心字段: ```json [ { "role": "system", "content": "你是一个专业的翻译助手" }, { "role": "user", "content": "把「你好」翻译成英文" }, { "role": "assistant", "content": "Hello" }, { "role": "user", "content": "再翻译成日文" } ] ``` ### 角色说明 | 角色 | 说明 | 数量 | | --- | --- | --- | | `system` | 系统提示,设定模型的行为和背景知识 | 0 或 1 条,放在最前面 | | `user` | 用户输入的消息 | 至少 1 条 | | `assistant` | 模型的回复 | 由模型生成,多轮时需回传 | | `tool` | 工具调用的执行结果 | 仅工具调用场景使用 | ### 内容格式 `content` 字段支持两种格式: **纯文本(最常用):** ```json { "role": "user", "content": "介绍一下李白" } ``` **多模态内容(图片理解等场景):** ```json { "role": "user", "content": [ { "type": "text", "text": "这张图片里有什么?" }, { "type": "image_url", "image_url": { "url": "https://example.com/photo.png" } } ] } ``` ## 快速开始 {#quick-start} :::tip 协议说明 以下示例基于 **Chat Completions 协议**进行演示。多轮对话的核心机制(维护完整 messages 数组)适用于所有协议,包括 CloudBase SDK、OpenAI SDK 和 Anthropic SDK 兼容协议。各协议的接入方式请参考[接入方式](./miniprogram-access)文档。 ::: 使用 CloudBase SDK(Web / Node.js)实现多轮对话: ```js const model = ai.createModel("cloudbase"); // 维护消息历史 const messages = [ { role: "system", content: "你是一个诗词专家" } ]; async function chat(userInput) { // 1. 追加用户消息 messages.push({ role: "user", content: userInput }); // 2. 调用模型 const result = await model.generateText({ model: "hy3", messages }); // 3. 追加助手回复到历史 messages.push({ role: "assistant", content: result.text }); return result.text; } // 多轮对话 await chat("李白最著名的诗是什么?"); // → "李白最著名的诗之一是《静夜思》..." await chat("这首诗的创作背景是什么?"); // → "《静夜思》创作于唐玄宗开元十四年(726年)..." // 模型能理解"这首诗"指的是上一轮提到的《静夜思》 ``` :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: 使用 OpenAI SDK 实现多轮对话: ```js const OpenAI = require("openai"); const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); const messages = [ { role: "system", content: "你是一个诗词专家" } ]; async function chat(userMessage) { messages.push({ role: "user", content: userMessage }); const completion = await client.chat.completions.create({ model: "hy3", messages }); const assistantMessage = completion.choices[0].message; messages.push(assistantMessage); return assistantMessage.content; } await chat("李白最著名的诗是什么?"); await chat("这首诗的创作背景是什么?"); ``` :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: 使用 HTTP API 实现多轮对话,每次请求需携带完整历史: ```bash curl -X POST 'https://.api.tcloudbasegateway.com/v1/ai/cloudbase/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "model": "hy3", "messages": [ {"role": "system", "content": "你是一个诗词专家"}, {"role": "user", "content": "李白最著名的诗是什么?"}, {"role": "assistant", "content": "李白最著名的诗之一是《静夜思》..."}, {"role": "user", "content": "这首诗的创作背景是什么?"} ] }' ``` 微信小程序中实现多轮对话: ```js Page({ data: { chatHistory: [], // 消息历史 inputValue: "" }, async sendMessage() { const { inputValue, chatHistory } = this.data; if (!inputValue.trim()) return; // 构建消息列表 const messages = [ { role: "system", content: "你是一个诗词专家" }, ...chatHistory, { role: "user", content: inputValue } ]; const model = wx.cloud.extend.AI.createModel("cloudbase"); let assistantContent = ""; const res = await model.streamText({ data: { model: "hy3", messages } }); for await (const text of res.textStream) { assistantContent += text; this.setData({ currentReply: assistantContent }); } // 更新历史 this.setData({ chatHistory: [ ...chatHistory, { role: "user", content: inputValue }, { role: "assistant", content: assistantContent } ], inputValue: "" }); } }); ``` ## 流式多轮对话 {#streaming-multi-turn} 在流式场景中,需要等流式输出完成后,再将完整的助手回复追加到历史中: ```js const model = ai.createModel("cloudbase"); const messages = [ { role: "system", content: "你是一个有帮助的助手" } ]; async function chatStream(userInput) { messages.push({ role: "user", content: userInput }); const res = await model.streamText({ model: "hy3", messages }); // 逐步输出文本 let fullText = ""; for await (const text of res.textStream) { fullText += text; process.stdout.write(text); // 实时输出 } // 流结束后,将完整回复追加到历史 messages.push({ role: "assistant", content: fullText }); return fullText; } ``` ## 历史管理策略 {#history-management} 随着对话轮次增加,`messages` 数组会越来越长,带来两个问题: 1. **Token 消耗增长**:每轮对话的输入 Token = 所有历史消息的 Token + 当前新消息的 Token 2. **超出上下文窗口**:当历史总 Token 超过模型的最大上下文长度时,请求会失败 以下是常见的管理策略: ### 策略一:上下文截断 {#truncation} 保留最近 N 轮对话,丢弃更早的历史。实现简单,适合大多数场景。 ```js const MAX_ROUNDS = 10; // 保留最近 10 轮 function trimMessages(messages) { // 始终保留 system 消息 const systemMsg = messages.find(m => m.role === "system"); const history = messages.filter(m => m.role !== "system"); // 每轮 = 1条user + 1条assistant = 2条消息 const trimmed = history.slice(-MAX_ROUNDS * 2); return systemMsg ? [systemMsg, ...trimmed] : trimmed; } // 每次调用前截断 const trimmedMessages = trimMessages(messages); const result = await model.generateText({ model: "hy3", messages: trimmedMessages }); ``` ### 策略二:Token 预算控制 {#token-budget} 根据 Token 数量而非轮次来控制历史长度,更精确。 ```js const MAX_INPUT_TOKENS = 8000; // 预留 8000 token 给输入 function trimByTokens(messages, maxTokens) { const systemMsg = messages.find(m => m.role === "system"); const history = messages.filter(m => m.role !== "system"); let totalTokens = estimateTokens(systemMsg?.content || ""); const result = systemMsg ? [systemMsg] : []; // 从最新的消息开始,往前累加 for (let i = history.length - 1; i >= 0; i--) { const msgTokens = estimateTokens(history[i].content); if (totalTokens + msgTokens > maxTokens) break; totalTokens += msgTokens; result.splice(systemMsg ? 1 : 0, 0, history[i]); } return result; } // 粗略估算 Token 数(中文约 1.5 token/字,英文约 0.75 token/word) function estimateTokens(text) { if (!text) return 0; return Math.ceil(text.length * 1.5); } ``` ### 策略三:滚动摘要 {#rolling-summary} 当历史过长时,用模型对早期对话生成摘要,压缩历史但保留核心信息。 ```js async function summarizeHistory(messages) { const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", messages: [ { role: "user", content: `请用一段话简要概括以下对话的关键信息:\n\n${ messages.map(m => `${m.role}: ${m.content}`).join("\n") }` } ] }); return result.text; } // 当历史超过阈值时,压缩早期对话为摘要 async function manageHistory(messages, maxRounds = 10) { const systemMsg = messages.find(m => m.role === "system"); const history = messages.filter(m => m.role !== "system"); if (history.length <= maxRounds * 2) return messages; // 将早期对话压缩为摘要 const earlyHistory = history.slice(0, -maxRounds * 2); const recentHistory = history.slice(-maxRounds * 2); const summary = await summarizeHistory(earlyHistory); return [ ...(systemMsg ? [systemMsg] : []), { role: "system", content: `[历史对话摘要] ${summary}` }, ...recentHistory ]; } ``` ### 策略对比 | 策略 | 优点 | 缺点 | 适用场景 | | --- | --- | --- | --- | | 上下文截断 | 实现简单,零额外开销 | 丢失早期信息 | 简单问答、客服 | | Token 预算控制 | 精确控制成本 | 实现稍复杂 | 对成本敏感的场景 | | 滚动摘要 | 保留核心信息 | 需额外 API 调用 | 长期对话、复杂任务 | ## 思考模型的多轮对话 {#thinking-model} 使用深度思考模型(如 `deepseek-r1`)时,模型会返回 `reasoning_content`(思考过程)和 `content`(最终回答)两个字段。 **关键规则:更新 messages 时只保留 `content`,忽略 `reasoning_content`。** ```js const OpenAI = require("openai"); const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); const messages = []; async function chatWithThinking(userMessage) { messages.push({ role: "user", content: userMessage }); const completion = await client.chat.completions.create({ model: "deepseek-r1", messages }); const choice = completion.choices[0]; // ⚠️ reasoning_content 是思考过程,不要追加到 messages 中 console.log("思考过程:", choice.message.reasoning_content); console.log("最终回答:", choice.message.content); // ✅ 只将 content 追加到历史 messages.push({ role: "assistant", content: choice.message.content }); return choice.message.content; } ``` :::warning 将 `reasoning_content` 追加到 messages 会导致后续请求格式异常或回复质量下降。思考过程仅用于展示,不参与上下文传递。 ::: ## 成本优化建议 {#cost-optimization} | 优化方向 | 做法 | | --- | --- | | 精简 system prompt | 用简短精确的指令替代冗长描述 | | 控制历史长度 | 使用上述截断或摘要策略 | | 选择合适的模型 | 简单任务用 `hy3`,复杂推理用 `deepseek-r1` | | 使用缓存 | 对于固定的长 system prompt,利用[提示词缓存](./cache)降低重复计费 | --- # AI/文本生成/功能介绍/流式输出 > 当前文档链接: https://docs.cloudbase.net/ai/model/streaming 流式输出(Streaming)让大模型在生成过程中逐步返回文本,无需等待完整响应。用户可以实时看到内容生成,大幅提升交互体验。 ## 工作原理 {#how-it-works} 流式输出基于 **SSE(Server-Sent Events)** 协议。服务端在生成过程中持续推送数据块(chunk),每个 chunk 包含一小段新生成的文本: ``` data: {"choices":[{"delta":{"content":"你"}}]} data: {"choices":[{"delta":{"content":"好"}}]} data: {"choices":[{"delta":{"content":"!"}}]} data: [DONE] ``` 客户端逐个接收这些 chunk,拼接后展示给用户,实现"打字机"效果。 ### 流式 vs 非流式 | 对比项 | 非流式(generateText) | 流式(streamText) | | --- | --- | --- | | 响应方式 | 等待生成完毕后一次性返回 | 边生成边返回 | | 首字延迟 | 较高(需等完整生成) | 极低(几百毫秒内出首字) | | 用户体验 | 有明显等待感 | 实时反馈,体验流畅 | | 适用场景 | 后端处理、批量任务 | 实时对话、长文本生成 | ## 快速开始 {#quick-start} ```js const model = ai.createModel("cloudbase"); const res = await model.streamText({ model: "hy3", messages: [{ role: "user", content: "写一首关于春天的诗" }] }); // 方式一:迭代文本流(推荐,仅获取文本内容) for await (const text of res.textStream) { process.stdout.write(text); } // 方式二:迭代数据流(获取完整响应信息) for await (const data of res.dataStream) { console.log(data.choices[0]?.delta?.content); } // 流结束后获取汇总信息 const messages = await res.messages; const usage = await res.usage; ``` :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: ```js const OpenAI = require("openai"); const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); const stream = await client.chat.completions.create({ model: "hy3", messages: [{ role: "user", content: "写一首关于春天的诗" }], stream: true }); for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ""; process.stdout.write(content); } ``` :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: ```bash curl -X POST 'https://.api.tcloudbasegateway.com/v1/ai/cloudbase/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "model": "hy3", "messages": [{"role": "user", "content": "写一首关于春天的诗"}], "stream": true }' ``` 响应格式(SSE): ``` data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"春"},"index":0}]} data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"风"},"index":0}]} data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"拂面"},"index":0}]} ... data: [DONE] ``` ```js const model = wx.cloud.extend.AI.createModel("cloudbase"); const res = await model.streamText({ data: { model: "hy3", messages: [{ role: "user", content: "写一首关于春天的诗" }] } }); let fullText = ""; for await (const text of res.textStream) { fullText += text; this.setData({ reply: fullText }); } ``` ## 前端消费流式数据 {#frontend-best-practices} ### Web 应用中的流式渲染 在 Web 应用中,推荐使用 CloudBase SDK 的 `textStream` 结合 DOM 更新: ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "您的环境ID", accessKey: "" }); async function streamChat(userInput) { const ai = app.ai(); const model = ai.createModel("cloudbase"); const res = await model.streamText({ model: "hy3", messages: [{ role: "user", content: userInput }] }); const outputEl = document.getElementById("output"); let fullText = ""; for await (const text of res.textStream) { fullText += text; outputEl.textContent = fullText; } } ``` ### React 中的流式渲染 ```jsx import { useState, useCallback } from "react"; function ChatComponent({ ai }) { const [reply, setReply] = useState(""); const [loading, setLoading] = useState(false); const sendMessage = useCallback(async (input) => { setLoading(true); setReply(""); const model = ai.createModel("cloudbase"); const res = await model.streamText({ model: "hy3", messages: [{ role: "user", content: input }] }); let text = ""; for await (const chunk of res.textStream) { text += chunk; setReply(text); } setLoading(false); }, [ai]); return (
{reply}
{loading && }
); } ``` ### Markdown 流式渲染 大模型输出通常是 Markdown 格式。直接渲染未完成的 Markdown 会导致格式闪烁(如未关闭的代码块)。推荐方案: ```jsx import ReactMarkdown from "react-markdown"; import { useState } from "react"; function StreamMarkdown({ ai, input }) { const [content, setContent] = useState(""); async function generate() { const model = ai.createModel("cloudbase"); const res = await model.streamText({ model: "hy3", messages: [{ role: "user", content: input }] }); let text = ""; for await (const chunk of res.textStream) { text += chunk; setContent(text); } } return {content}; } ``` :::tip 如果遇到渲染闪烁问题,可以设置一个小的缓冲区(如每收到 3-5 个 chunk 才更新一次 DOM),平衡实时性和渲染稳定性。 ::: ## 取消请求 {#abort} 用户点击"停止生成"时,需要中止流式请求: ### 使用 AbortController ```js const controller = new AbortController(); // 发起流式请求 const model = ai.createModel("cloudbase"); const res = await model.streamText({ model: "hy3", messages: [{ role: "user", content: "写一篇长文章" }], signal: controller.signal // 传入 abort signal }); // 用户点击"停止" document.getElementById("stop-btn").onclick = () => { controller.abort(); }; // 消费流时捕获中止 try { for await (const text of res.textStream) { outputEl.textContent += text; } } catch (err) { if (err.name === "AbortError") { console.log("用户取消了生成"); } } ``` ### OpenAI SDK 中取消 ```js const stream = await client.chat.completions.create({ model: "hy3", messages: [{ role: "user", content: "写一篇长文章" }], stream: true }); // 提前终止 for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ""; process.stdout.write(content); if (shouldStop) { stream.controller.abort(); break; } } ``` ## 错误处理 {#error-handling} 流式请求可能在传输过程中断开,需要妥善处理: ```js async function streamWithRetry(messages, maxRetries = 3) { const model = ai.createModel("cloudbase"); for (let attempt = 0; attempt < maxRetries; attempt++) { try { const res = await model.streamText({ model: "hy3", messages }); let fullText = ""; for await (const text of res.textStream) { fullText += text; } return fullText; } catch (err) { if (attempt === maxRetries - 1) throw err; // 等待后重试 await new Promise(r => setTimeout(r, 1000 * (attempt + 1))); } } } ``` ### 常见错误 | 错误类型 | 原因 | 处理建议 | | --- | --- | --- | | 网络中断 | 网络不稳定 | 重试,或提示用户检查网络 | | 超时 | 生成时间过长 | 增加 timeout 配置 | | Token 超限 | 输入 + 输出超过模型上下文窗口 | 截断历史消息 | | 429 限流 | 请求频率过高 | 指数退避重试 | ## 服务端流式转发 {#server-side-streaming} 在服务端接收流式响应并转发给前端(如 Express / Koa): ```js const express = require("express"); const OpenAI = require("openai"); const app = express(); const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); app.post("/api/chat", async (req, res) => { const { messages } = req.body; // 设置 SSE 响应头 res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); const stream = await client.chat.completions.create({ model: "hy3", messages, stream: true }); for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content; if (content) { res.write(`data: ${JSON.stringify({ content })}\n\n`); } } res.write("data: [DONE]\n\n"); res.end(); }); ``` --- # AI/文本生成/功能介绍/深度思考 > 当前文档链接: https://docs.cloudbase.net/ai/model/deep-thinking 深度思考(Deep Thinking / Reasoning)是部分大模型提供的增强推理能力。模型在回答前会先进行内部"思考",输出推理过程后再给出最终答案,适合数学、逻辑、代码分析等复杂任务。 **启用方式:** 在请求中添加 `reasoning_effort` 参数即可开启深度思考。该参数支持三个级别: | 值 | 说明 | 适用场景 | | --- | --- | --- | | `low` | 轻度思考,推理步数少 | 中等复杂度任务 | | `medium` | 平衡模式 | 大多数日常任务 | | `high` | 深度思维链推理 | 数学、编程、复杂逻辑推理 | 不传该参数时,`hy3` 默认不启用深度思考(快思考模式)。 ## 适用模型 {#supported-models} | 模型 | 默认行为 | 说明 | | --- | --- | --- | | `hy3` | 默认不思考,通过 `reasoning_effort` 按需开启 | 推荐,灵活控制推理深度 | | `deepseek-r1` | 默认始终思考 | 无需额外参数,始终输出 `reasoning_content` | 具体支持情况请参考[接入大模型](./overview)中的模型列表。 :::note 使用不支持深度思考的模型时传入相关参数不会报错,但不会产生思考内容。 ::: ## 使用方式 {#usage} ### 各协议开启方式 {#protocols} 云开发 AI 支持多种协议接入,不同协议开启深度思考的参数不同: | 协议 | 开启参数 | 说明 | | --- | --- | --- | | **Chat Completions** | `reasoning_effort: "low" / "medium" / "high"` | OpenAI 标准协议,下方示例默认使用此协议 | | **Anthropic Messages** | `thinking: { type: "enabled", budget_tokens: N }` | Anthropic 标准协议,`budget_tokens` 控制最大思考 token 数 | | **OpenAI Responses** | `reasoning: { effort: "low" / "medium" / "high" }` | OpenAI Responses 协议 |
Anthropic Messages 协议示例 ```js const Anthropic = require("@anthropic-ai/sdk"); const client = new Anthropic({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); const response = await client.messages.create({ model: "hy3", max_tokens: 8000, thinking: { type: "enabled", budget_tokens: 5000 // 最大思考 token 数 }, messages: [{ role: "user", content: "证明 √2 是无理数" }] }); for (const block of response.content) { if (block.type === "thinking") { console.log("思考过程:", block.thinking); } else if (block.type === "text") { console.log("最终回答:", block.text); } } ``` 接入方式详见 [Anthropic SDK 调用](./anthropic-sdk-access)。 在 CodeSandbox 中打开完整示例代码 →
OpenAI Responses 协议示例 ```js const OpenAI = require("openai"); const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); const response = await client.responses.create({ model: "hy3", reasoning: { effort: "high" }, input: "证明 √2 是无理数" }); for (const item of response.output) { if (item.type === "reasoning") { console.log("思考过程:", item.summary.map(s => s.text).join("")); } else if (item.type === "message") { console.log("最终回答:", item.content.map(c => c.text).join("")); } } ``` 接入方式详见 [OpenAI SDK 调用](./openai-sdk-access)。 在 CodeSandbox 中打开完整示例代码 →
以下示例基于 **Chat Completions 协议**: ```js const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", reasoning_effort: "high", // 开启深度思考:low / medium / high messages: [{ role: "user", content: "证明 √2 是无理数" }] }); // 通过 rawResponses 获取思考内容 const rawResponse = result.rawResponses[0]; const message = rawResponse.choices[0].message; console.log("思考过程:", message.reasoning_content); console.log("最终回答:", result.text); ``` :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: **非流式调用:** ```js const OpenAI = require("openai"); const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); const completion = await client.chat.completions.create({ model: "hy3", messages: [{ role: "user", content: "证明 √2 是无理数" }], reasoning_effort: "high" // 开启深度思考:low / medium / high }); const message = completion.choices[0].message; console.log("思考过程:", message.reasoning_content); console.log("最终回答:", message.content); ``` **流式调用:** ```js const stream = await client.chat.completions.create({ model: "hy3", messages: [{ role: "user", content: "证明 √2 是无理数" }], reasoning_effort: "high", stream: true }); let reasoning = ""; let answer = ""; for await (const chunk of stream) { const delta = chunk.choices[0]?.delta; // 思考过程 if (delta?.reasoning_content) { reasoning += delta.reasoning_content; process.stdout.write(`[思考] ${delta.reasoning_content}`); } // 最终回答 if (delta?.content) { answer += delta.content; process.stdout.write(delta.content); } } ``` :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: ```bash curl -X POST 'https://.api.tcloudbasegateway.com/v1/ai/cloudbase/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "model": "hy3", "reasoning_effort": "high", "messages": [ {"role": "user", "content": "证明 √2 是无理数"} ] }' ``` ## 输出格式 {#output-format} 启用深度思考后,模型返回两部分内容: | 字段 | 说明 | 用途 | | --- | --- | --- | | `reasoning_content` | 思考过程 | 展示给用户看(可折叠),帮助理解模型推理逻辑 | | `content` | 最终回答 | 实际使用的结果 | **非流式响应示例:** ```json { "choices": [{ "message": { "role": "assistant", "reasoning_content": "用户问的是 28 的质因数分解。我需要逐步分解:28 ÷ 2 = 14,14 ÷ 2 = 7,7 是质数。所以 28 = 2² × 7。", "content": "28 的质因数分解为 2² × 7。\n\n分解步骤:\n1. 28 ÷ 2 = 14\n2. 14 ÷ 2 = 7\n3. 7 是质数,停止\n\n因此 28 = 2 × 2 × 7 = 2² × 7" }, "finish_reason": "stop" }] } ``` ## 前端展示建议 {#frontend-display} 思考过程通常较长,建议以可折叠的方式展示: ```jsx function ThinkingMessage({ reasoning, content }) { const [expanded, setExpanded] = useState(false); return (
{reasoning && (
{expanded && (
{reasoning}
)}
)}
{content}
); } ``` ## 多轮对话中的注意事项 {#multi-turn-notes} 使用深度思考模型进行多轮对话时,**不要将 `reasoning_content` 追加到 messages 历史中**: ```js const messages = []; async function chat(userMessage) { messages.push({ role: "user", content: userMessage }); const completion = await client.chat.completions.create({ model: "hy3", messages, reasoning_effort: "high" }); const choice = completion.choices[0].message; // ✅ 正确:只追加 content messages.push({ role: "assistant", content: choice.content }); // ❌ 错误:不要追加 reasoning_content // messages.push({ // role: "assistant", // content: choice.reasoning_content + choice.content // }); return { reasoning: choice.reasoning_content, answer: choice.content }; } ``` :::warning 将思考过程追加到 messages 会导致: 1. 输入 Token 快速膨胀(思考过程通常很长) 2. 模型回复质量下降 3. 可能出现格式异常 ::: ## 适用场景 {#use-cases} | 场景 | 推荐 | 原因 | | --- | --- | --- | | 数学证明 / 推理 | ✅ 推荐 | 需要严谨的逐步推理 | | 代码 Bug 分析 | ✅ 推荐 | 需要追踪执行流程 | | 复杂逻辑判断 | ✅ 推荐 | 需要考虑多种条件 | | 简单问答 | ❌ 不推荐 | 增加不必要的延迟和成本 | | 实时对话 | ❌ 不推荐 | 思考过程导致首字延迟较高 | | 创意写作 | ⚠️ 视情况 | 短文案不需要,长篇规划可能有帮助 | ## 成本与性能 {#cost-and-performance} | 指标 | 深度思考模型 | 普通模型 | | --- | --- | --- | | 首字延迟 | 较高(需完成思考) | 较低 | | 输出 Token | 较多(含思考内容) | 较少 | | 回答准确性 | 较高(复杂任务) | 一般 | | 计费 | 思考 Token 也计费 | 仅回答内容计费 | :::tip 根据任务复杂度选择推理深度:简单任务不传 `reasoning_effort`(快思考),复杂推理任务设为 `"high"`(深度思考)。 ::: --- # AI/文本生成/功能介绍/多模态理解 > 当前文档链接: https://docs.cloudbase.net/ai/model/multimodal 多模态理解(Multimodal Understanding)允许模型在同一次对话中接收文本之外的输入——图片、视频、文件等,并基于这些内容回答问题。常见场景:图片描述、截图问答、视频摘要、文档抽取、OCR 识别等。 :::tip 与图片生成的区别 - **多模态理解**(本文):输入图片/视频/文件,模型输出文本。 - **图片生成**:输入文本描述,模型输出图片,详见[图片生成](../image-model/overview)。 ::: ## 支持的模型 {#supported-models} 不同模型在多模态能力上差异较大,下表对比常用模型: | 模型 | 图片输入 | 视频输入 | 文件输入 | 备注 | | --- | --- | --- | --- | --- | | `glm-5v-turbo` | ✅ URL / Base64 | ✅ URL | ✅ URL(PDF/TXT/DOC)| 图、视频、文件不可在同一请求中混传 | | `qwen3.5-plus` | ✅ URL / Base64 | ✅ URL | — | 默认开启思考,可与 `enable_thinking` 配合 | | `kimi-k2.6` | ✅ URL / Base64 | ✅ URL | — | Kimi 系列中唯一支持视频的模型 | | `kimi-k2.5` | ✅ URL / Base64 | — | — | 不支持视频 | | `kimi-k2.7-code` | ✅ 仅 Base64 | — | — | 编码专用,URL 形式不支持 | :::note - 表格仅列出常见多模态模型,完整列表请参考[接入大模型](./overview)中的模型说明。 - 不支持多模态的文本模型(如 `deepseek-v4-flash`、`hy3`)调用时若传入图片/视频,会被忽略或报错。 ::: ### 模型差异要点 {#model-differences} - **图片传入方式**:除 `kimi-k2.7-code` 仅支持 Base64 外,其他模型均同时支持 URL 直链和 Base64。 - **视频支持**:`glm-5v-turbo`、`qwen3.5-plus`、`kimi-k2.6` 三家支持,且**仅接受公网可访问的视频 URL**(不支持 Base64)。 - **文件支持**:目前仅 `glm-5v-turbo` 支持 PDF/TXT/DOC,且**只能通过 URL 传入**。 - **混合限制**:`glm-5v-turbo` 不允许在同一请求中同时传入图片、视频、文件三类不同媒体——需要拆分为多次调用。 - **图片大小**:Base64 形式建议单张不超过 5 MB。URL 形式需保证图片可被服务端访问(公网直链)。 ## 消息格式 {#message-format} 多模态请求遵循 OpenAI Chat Completions 标准协议:将 `messages[n].content` 由字符串改为对象数组,每个对象表示一段内容(文本、图片、视频或文件)。 ```ts type ContentBlock = | { type: "text"; text: string } | { type: "image_url"; image_url: { url: string } } | { type: "video_url"; video_url: { url: string } } | { type: "file"; file: { file_url: string } }; ``` ### 文本块 ```js { type: "text", text: "请描述这张图片的内容" } ``` ### 图片块 **URL 直链:** ```js { type: "image_url", image_url: { url: "https://example.com/photo.jpg" } } ``` **Base64:** ```js const fs = require("fs"); const base64 = fs.readFileSync("./photo.jpg").toString("base64"); { type: "image_url", image_url: { url: `data:image/jpeg;base64,${base64}` } } ``` ### 视频块 ```js { type: "video_url", video_url: { url: "https://example.com/clip.mp4" } } ``` ### 文件块(仅 `glm-5v-turbo`) ```js { type: "file", file: { file_url: "https://example.com/contract.pdf" } } ``` ## 使用方式 {#usage} ### 图片理解 {#image-understanding} ```js const model = ai.createModel("cloudbase"); const res = await model.generateText({ model: "glm-5v-turbo", messages: [ { role: "user", content: [ { type: "text", text: "这张图里有几只猫?颜色分别是什么?" }, { type: "image_url", image_url: { url: "https://example.com/cats.jpg" } } ] } ] }); console.log(res.text); ``` ```js const OpenAI = require("openai"); const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); const completion = await client.chat.completions.create({ model: "glm-5v-turbo", messages: [ { role: "user", content: [ { type: "text", text: "这张图里有几只猫?颜色分别是什么?" }, { type: "image_url", image_url: { url: "https://example.com/cats.jpg" } } ] } ] }); console.log(completion.choices[0].message.content); ``` ```js const model = wx.cloud.extend.AI.createModel("cloudbase"); const res = await model.generateText({ data: { model: "glm-5v-turbo", messages: [ { role: "user", content: [ { type: "text", text: "这张图里有几只猫?颜色分别是什么?" }, { type: "image_url", image_url: { url: "https://example.com/cats.jpg" } } ] } ] } }); console.log(res.text); ``` ```bash curl -X POST 'https://.api.tcloudbasegateway.com/v1/ai/cloudbase/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "model": "glm-5v-turbo", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "这张图里有几只猫?颜色分别是什么?"}, {"type": "image_url", "image_url": {"url": "https://example.com/cats.jpg"}} ] } ] }' ``` ### 使用 Base64 传入本地图片 {#image-base64} 适用于图片不在公网或不便上传的场景: ```js const fs = require("fs"); const path = require("path"); const filePath = path.resolve(__dirname, "./local.jpg"); const base64 = fs.readFileSync(filePath).toString("base64"); const dataUrl = `data:image/jpeg;base64,${base64}`; const model = ai.createModel("cloudbase"); const res = await model.generateText({ model: "kimi-k2.6", messages: [ { role: "user", content: [ { type: "text", text: "提取图片中的文字" }, { type: "image_url", image_url: { url: dataUrl } } ] } ] }); console.log(res.text); ``` :::tip 在小程序/Web 端获取 Base64 - 小程序:`wx.getFileSystemManager().readFile({ filePath, encoding: "base64" })` - Web:通过 `FileReader.readAsDataURL` 拿到完整的 `data:image/...;base64,...` 字符串。 ::: ### 多图同时输入 {#multi-image} 将多个 `image_url` 块依次放入 `content` 数组即可: ```js const res = await model.generateText({ model: "glm-5v-turbo", messages: [ { role: "user", content: [ { type: "text", text: "对比下面两张图,说出 3 处差异" }, { type: "image_url", image_url: { url: "https://example.com/before.jpg" } }, { type: "image_url", image_url: { url: "https://example.com/after.jpg" } } ] } ] }); ``` ### 视频理解 {#video-understanding} 视频仅支持公网 URL,且模型必须为 `glm-5v-turbo` / `qwen3.5-plus` / `kimi-k2.6` 之一: ```js const model = ai.createModel("cloudbase"); const res = await model.generateText({ model: "kimi-k2.6", messages: [ { role: "user", content: [ { type: "text", text: "用 100 字总结这段视频的内容" }, { type: "video_url", video_url: { url: "https://example.com/clip.mp4" } } ] } ] }); console.log(res.text); ``` :::note 视频处理耗时较长,建议配合[流式输出](./streaming)使用,避免请求超时。 ::: ### 文件理解(PDF/文本) {#file-understanding} 仅 `glm-5v-turbo` 支持,且只能通过 URL: ```js const res = await model.generateText({ model: "glm-5v-turbo", messages: [ { role: "user", content: [ { type: "text", text: "总结这份合同的核心条款,并列出风险点" }, { type: "file", file: { file_url: "https://example.com/contract.pdf" } } ] } ] }); ``` :::warning `glm-5v-turbo` 不允许在同一请求中同时传入图片、视频、文件三类——需要分别调用。 ::: ## 与流式输出结合 {#with-streaming} 多模态请求往往响应较慢,推荐配合流式输出使用: ```js const res = await model.streamText({ model: "glm-5v-turbo", messages: [ { role: "user", content: [ { type: "text", text: "详细描述这张图的构图和色彩" }, { type: "image_url", image_url: { url: "https://example.com/photo.jpg" } } ] } ] }); for await (const text of res.textStream) { process.stdout.write(text); } ``` 更多用法参考[流式输出](./streaming)。 ## 与深度思考结合 {#with-thinking} 部分模型支持在多模态输入上启用深度思考,适合"看图做数学题"、"图表分析"等任务: ```js const res = await model.generateText({ model: "qwen3.5-plus", reasoning_effort: "high", messages: [ { role: "user", content: [ { type: "text", text: "图中函数图像对应哪个表达式?给出推导过程" }, { type: "image_url", image_url: { url: "https://example.com/chart.png" } } ] } ] }); const raw = res.rawResponses[0].choices[0].message; console.log("思考过程:", raw.reasoning_content); console.log("最终回答:", res.text); ``` 参考[深度思考](./deep-thinking)了解 `reasoning_effort` 用法和模型差异。 ## 多轮对话中的多模态消息 {#multi-turn} 多模态消息可以正常出现在历史 `messages` 中,但要注意: - **图片 URL 长期有效性**:若图片来自临时签名 URL,过期后历史消息会失效,建议改用 Base64 或对象存储永久链接。 - **Token 成本**:图片在编码后会占用相当于数百到数千 token,长对话务必使用[上下文管理](./context-management)裁剪历史。 - **角色限制**:仅 `user` 角色支持数组形式的 `content`,`assistant` 回复仍以纯文本为主。 ```js const messages = [ { role: "user", content: [ { type: "text", text: "图里是哪种花?" }, { type: "image_url", image_url: { url: "https://example.com/flower.jpg" } } ] }, { role: "assistant", content: "这是一朵向日葵。" }, { role: "user", content: "它的花期一般是几月?" } ]; const res = await model.generateText({ model: "glm-5v-turbo", messages }); ``` ## 常见问题 {#faq} ### 报错 "model does not support image input" 模型不支持多模态。请改用上方[支持的模型](#supported-models)列表中的模型。 ### 图片传 URL 时模型说"看不到图片" - 检查 URL 是否公网可访问、是否需要鉴权(签名 URL 注意有效期)。 - 国外模型有时无法访问国内域名,建议把图片放到云开发[文件存储](../../storage/introduce)并使用其下载链接。 - 部分模型(如 `kimi-k2.7-code`)只接受 Base64,URL 会被拒绝。 ### 视频识别效果不理想 - 视频时长越短、画面越清晰,效果越好。建议先用工具压缩到 30 秒内、分辨率 720p 左右。 - 复杂视频可先抽帧成多张图片,作为多图输入也是可行方案。 ### 如何控制图片清晰度(detail 参数) 部分模型支持 `image_url.detail`(`low` / `high` / `auto`)控制图像分析精度。`low` 更省 token 但识别能力弱,`high` 反之。是否生效以具体模型为准。 --- # AI/文本生成/功能介绍/上下文缓存 > 当前文档链接: https://docs.cloudbase.net/ai/model/cache 提示词缓存(Prompt Caching)可以减少重复输入内容的 Token 计费,适用于多轮对话中固定的 system prompt、长文档分析等场景。 ## 工作原理 {#how-it-works} 在多轮对话中,每次请求都需要携带完整的历史消息,这意味着固定不变的内容(如 system prompt、工具定义、长文档)会被反复计费。 提示词缓存的核心思路:**对于请求中重复出现的前缀部分,只在首次写入时全价计费,后续命中缓存时大幅降低费用。** ![提示词缓存的核心思路](https://qcloudimg.tencent-cloud.cn/raw/c048360f543f5eafbf3a259406644f6f.png) ## 支持的协议 {#supported-protocols} CloudBase AI 通过 Anthropic Messages API 协议支持提示词缓存: | 协议 | 缓存支持 | 说明 | | --- | --- | --- | | Anthropic Messages API | ✅ 支持 | 显式缓存 + 自动缓存 | | Chat Completions API | — | 不支持客户端缓存控制 | | CloudBase SDK | — | 不支持客户端缓存控制 | :::note 使用 Chat Completions 协议或 CloudBase SDK 时,服务端可能有内部缓存优化,但客户端无法显式控制。如需精确控制缓存行为,请使用 Anthropic Messages API。 ::: ## 使用方式 {#usage} 在需要缓存的内容块上添加 `cache_control` 标记: ```bash curl "https://.api.tcloudbasegateway.com/v1/ai/cloudbase/v1/messages" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "hy3", "max_tokens": 1024, "system": [ { "type": "text", "text": "你是一个法律条文分析专家。以下是一份完整的合同规范(共 5 万字):...", "cache_control": {"type": "ephemeral"} } ], "messages": [ {"role": "user", "content": "请找出关于违约金的条款"} ] }' ``` 第一次请求会将 system prompt 写入缓存,后续相同前缀的请求将命中缓存,大幅降低 Token 费用。 在请求体最外层声明 `cache_control`,系统自动识别重复的静态前缀进行缓存: ```bash curl "https://.api.tcloudbasegateway.com/v1/ai/cloudbase/v1/messages" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "hy3", "max_tokens": 1024, "cache_control": {"type": "ephemeral"}, "system": "你是一个专业的天气查询助手。", "tools": [ { "name": "get_weather", "description": "获取指定城市的实时天气", "input_schema": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"] } } ], "messages": [ {"role": "user", "content": "今天北京天气怎么样?"} ] }' ``` 自动缓存适合多轮对话场景,系统会自动判断哪些前缀内容可以缓存。 :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: ## 检查缓存命中 {#check-cache-hit} 通过响应中的 `usage` 字段判断缓存状态: ```json { "usage": { "input_tokens": 350, "output_tokens": 120, "cache_creation_input_tokens": 4200, "cache_read_input_tokens": 0 } } ``` | 字段 | 说明 | 计费 | | --- | --- | --- | | `input_tokens` | 未被缓存的输入 Token | 正常价格 | | `cache_creation_input_tokens` | 本次新写入缓存的 Token | 约 1.25x 正常价格 | | `cache_read_input_tokens` | 本次从缓存读取的 Token | 约 0.1x 正常价格 | **判断逻辑:** - `cache_creation_input_tokens > 0`:首次写入缓存(略贵于正常计费) - `cache_read_input_tokens > 0`:命中缓存(仅 10% 的正常价格) - 两者都为 0:未使用缓存 ## 适用场景 {#use-cases} | 场景 | 缓存收益 | 说明 | | --- | --- | --- | | 长 system prompt 复用 | ⭐⭐⭐ | 几千字的角色设定、规则说明 | | 长文档分析 | ⭐⭐⭐ | 将文档放入 system,多次提问 | | 固定工具定义 | ⭐⭐ | 每次请求都带相同的 tools 列表 | | 多轮对话 | ⭐⭐ | 前面轮次的历史被缓存 | | 单轮短对话 | ⭐ | 无重复前缀,收益有限 | ## 缓存失效条件 {#cache-invalidation} 以下情况会导致缓存无法命中: | 条件 | 说明 | | --- | --- | | 内容变更 | 缓存的前缀内容必须**精确匹配**,任何一个字符的差异都会导致 miss | | 工具定义变更 | 修改了 tools 列表中的任何字段 | | 缓存过期 | 缓存有 TTL(通常为 5 分钟),过期后需重新写入 | | 超过断点限制 | 显式缓存最多支持 4 个 `cache_control` 断点 | :::tip 为最大化缓存命中率: 1. 将固定内容(system prompt、工具定义)放在消息列表最前面 2. 避免在固定内容中插入动态信息(如当前时间戳) 3. 保持工具定义的稳定性 ::: ## 成本优化示例 {#cost-example} 假设你的应用有一个 4000 Token 的 system prompt,进行 10 轮对话: **不使用缓存:** ``` 每轮输入费用 = 4000 × 正常单价 10 轮总费用 = 4000 × 10 × 正常单价 = 40000 × 正常单价 ``` **使用缓存:** ``` 第 1 轮 = 4000 × 1.25(写入缓存) 第 2-10 轮 = 4000 × 0.1 × 9(读取缓存) 总费用 = 5000 + 3600 = 8600 × 正常单价 ``` **节省约 78% 的 system prompt 费用。** --- # AI/文本生成/功能介绍/上下文管理 > 当前文档链接: https://docs.cloudbase.net/ai/model/context-management 当多轮对话的历史消息超出模型上下文窗口时,Responses API 提供了**自动截断**和**上下文压缩**两种策略,无需手动处理 token 溢出问题。 :::note 上下文管理功能仅在使用 **Responses API** 协议时可用。如需了解协议详情,请参阅 [API 协议介绍](../ai-tools/protocol)。 ::: ## 自动截断(Truncation) {#truncation} 通过 `truncation` 参数控制上下文超限时的处理策略: | 值 | 行为 | |---|---| | `"disabled"` | 不进行截断,上下文超限时返回错误(默认) | | `"auto"` | 自动从对话开头丢弃最早的消息,直到内容能装入模型上下文窗口 | ### 使用方式 ```bash curl "https://{{YOUR-ENV-ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase/responses" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {{YOUR-API-KEY}}" \ -d '{ "model": "hy3", "previous_response_id": "resp_xxxxx", "truncation": "auto", "input": "请继续我们之前的讨论" }' ``` 设置 `truncation: "auto"` 后,即使对话积累了大量历史,API 也会自动滑动窗口,保留最近的对话内容。 ### 工作原理 ``` 完整对话历史(可能超出窗口) ├── 第 1 轮(最早) ← 超限时优先丢弃 ├── 第 2 轮 ├── ... ├── 第 N-1 轮 └── 第 N 轮(最近) ← 始终保留 ``` ## 上下文压缩(Compaction) {#compaction} 与截断不同,压缩不是简单丢弃早期消息,而是通过**摘要**来保留关键信息的同时减少 token 消耗。 ### 手动压缩 对已有的响应调用压缩接口: ```bash curl "https://{{YOUR-ENV-ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase/responses/{{RESPONSE_ID}}/compact" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {{YOUR-API-KEY}}" \ -d '{ "model": "hy3" }' ``` API 会对该响应关联的对话历史进行摘要压缩,返回一个新的压缩后响应 ID,后续对话可基于该 ID 继续。 ### 自动压缩 通过 `context_management` 参数设置自动触发压缩的 token 阈值: ```bash curl "https://{{YOUR-ENV-ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase/responses" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {{YOUR-API-KEY}}" \ -d '{ "model": "hy3", "previous_response_id": "resp_xxxxx", "context_management": { "type": "compaction", "compact_threshold": 80000 }, "input": "请继续我们之前的讨论" }' ``` 当对话历史的 token 总量达到 `compact_threshold`(本例为 80000)时,系统自动执行压缩,无需手动调用。 ## 截断 vs 压缩 {#comparison} | 特性 | 自动截断(Truncation) | 上下文压缩(Compaction) | |---|---|---| | 处理方式 | 直接丢弃最早的对话项 | 通过摘要压缩对话历史 | | 信息保留 | 早期信息完全丢失 | 通过摘要保留关键信息 | | 触发方式 | 每次请求自动检测 | 手动调用或设置 token 阈值自动触发 | | 适用场景 | 对早期对话不敏感的场景 | 需要保留全局上下文的长对话 | | 延迟影响 | 无额外延迟 | 压缩过程需要额外时间 | ## 适用场景 {#use-cases} | 场景 | 推荐策略 | 说明 | |---|---|---| | 客服对话 | 截断 `auto` | 只需关注最近几轮,早期问题已解决 | | 长文档协作编辑 | 压缩 | 需要保留全局理解,不能丢失早期上下文 | | 代码助手 | 压缩 | 保留项目结构和之前的决策信息 | | 闲聊/短对话 | 不需要 | 对话不会超出窗口 | ## 注意事项 {#notes} - 使用 `previous_response_id` 串联对话时,被引用的响应必须以 `store: true` 创建 - 截断和压缩可以组合使用:先设置 `context_management` 自动压缩,再设置 `truncation: "auto"` 作为兜底 - 压缩产生的摘要本身也会消耗 token,适合对话已经较长(数万 token)时使用 --- # AI/文本生成/功能介绍/工具调用 > 当前文档链接: https://docs.cloudbase.net/ai/model/tool-calling 工具调用(Function Calling / Tool Use)允许大模型调用自定义函数,扩展模型能力,适用于信息检索、数据库操作、外部 API 调用等场景。 ## 工作原理 ```mermaid sequenceDiagram participant 用户 participant 应用 participant 大模型 participant 工具函数 用户->>应用: 北京天气怎么样? 应用->>大模型: 发送消息 + 可用工具列表 大模型->>应用: 返回工具调用请求 应用->>工具函数: 执行 get_weather({city: "北京"}) 工具函数->>应用: 返回 "26°C, 晴" 应用->>大模型: 发送工具执行结果 大模型->>应用: 生成最终回复 应用->>用户: 北京今天 26°C,晴天,适宜外出 ``` CloudBase AI SDK 自动处理上述流程,开发者只需定义工具并注册即可。 ## 前置准备 {#prerequisites} 使用工具调用前,需先初始化 CloudBase AI 实例: ```js const cloudbase = require("@cloudbase/node-sdk"); const app = cloudbase.init({ env: "" }); const ai = app.ai(); ``` ## 使用步骤 {#steps} ### 1. 定义工具 ```js const getWeatherTool = { name: "get_weather", description: "查询指定城市的天气信息", fn: async ({ city }) => { // 实际场景中调用天气 API return `${city}:26°C,晴`; }, parameters: { type: "object", properties: { city: { type: "string", description: "城市名称" } }, required: ["city"] } }; ``` ### 2. 注册工具 ```js ai.registerFunctionTool(getWeatherTool); ``` ### 3. 调用模型 ```js const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "deepseek-v4-flash", tools: [getWeatherTool], messages: [{ role: "user", content: "北京天气怎么样?" }] }); console.log(result.text); // 输出:北京今天 26°C,晴天,适宜外出活动。 ``` :::tip 在线示例 在 CodeSandbox 中打开 CloudBase SDK 完整示例 → | OpenAI SDK 示例 → ::: ## 工具定义 ### FunctionTool 类型 ```ts interface FunctionTool { name: string; // 工具名称,唯一标识 description: string; // 工具描述,帮助模型理解用途 fn: Function; // 执行函数 parameters: object; // 参数定义(JSON Schema 格式) } ``` ### 参数定义(JSON Schema) ```js { type: "object", properties: { // 字符串参数 city: { type: "string", description: "城市名称" }, // 数字参数 limit: { type: "number", description: "返回结果数量" }, // 布尔参数 detailed: { type: "boolean", description: "是否返回详细信息" }, // 枚举参数 unit: { type: "string", enum: ["celsius", "fahrenheit"], description: "温度单位" } }, required: ["city"] // 必填参数 } ``` ## 多工具示例 ```js // 天气查询工具 const getWeatherTool = { name: "get_weather", description: "查询城市天气", fn: async ({ city }) => `${city}:26°C,晴`, parameters: { type: "object", properties: { city: { type: "string", description: "城市名称" } }, required: ["city"] } }; // 时间查询工具 const getTimeTool = { name: "get_time", description: "查询指定城市的当前时间", fn: async ({ city }) => { const now = new Date(); return `${city}当前时间:${now.toLocaleTimeString()}`; }, parameters: { type: "object", properties: { city: { type: "string", description: "城市名称" } }, required: ["city"] } }; // 注册多个工具 ai.registerFunctionTool(getWeatherTool); ai.registerFunctionTool(getTimeTool); // 调用时传入所有工具 const result = await model.generateText({ model: "deepseek-v4-flash", tools: [getWeatherTool, getTimeTool], messages: [{ role: "user", content: "北京现在几点了?天气怎么样?" }] }); ``` ## 高级配置 ### toolChoice 控制模型如何选择工具: ```js const result = await model.generateText({ model: "deepseek-v4-flash", tools: [getWeatherTool], toolChoice: "auto", // 模型自动决定是否使用工具 messages: [{ role: "user", content: "北京天气怎么样?" }] }); ``` | 值 | 说明 | | --- | --- | | `"auto"` | 模型自动决定(默认) | | `"none"` | 禁止使用工具 | | `"custom"` | 强制使用指定工具 | ### maxSteps 限制模型调用工具的最大次数: ```js const result = await model.generateText({ model: "deepseek-v4-flash", tools: [getWeatherTool], maxSteps: 3, // 最多调用 3 次工具 messages: [{ role: "user", content: "对比北京和上海的天气" }] }); ``` ### onStepFinish 监听每次工具调用完成: ```js const result = await model.generateText({ model: "deepseek-v4-flash", tools: [getWeatherTool], onStepFinish: ({ toolCall, toolResult, text }) => { if (toolCall) { console.log("调用工具:", toolCall.function.name); console.log("工具结果:", toolResult); } if (text) { console.log("生成文本:", text); } }, messages: [{ role: "user", content: "北京天气怎么样?" }] }); ``` ## 流式调用 工具调用也支持流式输出: ```js const result = await model.streamText({ model: "deepseek-v4-flash", tools: [getWeatherTool], messages: [{ role: "user", content: "北京天气怎么样?" }] }); for await (const text of result.textStream) { console.log(text); } ``` ## 实际应用示例 ### 数据库查询 ```js const queryDatabaseTool = { name: "query_database", description: "查询数据库中的用户信息", fn: async ({ userId }) => { const db = app.database(); const result = await db.collection("users").doc(userId).get(); return JSON.stringify(result.data); }, parameters: { type: "object", properties: { userId: { type: "string", description: "用户 ID" } }, required: ["userId"] } }; ``` ### 外部 API 调用 ```js const searchWebTool = { name: "search_web", description: "搜索网络获取最新信息", fn: async ({ query }) => { const response = await fetch(`https://api.example.com/search?q=${query}`); const data = await response.json(); return JSON.stringify(data.results.slice(0, 3)); }, parameters: { type: "object", properties: { query: { type: "string", description: "搜索关键词" } }, required: ["query"] } }; ``` ### 计算器 ```js const calculatorTool = { name: "calculator", description: "执行数学计算", fn: ({ expression }) => { try { // 注意:实际使用时应使用安全的表达式解析库 return String(eval(expression)); } catch { return "计算错误"; } }, parameters: { type: "object", properties: { expression: { type: "string", description: "数学表达式,如 2+2*3" } }, required: ["expression"] } }; ``` ## 类型定义 ### IOnStepFinish ```ts interface IOnStepFinish { messages: ChatModelMessage[]; // 当前消息列表 text?: string; // 生成的文本 toolCall?: ToolCall; // 工具调用信息 toolResult?: unknown; // 工具执行结果 finishReason?: string; // 结束原因 stepUsage?: Usage; // 当前步骤 token 使用量 totalUsage?: Usage; // 累计 token 使用量 } ``` ### ToolCall ```ts interface ToolCall { id: string; type: string; function: { name: string; arguments: string; // JSON 字符串 }; } ``` ## 最佳实践 1. **清晰的工具描述** - 帮助模型准确理解工具用途 2. **完整的参数说明** - 包含类型、描述、是否必填 3. **错误处理** - 工具函数应处理异常情况 4. **限制调用次数** - 使用 `maxSteps` 避免无限循环 5. **异步函数** - 工具函数可以是异步的 --- # AI/文本生成/SDK 调用方式/小程序调用 > 当前文档链接: https://docs.cloudbase.net/ai/model/miniprogram-access 微信小程序通过 `wx.cloud.extend.AI` 调用 CloudBase AI 大模型,无需额外安装 SDK。 ## 准备工作 - 微信基础库版本 ≥ 3.15.1 - 已创建云开发环境(旧套餐可升级),并已启用模型开关(见 [接入大模型](./overview)) ## 初始化 在 `app.js` 中初始化云开发: ```js App({ onLaunch() { wx.cloud.init({ env: "您的环境ID" }); } }); ``` ## 文本生成 ### generateText() - 非流式 一次性返回完整结果,适用于短文本生成。 ```js const model = wx.cloud.extend.AI.createModel("cloudbase"); const res = await model.generateText({ model: "hy3", messages: [{ role: "user", content: "介绍一下李白" }], }); // 返回值为原始模型响应 console.log(res.choices[0].message.content); console.log(res.usage); // { prompt_tokens, completion_tokens, total_tokens } ``` ### 返回值 ```ts interface GenerateTextResponse { id: string; object: "chat.completion"; created: number; model: string; choices: Array<{ index: number; message: { role: "assistant"; content: string; }; finish_reason: string; }>; usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number; }; } ``` ### streamText() - 流式 流式返回文本,适用于实时对话、长文本生成场景。 ```js const model = wx.cloud.extend.AI.createModel("cloudbase"); const res = await model.streamText({ data: { model: "hy3", messages: [{ role: "user", content: "介绍一下李白" }], } }); // 使用 textStream 获取增量文本 for await (const text of res.textStream) { console.log("文本片段:", text); } ``` ### 参数 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | data | object | 是 | 请求参数,包含 model 和 messages | | data.model | string | 是 | 模型名称 | | data.messages | array | 是 | 消息列表 | ### 返回值 | 属性 | 类型 | 说明 | | --- | --- | --- | | textStream | AsyncIterable\ | 增量文本流 | | eventStream | AsyncIterable\ | 原始事件流(包含元数据) | ### 检测流结束 使用 `eventStream` 可获取完整事件信息,当返回 `[DONE]` 时表示流结束: ```js for await (const event of res.eventStream) { console.log(event); if (event.data === "[DONE]") { break; } } ``` ## 完整示例 ### 聊天页面 ```js Page({ data: { messages: [], inputValue: "", isLoading: false }, async sendMessage() { const { inputValue, messages } = this.data; if (!inputValue.trim() || this.data.isLoading) return; // 添加用户消息 const userMessage = { role: "user", content: inputValue }; const newMessages = [...messages, userMessage]; this.setData({ messages: [...newMessages, { role: "assistant", content: "" }], inputValue: "", isLoading: true }); try { const model = wx.cloud.extend.AI.createModel("cloudbase"); let assistantContent = ""; const res = await model.streamText({ data: { model: "hy3", messages: newMessages, } }); // 使用 textStream 逐步更新 UI for await (const text of res.textStream) { assistantContent += text; this.setData({ messages: [ ...newMessages, { role: "assistant", content: assistantContent } ] }); } } catch (error) { console.error("调用失败:", error); wx.showToast({ title: "请求失败", icon: "error" }); } finally { this.setData({ isLoading: false }); } } }); ``` ## 与 Web SDK 的差异 | 特性 | 小程序 | Web SDK | | --- | --- | --- | | 命名空间 | `wx.cloud.extend.AI` | `app.ai()` | | 参数格式 | `streamText` 包裹在 `data` 对象中,`generateText` 直接传递 | 直接传递 | | 返回值 | 原始响应 `{ choices, usage }` | 封装后 `{ text, usage, messages }` | | 图片生成 | 不支持 | 支持 | --- # AI/文本生成/SDK 调用方式/wx-server-sdk 调用 > 当前文档链接: https://docs.cloudbase.net/ai/model/wx-server-sdk-access 通过微信云开发内置的 `wx-server-sdk` 在云函数中调用 CloudBase AI 大模型,无需额外安装依赖,支持文本生成、流式输出等能力。 :::tip 在线示例 在 CodeSandbox 中查看示例代码 →(注意:此代码需部署为微信云函数运行,无法直接在 CodeSandbox 中执行) ::: ## 准备工作 - 已创建云开发环境(旧套餐可升级),并已启用模型开关(见 [接入大模型](./overview)) - wx-server-sdk **3.0.5-beta.1 或更高版本**(支持 `cloud.ai()`) ## 版本说明 `cloud.ai()` 接口需要 wx-server-sdk **3.0.5-beta.1** 或更高版本。可在云函数的 `package.json` 中指定版本: ```json { "dependencies": { "wx-server-sdk": "3.0.5-beta.1" } } ``` ## 初始化 ```js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, timeout: 60000 // AI 生成可能耗时较长,建议设置较长超时时间 }) exports.main = async (event, context) => { const ai = cloud.ai() // 使用 AI 功能 } ``` :::tip 超时配置 AI 模型生成文本可能需要较长时间,建议将 `timeout` 设置为 60000(60 秒)或更高,避免请求超时。 ::: ## 文本生成 ### generateText() - 非流式 一次性返回完整结果。 ```js const ai = cloud.ai() const model = ai.createModel('cloudbase') const result = await model.generateText({ model: 'hy3', messages: [{ role: 'user', content: '介绍一下李白' }], }) console.log(result.text) // 生成的文本 console.log(result.usage) // Token 使用量 console.log(result.messages) // 完整消息历史 ``` #### 返回值 | 属性 | 类型 | 说明 | | --- | --- | --- | | text | string | 生成的文本 | | messages | ChatModelMessage[] | 完整消息历史 | | usage | Usage | Token 使用量 | | rawResponses | unknown[] | 原始模型响应 | | error | unknown | 错误信息(如有) | ### streamText() - 流式 流式返回文本,适用于实时对话场景。 ```js const ai = cloud.ai() const model = ai.createModel('cloudbase') const res = await model.streamText({ model: 'hy3', messages: [{ role: 'user', content: '介绍一下李白' }], }) // 方式一:迭代文本流(推荐) for await (const text of res.textStream) { console.log(text) // 增量文本 } // 方式二:迭代数据流,获取完整响应数据 for await (const data of res.dataStream) { console.log(data) // 包含 choices、usage 等完整信息 } // 获取最终结果 const messages = await res.messages const usage = await res.usage ``` #### 返回值 | 属性 | 类型 | 说明 | | --- | --- | --- | | textStream | AsyncIterable\ | 增量文本流 | | dataStream | AsyncIterable\ | 完整数据流 | | messages | Promise\ | 最终消息历史 | | usage | Promise\ | 最终 Token 使用量 | ## 完整示例 ### AI 聊天接口 ```js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, timeout: 60000 }) exports.main = async (event, context) => { const { messages } = event const ai = cloud.ai() const model = ai.createModel('cloudbase') try { const result = await model.generateText({ model: 'hy3', messages }) return { success: true, text: result.text, usage: result.usage } } catch (error) { return { success: false, error: error.message } } } ``` ### 流式响应 ```js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, timeout: 60000 }) exports.main = async (event, context) => { const { messages } = event const ai = cloud.ai() const model = ai.createModel('cloudbase') const res = await model.streamText({ model: 'hy3', messages }) // 收集完整文本 let fullText = '' for await (const text of res.textStream) { fullText += text } const usage = await res.usage return { text: fullText, usage } } ``` ## 与 @cloudbase/node-sdk 的差异 | 特性 | wx-server-sdk | @cloudbase/node-sdk | | --- | --- | --- | | 安装 | 内置,无需安装 | `npm install @cloudbase/node-sdk` | | 版本要求 | 3.0.5-beta.1+ | 3.16.0+ | | 初始化 | `cloud.init()` | `tcb.init()` | | AI 入口 | `cloud.ai()` | `app.ai()` | | 图片生成 | 支持 | 支持 | | 适用场景 | 微信云函数 | 微信云函数 / 云托管 / 独立 Node.js 服务 | --- # AI/文本生成/SDK 调用方式/Web SDK 调用 > 当前文档链接: https://docs.cloudbase.net/ai/model/web-access 通过 `@cloudbase/js-sdk` 在 Web 应用中调用 CloudBase AI 大模型,支持文本生成、流式输出、图生文等能力。 :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: ## 准备工作 - 已创建云开发环境(旧套餐可升级),并已启用模型开关(见 [接入大模型](./overview)) ## 安装 ```bash npm install @cloudbase/js-sdk ``` ## 初始化 ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "您的环境ID", accessKey: "" // 从控制台获取 }); // 获取 AI 实例 const ai = app.ai(); ``` > Publishable Key 获取地址:[CloudBase 控制台 → 环境配置 → API Key](https://tcb.cloud.tencent.com/dev#/env/apikey) ## 文本生成 ### generateText() - 非流式 一次性返回完整结果。 ```js const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", messages: [{ role: "user", content: "介绍一下李白" }], }); console.log(result.text); // 生成的文本 console.log(result.usage); // Token 使用量 console.log(result.messages); // 完整消息历史 ``` ### 返回值 | 属性 | 类型 | 说明 | | --- | --- | --- | | text | string | 生成的文本 | | messages | ChatModelMessage[] | 完整消息历史 | | usage | Usage | Token 使用量 | | rawResponses | unknown[] | 原始模型响应 | | error | unknown | 错误信息(如有) | ### streamText() - 流式 流式返回文本,适用于实时对话场景。 ```js const model = ai.createModel("cloudbase"); const res = await model.streamText({ model: "hy3", messages: [{ role: "user", content: "介绍一下李白" }], }); // 方式一:迭代文本流(推荐) for await (const text of res.textStream) { console.log(text); // 增量文本 } // 方式二:迭代数据流,获取完整响应数据 for await (const data of res.dataStream) { console.log(data); // 包含 choices、usage 等完整信息 } // 获取最终结果 const messages = await res.messages; const usage = await res.usage; ``` ### 返回值 | 属性 | 类型 | 说明 | | --- | --- | --- | | textStream | AsyncIterable\ | 增量文本流 | | dataStream | AsyncIterable\ | 完整数据流 | | messages | Promise\ | 最终消息历史 | | usage | Promise\ | 最终 Token 使用量 | ## 图生文(视觉理解) 使用支持视觉的模型(如 `hunyuan-vision`)理解图片内容。 :::tip 需要先配置自定义模型 `hunyuan-custom`,填写混元大模型的 BaseURL 和 API Key。 ::: ```js const model = ai.createModel("hunyuan-custom"); const res = await model.streamText({ model: "hunyuan-vision", messages: [ { role: "user", content: [ { type: "text", text: "这张图片的内容是什么?" }, { type: "image_url", image_url: { url: "https://example.com/image.png" } } ] } ] }); for await (const text of res.textStream) { console.log(text); } ``` ### 消息内容格式 当使用图生文时,`content` 为数组格式: **文本内容:** ```ts interface TextContent { type: "text"; text: string; } ``` **图片内容:** ```ts interface ImageContent { type: "image_url"; image_url: { url: string; // 图片 URL }; } ``` ## 文生图 :::warning 注意 图片生成功能仅在 Node SDK 中可用,Web SDK 暂不支持。如需在 Web 应用中使用,请通过云函数调用。详见 [Node SDK 调用 - 图片生成](./nodejs-access#图片生成)。 ::: ## 完整示例 ### 聊天应用 ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "您的环境ID", accessKey: "" }); async function chat(userInput, history = []) { const auth = app.auth(); await auth.signInAnonymously(); const ai = app.ai(); const model = ai.createModel("cloudbase"); const messages = [ ...history, { role: "user", content: userInput } ]; const res = await model.streamText({ model: "hy3", messages }); let fullText = ""; for await (const text of res.textStream) { fullText += text; // 更新 UI document.getElementById("output").textContent = fullText; } return fullText; } ``` --- # AI/文本生成/SDK 调用方式/Node SDK 调用 > 当前文档链接: https://docs.cloudbase.net/ai/model/nodejs-access 通过 `@cloudbase/node-sdk` 在 Node.js 服务端(如云函数、云托管)调用 CloudBase AI 大模型,支持文本生成、流式输出、图片生成等能力。 :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: ## 准备工作 - 已创建云开发环境(旧套餐可升级),并已启用模型开关(见 [接入大模型](./overview)) ## 安装 ```bash npm install @cloudbase/node-sdk ``` :::warning 版本要求 Node SDK 调用混元生图模型需要 **@cloudbase/node-sdk >= 3.18.3**;其依赖的 **@cloudbase/ai >= 2.30.0**(该版本已内置 `HY-Image-3.0-Plus-4090-Tob-v1.0` 模型配置)。请检查版本:`npm list @cloudbase/ai` ::: ## 初始化 ### 云函数中使用 ```js const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "您的环境ID", timeout: 60000 // AI 生成可能耗时较长,建议设置较长超时时间 }); exports.main = async (event, context) => { const ai = app.ai(); // 使用 AI 功能 }; ``` ### 独立 Node.js 服务中使用 ```js const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "您的环境ID", secretId: "", secretKey: "", timeout: 60000 // 设置超时时间为 60 秒,AI 生成可能耗时较长 }); const ai = app.ai(); ``` > 密钥获取地址:[腾讯云 API 密钥管理](https://console.cloud.tencent.com/cam/capi) :::tip 超时配置 AI 模型生成文本可能需要较长时间,建议将 `timeout` 设置为 60000(60 秒)或更高,避免请求超时。 ::: ## 文本生成 ### generateText() - 非流式 一次性返回完整结果。 ```js const model = ai.createModel("cloudbase"); const result = await model.generateText({ model: "hy3", messages: [{ role: "user", content: "介绍一下李白" }], }); console.log(result.text); // 生成的文本 console.log(result.usage); // Token 使用量 console.log(result.messages); // 完整消息历史 ``` ### 返回值 | 属性 | 类型 | 说明 | | ------------ | ------------------ | ---------------- | | text | string | 生成的文本 | | messages | ChatModelMessage[] | 完整消息历史 | | usage | Usage | Token 使用量 | | rawResponses | unknown[] | 原始模型响应 | | error | unknown | 错误信息(如有) | ### streamText() - 流式 流式返回文本,适用于实时对话场景。 ```js const model = ai.createModel("cloudbase"); const res = await model.streamText({ model: "hy3", messages: [{ role: "user", content: "介绍一下李白" }], }); // 方式一:迭代文本流(推荐) for await (const text of res.textStream) { console.log(text); // 增量文本 } // 方式二:迭代数据流,获取完整响应数据 for await (const data of res.dataStream) { console.log(data); // 包含 choices、usage 等完整信息 } // 获取最终结果 const messages = await res.messages; const usage = await res.usage; ``` ### 返回值 | 属性 | 类型 | 说明 | | ---------- | ----------------------------- | ----------------- | | textStream | AsyncIterable\ | 增量文本流 | | dataStream | AsyncIterable\ | 完整数据流 | | messages | Promise\ | 最终消息历史 | | usage | Promise\ | 最终 Token 使用量 | ## 图片生成 :::tip 仅 Node SDK 支持 图片生成功能仅在 Node SDK 中可用,Web SDK 和小程序均不支持。 ::: ```js const imageModel = ai.createImageModel("hunyuan-image"); const res = await imageModel.generateImage({ model: "HY-Image-3.0-Plus-4090-Tob-v1.0", prompt: "一只可爱的猫咪在草地上玩耍", size: "1280x720", revise: { value: true }, enable_thinking: { value: false } }); console.log(res.data[0].url); // 图片 URL(24 小时有效) console.log(res.data[0].revised_prompt); // 改写后的 prompt ``` ### 图片生成参数 | 参数 | 类型 | 必填 | 说明 | | --------------- | -------------------- | ---- | -------------------------------------------------------------------------------------- | | model | string | 是 | 模型名称,`HY-Image-3.0-Plus-4090-Tob-v1.0`(推荐)或 `hunyuan-image-v3.0-v1.0.1` | | prompt | string | 是 | 图片描述,不超过 8192 字符 | | size | string | 否 | 图片尺寸,格式 `"宽x高"`,默认 `1024x1024`,宽高范围 [512, 2048],面积不超过 1024x1024 | | seed | number | 否 | 生成种子,仅当生成 1 张图片时生效,范围 [1, 4294967295] | | footnote | string | 否 | 自定义水印内容,限制 16 字符,显示在图片右下角 | | revise | `{ value: boolean }` | 否 | 是否改写 prompt,默认开启,改写会增加约 30s 耗时 | | enable_thinking | `{ value: boolean }` | 否 | 改写时是否开启 thinking 模式,默认开启,效果更好但耗时增加(最大 60s) | ### 返回值 | 属性 | 类型 | 说明 | | --------------------- | ------ | ----------------------- | | id | string | 请求 ID | | created | number | 时间戳 | | data[].url | string | 图片 URL(24 小时有效) | | data[].revised_prompt | string | 改写后的 prompt | ## 图生文(视觉理解) 使用支持视觉的模型(如 `hunyuan-vision`)理解图片内容。 :::tip 需要先配置自定义模型 `hunyuan-custom`,填写混元大模型的 BaseURL 和 API Key。 ::: ```js const model = ai.createModel("hunyuan-custom"); const res = await model.streamText({ model: "hunyuan-vision", messages: [ { role: "user", content: [ { type: "text", text: "这张图片的内容是什么?" }, { type: "image_url", image_url: { url: "https://example.com/image.png" } } ] } ] }); for await (const text of res.textStream) { console.log(text); } ``` ## 完整示例 ### 云函数:AI 聊天接口 ```js const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "您的环境ID", timeout: 60000 // AI 生成可能耗时较长 }); exports.main = async (event, context) => { const { messages } = event; const ai = app.ai(); const model = ai.createModel("cloudbase"); try { const result = await model.generateText({ model: "hy3", messages }); return { success: true, text: result.text, usage: result.usage }; } catch (error) { return { success: false, error: error.message }; } }; ``` ### 云函数:流式响应(SSE) ```js const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "您的环境ID", timeout: 60000 // AI 生成可能耗时较长 }); exports.main = async (event, context) => { const { messages } = event; const ai = app.ai(); const model = ai.createModel("cloudbase"); const res = await model.streamText({ model: "hy3", messages }); // 收集完整文本 let fullText = ""; for await (const text of res.textStream) { fullText += text; } const usage = await res.usage; return { text: fullText, usage }; }; ``` ### 云函数:图片生成 ```js const tcb = require("@cloudbase/node-sdk"); const app = tcb.init({ env: "您的环境ID", timeout: 60000 // 图片生成可能耗时较长 }); exports.main = async (event, context) => { const { prompt } = event; const ai = app.ai(); const imageModel = ai.createImageModel("hunyuan-image"); const res = await imageModel.generateImage({ model: "HY-Image-3.0-Plus-4090-Tob-v1.0", prompt, size: "1280x720", enable_thinking: { value: false } }); return { url: res.data[0].url, revisedPrompt: res.data[0].revised_prompt }; }; ``` ## 与 Web SDK 的差异 | 特性 | Node SDK | Web SDK | | -------- | ---------------- | ---------------------- | | 初始化 | `tcb.init()` | `cloudbase.init()` | | 认证方式 | 密钥或云函数环境 | Publishable Key + 登录 | | 图片生成 | 支持 | 不支持 | | 运行环境 | 服务端 | 浏览器 | --- # AI/文本生成/SDK 调用方式/cURL 调用 > 当前文档链接: https://docs.cloudbase.net/ai/model/curl-access 通过 HTTP API 直接调用 CloudBase AI 大模型,适用于后端服务、脚本、任意编程语言。 ## 准备工作 1. 已创建云开发环境(旧套餐可升级),并已启用模型开关(见 [接入大模型](./overview)) 2. 已创建 API Key([获取地址](https://tcb.cloud.tencent.com/dev#/env/apikey)) ## API 端点 ``` https://.api.tcloudbasegateway.com/v1/ai//chat/completions ``` | 参数 | 说明 | 示例 | | --- | --- | --- | | ENV_ID | 环境 ID | `your-env-id` | | PROVIDER | 模型提供商 | `cloudbase` | ## 认证 在请求头中添加 API Key: ``` Authorization: Bearer ``` ## 文本生成 ### 非流式调用 ```bash curl -X POST 'https://.api.tcloudbasegateway.com/v1/ai/cloudbase/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "model": "hy3", "messages": [ {"role": "user", "content": "介绍一下李白"} ], "stream": false }' ``` **响应示例:** ```json { "id": "chatcmpl-xxx", "object": "chat.completion", "created": 1234567890, "model": "hy3", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "李白(701年—762年),字太白,号青莲居士..." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 10, "completion_tokens": 150, "total_tokens": 160 } } ``` ### 流式调用 ```bash curl -X POST 'https://.api.tcloudbasegateway.com/v1/ai/cloudbase/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -H 'Accept: text/event-stream' \ -d '{ "model": "hy3", "messages": [ {"role": "user", "content": "介绍一下你自己"} ], "stream": true }' ``` **响应格式(SSE):** ``` data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"我"},"index":0}]} data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"是"},"index":0}]} data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"DeepSeek"},"index":0}]} data: [DONE] ``` ## 请求参数 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | model | string | 是 | 模型名称 | | messages | array | 是 | 消息列表 | | stream | boolean | 否 | 是否流式返回,默认 `false` | | temperature | number | 否 | 采样温度,范围 0-2 | | top_p | number | 否 | 核采样,范围 0-1 | | max_tokens | number | 否 | 最大生成 token 数 | ### messages 格式 ```json [ {"role": "system", "content": "你是一个有帮助的助手"}, {"role": "user", "content": "用户问题"}, {"role": "assistant", "content": "助手回复"}, {"role": "user", "content": "后续问题"} ] ``` | role | 说明 | | --- | --- | | system | 系统提示,设定助手行为 | | user | 用户消息 | | assistant | 助手回复 | ## 多轮对话 多轮对话需要在每次请求中携带完整的历史消息,详见[多轮对话](./multi-turn)文档。 ## 错误处理 调用过程中遇到错误?请参考 [常见错误码](../FAQ#常见错误码)。 ## 编程语言示例 ### Python ```python import requests url = "https://.api.tcloudbasegateway.com/v1/ai/cloudbase/chat/completions" headers = { "Authorization": "Bearer ", "Content-Type": "application/json" } data = { "model": "hy3", "messages": [{"role": "user", "content": "你好"}], "stream": False } response = requests.post(url, headers=headers, json=data) result = response.json() print(result["choices"][0]["message"]["content"]) ``` ### Node.js ```js const response = await fetch( "https://.api.tcloudbasegateway.com/v1/ai/cloudbase/chat/completions", { method: "POST", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify({ model: "hy3", messages: [{ role: "user", content: "你好" }] }) } ); const result = await response.json(); console.log(result.choices[0].message.content); ``` --- # AI/文本生成/SDK 调用方式/OpenAI SDK 调用 > 当前文档链接: https://docs.cloudbase.net/ai/model/openai-sdk-access CloudBase AI 同时兼容 OpenAI Chat Completions 和 Anthropic Messages API 协议,可直接使用 OpenAI SDK 调用,方便项目迁移和多模型切换。 :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: ## 准备工作 1. 已创建云开发环境(旧套餐可升级),并已启用模型开关(见 [接入大模型](./overview)) 2. 已创建 API Key([获取地址](https://tcb.cloud.tencent.com/dev#/env/apikey)) ## 安装 ```bash # Node.js npm install openai # Python pip install openai ``` ## 配置 将 OpenAI SDK 的 `baseURL` 和 `apiKey` 替换为 CloudBase 的配置: | 配置项 | 值 | | --- | --- | | baseURL | `https://.api.tcloudbasegateway.com/v1/ai/` | | apiKey | 你的 CloudBase API Key | ### 支持的 Provider | Provider | 说明 | | --- | --- | | cloudbase | 资源点套餐(统一入口,支持多种模型) | ## Node.js 示例 ### 非流式调用 ```js const OpenAI = require("openai"); const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); async function main() { const completion = await client.chat.completions.create({ model: "deepseek-v4-flash", messages: [{ role: "user", content: "你好" }], temperature: 0.7 }); console.log(completion.choices[0].message.content); } main(); ``` ### 流式调用 ```js const OpenAI = require("openai"); const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); async function main() { const stream = await client.chat.completions.create({ model: "deepseek-v4-flash", messages: [{ role: "user", content: "介绍一下你自己" }], stream: true }); for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ""; process.stdout.write(content); } } main(); ``` ## Python 示例 ### 非流式调用 ```python from openai import OpenAI client = OpenAI( api_key="", base_url="https://.api.tcloudbasegateway.com/v1/ai/cloudbase" ) completion = client.chat.completions.create( model="deepseek-v4-flash", messages=[{"role": "user", "content": "你好"}], temperature=0.7 ) print(completion.choices[0].message.content) ``` ### 流式调用 ```python from openai import OpenAI client = OpenAI( api_key="", base_url="https://.api.tcloudbasegateway.com/v1/ai/cloudbase" ) stream = client.chat.completions.create( model="deepseek-v4-flash", messages=[{"role": "user", "content": "介绍一下你自己"}], stream=True ) for chunk in stream: content = chunk.choices[0].delta.content or "" print(content, end="", flush=True) ``` ## 多轮对话 多轮对话需要在每次请求中维护完整的 messages 数组,详见[多轮对话](./multi-turn)文档。 ## 切换模型 只需修改 `baseURL` 中的 provider 和 `model` 参数即可切换不同模型: ```js // 使用 cloudbase(资源点套餐,推荐) const cloudbaseClient = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); await cloudbaseClient.chat.completions.create({ model: "deepseek-v4-flash", messages: [{ role: "user", content: "你好" }] }); ``` ## 支持的参数 | 参数 | 类型 | 说明 | | --- | --- | --- | | model | string | 模型名称 | | messages | array | 消息列表 | | stream | boolean | 是否流式返回 | | temperature | number | 采样温度 (0-2) | | top_p | number | 核采样 (0-1) | | max_tokens | number | 最大生成 token 数 | | presence_penalty | number | 存在惩罚 | | frequency_penalty | number | 频率惩罚 | ## 从 OpenAI 迁移 如果你的项目原本使用 OpenAI,迁移到 CloudBase 只需两步: 1. **修改配置** ```js // 原 OpenAI 配置 const client = new OpenAI({ apiKey: "sk-xxx" // OpenAI API Key }); // 改为 CloudBase 配置 const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); ``` 2. **修改模型名称** ```js // 原 OpenAI 模型 model: "gpt-4" // 改为 CloudBase 支持的模型 model: "deepseek-v4-flash" // 或其他已开启的模型 ``` ## 相关文档 如果你使用 Anthropic SDK,也可以通过同样的方式接入云开发,详见 [Anthropic SDK 调用](./anthropic-sdk-access)。 ## 错误处理 ```js const OpenAI = require("openai"); const client = new OpenAI({ apiKey: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); try { const completion = await client.chat.completions.create({ model: "deepseek-v4-flash", messages: [{ role: "user", content: "你好" }] }); console.log(completion.choices[0].message.content); } catch (error) { if (error instanceof OpenAI.APIError) { console.error("API 错误:", error.status, error.message); } else { throw error; } } ``` --- # AI/文本生成/SDK 调用方式/Anthropic SDK 调用 > 当前文档链接: https://docs.cloudbase.net/ai/model/anthropic-sdk-access CloudBase AI 同时兼容 OpenAI Chat Completions 和 Anthropic Messages API 协议,可直接使用 Anthropic SDK 调用,只需将 `authToken` 和 `baseURL` 替换为云开发配置即可。 :::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → ::: :::info 认证方式 CloudBase 使用 `Authorization: Bearer ` 进行认证,对应 Anthropic SDK 的 `authToken` 参数,**不是** `apiKey`。 ::: ## 准备工作 1. 已创建云开发环境(旧套餐可升级),并已启用模型开关(见 [接入大模型](./overview)) 2. 已创建 API Key([获取地址](https://tcb.cloud.tencent.com/dev#/env/apikey)) ## 安装 ```bash # Node.js npm install @anthropic-ai/sdk # Python pip install anthropic ``` ## 配置 初始化时,将 `authToken` 和 `baseURL` 传给 SDK: | 参数 | 值 | | --- | --- | | authToken | 云开发 API Key | | baseURL | `https://.api.tcloudbasegateway.com/v1/ai/cloudbase` | 将 `` 替换为实际的云开发环境 ID,`` 替换为实际的 API Key。 ## Node.js 示例 ### 非流式调用 ```js const Anthropic = require("@anthropic-ai/sdk"); const client = new Anthropic({ authToken: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); async function main() { const message = await client.messages.create({ model: "hy3", max_tokens: 1024, messages: [{ role: "user", content: "你好" }] }); console.log(message.content[0].text); } main(); ``` ### 流式调用 ```js const Anthropic = require("@anthropic-ai/sdk"); const client = new Anthropic({ authToken: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); async function main() { const stream = await client.messages.stream({ model: "hy3", max_tokens: 1024, messages: [{ role: "user", content: "介绍一下你自己" }] }); for await (const chunk of stream) { if ( chunk.type === "content_block_delta" && chunk.delta.type === "text_delta" ) { process.stdout.write(chunk.delta.text); } } } main(); ``` ## Python 示例 ### 非流式调用 ```python import anthropic client = anthropic.Anthropic( auth_token="", base_url="https://.api.tcloudbasegateway.com/v1/ai/cloudbase" ) message = client.messages.create( model="hy3", max_tokens=1024, messages=[{"role": "user", "content": "你好"}] ) print(message.content[0].text) ``` ### 流式调用 ```python import anthropic client = anthropic.Anthropic( auth_token="", base_url="https://.api.tcloudbasegateway.com/v1/ai/cloudbase" ) with client.messages.stream( model="hy3", max_tokens=1024, messages=[{"role": "user", "content": "介绍一下你自己"}] ) as stream: for text in stream.text_stream: print(text, end="", flush=True) ``` ## 多轮对话 多轮对话需要在每次请求中维护完整的 messages 数组,详见[多轮对话](./multi-turn)文档。 ## 从 Anthropic 迁移 如果你的项目原本使用 Anthropic SDK,迁移到 CloudBase 需要修改客户端初始化部分: ```js // 原 Anthropic 配置 const client = new Anthropic({ apiKey: "" }); // 改为 CloudBase 配置 const client = new Anthropic({ authToken: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); ``` 同时将模型名称替换为云开发中已开启的模型(如 `hy3`),其余业务代码无需改动。 ## 错误处理 ```js const Anthropic = require("@anthropic-ai/sdk"); const client = new Anthropic({ authToken: "", baseURL: "https://.api.tcloudbasegateway.com/v1/ai/cloudbase" }); try { const message = await client.messages.create({ model: "hy3", max_tokens: 1024, messages: [{ role: "user", content: "你好" }] }); console.log(message.content[0].text); } catch (error) { if (error instanceof Anthropic.APIError) { console.error("API 错误:", error.status, error.message); } else { throw error; } } ``` ## 支持的参数 | 参数 | 类型 | 说明 | | --- | --- | --- | | model | string | 模型名称 | | messages | array | 消息列表 | | system | string | 系统提示词 | | max_tokens | number | 最大生成 token 数(必填) | | stream | boolean | 是否流式返回 | | temperature | number | 采样温度 (0-1) | | top_p | number | 核采样 (0-1) | --- # AI/文本生成/接入 AI 工具/云开发 LLM API 支持协议介绍 > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/protocol 云开发 CloudBase AI 支持以下协议:Chat Completions、Responses API、Anthropic Messages API,凡是支持自定义接口的 AI 工具都可以直接接入。 最通用的大模型对话协议,绝大多数 AI 工具默认使用此协议(OpenAI Compatible)。 以工具调用为例,完整流程需要两次 API 请求: **第一次请求:发送用户消息和可用工具定义** ```bash curl https://{{YOUR-ENV-ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {{YOUR-API-KEY}}" \ -d '{ "model": "hy3", "messages": [ { "role": "user", "content": "帮我查一下北京今天的天气。" } ], "tools": [ { "type": "function", "function": { "name": "get_current_weather", "description": "获取指定城市的天气预报", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "城市名称" } }, "required": ["location"] } } } ] }' ``` 模型返回 `finish_reason: "tool_calls"`,表示需要客户端执行工具。客户端调用工具获取结果后,将工具返回值追加到 `messages` 中发起第二次请求: **第二次请求:携带工具调用结果** ```bash curl https://{{YOUR-ENV-ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {{YOUR-API-KEY}}" \ -d '{ "model": "hy3", "messages": [ { "role": "user", "content": "帮我查一下北京今天的天气。" }, { "role": "assistant", "tool_calls": [{ "id": "call_123", "type": "function", "function": { "name": "get_current_weather", "arguments": "{\"location\": \"北京\"}" } }] }, { "role": "tool", "tool_call_id": "call_123", "content": "{\"temperature\": \"25℃\", \"weather\": \"晴朗\"}" } ] }' ``` 模型会根据工具返回的结果生成最终回复。 OpenAI 推出的新一代 API,原生支持状态管理,通过 `previous_response_id` 串联多轮对话,无需客户端手动拼接历史消息。 **第一次请求:发送用户消息和工具定义** ```bash curl "https://{{YOUR-ENV-ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase/responses" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {{YOUR-API-KEY}}" \ -d '{ "model": "hy3", "input": "请问今天北京的天气怎么样?", "tools": [ { "type": "function", "name": "get_current_weather", "description": "获取指定城市的实时天气", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "城市名称" } }, "required": ["location"] } } ], "tool_choice": "auto" }' ``` 模型返回 `function_call` 类型的 output,包含响应 ID(如 `resp_xxx`)。客户端调用工具后,通过 `previous_response_id` 串联上下文: **第二次请求:携带工具结果和 `previous_response_id`** ```bash curl "https://{{YOUR-ENV-ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase/responses" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {{YOUR-API-KEY}}" \ -d '{ "model": "hy3", "previous_response_id": "resp_570bc50971764d1fb1c167d90fc1a584", "input": [ { "type": "function_call_output", "call_id": "call_abc", "output": "{\"temperature\": \"26°C\", \"condition\": \"晴朗\"}" } ] }' ``` :::note 虽然 Responses API 在客户端只需传 `previous_response_id` 即可串联上下文,但服务端仍在维护完整的对话历史。 ::: Anthropic(Claude 模型)的原生接口协议。 :::note 云开发 CloudBase AI 使用 `Authorization: Bearer` 方式鉴权,而非 Anthropic 原生的 `x-api-key` 头。请求时需要携带 `anthropic-version` 头。 ::: **请求示例(带工具调用):** ```bash curl "https://{{YOUR-ENV-ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase/v1/messages" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {{YOUR-API-KEY}}" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "hy3", "max_tokens": 1024, "tools": [ { "name": "get_weather", "description": "获取指定城市的实时天气", "input_schema": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称" } }, "required": ["city"] } } ], "messages": [ { "role": "user", "content": "请问今天北京的天气怎么样?" } ] }' ``` 模型返回 `stop_reason: "tool_use"` 时,`content` 数组中会包含 `type: "tool_use"` 块,记录其中的 `id` 用于后续工具结果回传。 **使用缓存** Anthropic Messages API 支持两种缓存模式: **显式缓存** 在特定内容块末尾添加 `"cache_control": {"type": "ephemeral"}` 标记,每个请求最多设置 **4 个断点**。适合需要精确控制缓存位置的场景: ```bash curl "https://{{YOUR-ENV-ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase/v1/messages" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {{YOUR-API-KEY}}" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "hy3", "max_tokens": 1024, "system": [ { "type": "text", "text": "你是一个专业的天气查询助手。", "cache_control": {"type": "ephemeral"} } ], "tools": [ { "name": "get_weather", "description": "获取指定城市的实时天气", "input_schema": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称" } }, "required": ["city"] }, "cache_control": {"type": "ephemeral"} } ], "messages": [ { "role": "user", "content": "请问今天北京的天气怎么样?" } ] }' ``` **自动缓存** 在请求体最外层声明 `"cache_control": {"type": "ephemeral"}`,系统自动识别重复的静态前缀进行缓存,适合多轮对话场景: ```bash curl "https://{{YOUR-ENV-ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase/v1/messages" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {{YOUR-API-KEY}}" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "hy3", "max_tokens": 1024, "cache_control": {"type": "ephemeral"}, "system": "你是一个专业的天气查询助手。", "tools": [ { "name": "get_weather", "description": "获取指定城市的实时天气", "input_schema": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称" } }, "required": ["city"] } } ], "messages": [ { "role": "user", "content": "请问今天北京的天气怎么样?" } ] }' ``` --- # AI/文本生成/接入 AI 工具/CC Switch > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/cc-switch [CC Switch](https://github.com/farion1231/cc-switch) 是一款开源的桌面应用,用于统一管理多个 AI 编程工具(Claude Code、Codex、Gemini CLI 等)的供应商配置。只需在 CC Switch 中添加一次云开发供应商配置,即可在多个工具中使用云开发大模型,无需为每个工具单独配置。 ## 配置步骤 我们以 CC Switch **v3.16.1** 版本为例。 ### 1. 安装 CC Switch 推荐使用 Homebrew 安装: ```bash brew install --cask cc-switch ``` 或从 [Releases 页面](https://github.com/farion1231/cc-switch/releases) 下载 `CC-Switch-*-macOS.dmg`。 从 [Releases 页面](https://github.com/farion1231/cc-switch/releases) 下载 `CC-Switch-*-Windows.msi` 安装包。 从 [Releases 页面](https://github.com/farion1231/cc-switch/releases) 下载对应的包: - `CC-Switch-*-Linux.deb` — Debian/Ubuntu - `CC-Switch-*-Linux.rpm` — Fedora/RHEL/openSUSE - `CC-Switch-*-Linux.AppImage` — 通用格式 ### 2. 添加云开发供应商 1. 打开 CC Switch 桌面应用 2. 点击右上角 **+** 按钮,选择 **添加供应商** 3. 在预设列表中选择 **自定义配置** 4. 填写以下信息: | 字段 | 说明 | |------|------| | **供应商名称** | 输入"云开发"或自定义名称 | | **API Key** | 在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai) 中获取的 API 密钥 | | **Base URL** | 请求地址:`https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase` | 5. 根据使用的 AI 工具选择 **API 格式** #### 选择 API 格式 云开发 CloudBase AI 支持以下协议: - **Anthropic Messages**:Claude 模型的原生接口协议 - **Chat Completions**:OpenAI 兼容的通用对话协议 - **Responses API**:OpenAI 新一代 API,目前仅部分模型支持 根据工具选择 API 格式: | AI 工具 | 推荐 API 格式 | 说明 | |---------|---------------|------| | Claude Code | Anthropic Messages | 原生格式,无需额外配置 | | Claude Desktop | Anthropic Messages | 原生格式,无需额外配置 | | Codex | Chat Completions | 需开启本地路由 | | Gemini CLI | Chat Completions | 通用格式 | | OpenCode | Chat Completions | 通用格式 | | OpenClaw | Chat Completions | 通用格式 | | Hermes | Chat Completions | 通用格式 | **配置 Claude Code / Claude Desktop(Anthropic Messages):** 选择 **Anthropic Messages** 格式(默认),粘贴云开发的 `API Key` 和 `Base URL` 即可。无需修改高级选项。 **配置 Codex(Chat Completions):** 1. 在高级选项中,将 API 格式切换为 **Chat Completions** 2. 勾选 **需要本地路由映射**(Needs Local Route Mapping) 3. 在模型映射表中填入模型 ID(从 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识) 6. 保存配置,切换启用云开发供应商即可使用 :::note 切换供应商后,大多数 AI 工具需要**重启终端或对应的 CLI 工具**。Claude Code 支持热切换,无需重启。 ::: ## 在 AI 工具中使用 配置完成后,以下 AI 工具可直接使用云开发大模型: - **Claude Code** — 使用 Anthropic Messages 协议(原生),终端运行 `claude` - **Claude Desktop** — 使用 Anthropic Messages 协议(原生) - **Codex** — 使用 Chat Completions 协议(需开启本地路由),终端运行 `codex` - **Gemini CLI** — 使用 Chat Completions 协议,终端运行 `gemini` - **OpenCode** — 使用 Chat Completions 协议 - **OpenClaw** — 使用 Chat Completions 协议 - **Hermes** — 使用 Chat Completions 协议 ## 常见错误排查 | 报错信息 | 解决方法 | |---------|----------| | `401` Credentials are invalid | API Key 填写错误,或者可能被删除,请前往 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai) 重新获取 | | `404` 未找到模型 / Model not found | Base URL 或模型名称配置不正确,请确认 AI 控制台中已启用该模型 | | `429` Token usage exceeded quota limit | Token 消耗完或未在 AI 控制台中启用模型 | | Codex 连接失败 / Connection refused | 确认已开启本地路由服务(代理模式),并且 API 格式选择为 Chat Completions | | 工具无法连接 | 检查网络环境是否能正常访问云开发 API,确认 Base URL 中的环境 ID 是否正确 | --- # AI/文本生成/接入 AI 工具/WorkBuddy > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/workbuddy [WorkBuddy](https://www.codebuddy.cn/work/) 是腾讯云代码助手推出的AI Agent 办公工具,自主规划并交付多模态复杂任务结果,支持多Agents 并行工作,极致提效。 ## 配置步骤 您可以选择通过 UI 配置,也可以选择编辑 WorkBuddy 自定义模型配置文件快捷配置。 我们以2026年6月9日最新 WorkBuddy 版本为例。 1. 打开 WorkBuddy,点击右上角账号菜单,进入 **设置** ![workbuddy](https://qcloudimg.tencent-cloud.cn/raw/f6a0ce6db8c5fd78dc0dbf985a92f6f5.png) 2. 选择**模型**,点击**添加模型**按钮 ![workbuddy](https://qcloudimg.tencent-cloud.cn/raw/e499f58ba428194291c194156254e6d7.png) 3. 选择**自定义**模型 ![workbuddy](https://qcloudimg.tencent-cloud.cn/raw/6abea106af884f9a8f6942f5172a426a.png) 4. 填写 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)复制的 `Base URL`和 `API Key`,从 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel)模型列表中选择已启用的模型,复制模型名,填入并保存 5. 选择相应的大模型即可对话(您可能需要重启 WorkBuddy 才能在模型列表中找到已添加的模型) ![workbuddy](https://qcloudimg.tencent-cloud.cn/raw/f1b2bb4dd5ac62880f517b73b6966254.png) 1. 打开配置文件 :::note 如果通过您文件浏览器找到文件,文件浏览器默认会隐藏掉以"."开头的文件名的文件,您可能需要打开文件浏览器的隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开,Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目" 打开。 ::: - Windows 配置路径 ```path C:\Users\<你的用户名>\.workbuddy\models.json ``` - Mac/Linux 配置路径 ```path ~/.workbuddy/models.json ``` 2. 从 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)复制的 `Base URL` 和 `API Key` 和模型名称,配置保存如下(以大模型 `hy3` 为例) ```json { "models": [ { "id": "hy3", "name": "hy3", "vendor": "Custom", "url": "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase", "apiKey": "eyJhbGciOiJSUzI1N...", "supportsToolCall": true, "supportsImages": false, "supportsReasoning": false } ] } ``` 3. 选择相应的大模型即可对话 ![workbuddy](https://qcloudimg.tencent-cloud.cn/raw/f1b2bb4dd5ac62880f517b73b6966254.png) ## 常见错误排查 | 报错信息 | 解决方法 | |---------------------|----------------------------------------------------------------------------------------------------| | `404` 错误:未找到模型 | Base URL 填写不正确,请前往 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中复制正确的 Base URL | | `401` 错误:请重新登录后继续使用 | API Key 填写错误,或者可能被删除 | | `6004` 错误 | 配置了错误的大模型名称,或者大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | --- # AI/文本生成/接入 AI 工具/CodeBuddy > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/codebuddy [CodeBuddy](https://www.codebuddy.cn/) 现已实现插件、IDE、CLI三端全开发场景覆盖。提供覆盖编码、扩展与协作的全栈开发生态。 ## 配置步骤 您可以选择通过 UI 配置,也可以选择编辑 [CodeBuddy 自定义模型配置文件](https://www.codebuddy.ai/docs/zh/cli/models)快捷配置。 我们以2026年6月9日最新 CodeBuddy 版本为例。 1. 打开 CodeBuddy,点击**打开 Agents** ![codebuddy](https://qcloudimg.tencent-cloud.cn/raw/4d21e7bb15e1db9163028287eb29126b.png) 2. 对话面板大模型列表中,选择**配置自定义模型** ![codebuddy](https://qcloudimg.tencent-cloud.cn/raw/9115ad396eface4b189980660a64d9aa.png) 3. 填写 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)复制的 `Base URL`和 `API Key` ,从 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel)模型列表中选择已启用的模型,复制模型名,填入并保存 4. 选择相应的大模型即可对话(您可能需要重启 CodeBuddy 面板才能在模型列表中找到已添加的模型) 1. 打开配置文件 :::note 如果通过您文件浏览器找到文件,文件浏览器默认会隐藏掉以"."开头的文件名的文件,您可能需要打开文件浏览器的隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开,Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目" 打开。 ::: - Windows 配置路径 ```path C:\Users\<你的用户名>\.codebuddy\models.json ``` - Mac/Linux 配置路径 ```path ~/.codebuddy/models.json ``` 2. 从 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)复制的 `Base URL` 和 `API Key` 和模型名称,配置保存如下(以大模型 `hy3` 为例) ```json { "models": [ { "id": "hy3", "name": "hy3", "vendor": "Custom", "url": "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase", "apiKey": "eyJhbGciOiJSUzI1N...", "supportsToolCall": true, "supportsImages": false, "supportsReasoning": false } ] } ``` 3. 选择相应的大模型即可对话(您可能需要重启 CodeBuddy 面板才能在模型列表中找到已添加的模型) ## 常见错误排查 | 报错信息 | 解决方法 | |----------------------------------------------|----------------------------------------------------------------------------------------------------| | `404` 错误:未找到模型 | Base URL 填写不正确,请前往 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中复制正确的 Base URL | | `401` 错误:Credentials are invalid | API Key 填写错误,或者可能被删除 | | 请求太频繁,请稍后再试:Token usage exceeded quota limit | 配置了错误的大模型名称,或者大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | --- # AI/文本生成/接入 AI 工具/CodeBuddy Code > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/codebuddy-code [CodeBuddy Code](https://www.codebuddy.ai/docs/zh/cli/overview) 是腾讯云代码助手 CodeBuddy 推出的终端 AI 编程工具(CLI),支持通过命令行与 AI 对话、生成代码、操作文件和执行终端命令。 ## 配置步骤 CodeBuddy Code 通过 `models.json` 配置文件管理自定义模型。 配置字段对照表 | 字段 | 解释 | |---|---| | id | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | name | 模型显示名称,便于区分 | | url | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | apiKey | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | 1. 打开或创建 CodeBuddy Code 配置文件 :::note 如果通过您文件浏览器找到文件,文件浏览器默认会隐藏掉以"."开头的文件名的文件,您可能需要打开文件浏览器的隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开,Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目" 打开。 ::: - Mac/Linux 配置路径 ```path ~/.codebuddy/models.json ``` - Windows 配置路径 ```path C:\Users\<你的用户名>\.codebuddy\models.json ``` 2. 填入以下配置 ```json { "models": [ { "id": "hy3", "name": "hy3 (CloudBase)", "vendor": "CloudBase", "url": "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase", "apiKey": "eyJhbGciOiJSUzI1N...", "maxInputTokens": 128000, "maxOutputTokens": 8192, "supportsToolCall": true, "supportsImages": true } ], "availableModels": ["hy3"] } ``` 3. 启动 CodeBuddy Code ```bash codebuddy --model hy3 ``` 或在交互中通过 `/model` 命令切换模型。 在项目根目录创建 `.codebuddy/models.json`,配置格式与用户级相同。项目级配置优先级高于用户级,会覆盖同 ID 模型。 ```path <项目根目录>/.codebuddy/models.json ``` 配置内容示例同用户级配置。启动时 CodeBuddy Code 会自动读取项目级配置。 ## 常见错误排查 | 报错信息 | 解决方法 | |---|---| | 请求失败 / 连接超时 | 确认 `url` 字段中的 Base URL 是否正确 | | `401` 认证失败 | API Key 错误或已失效,请在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中重新获取 | | `429` Token usage exceeded quota limit | 模型名称填写错误,或者大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | | 模型列表中看不到 | 检查 `availableModels` 数组中是否包含该模型 ID | --- # AI/文本生成/接入 AI 工具/Claude Code > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/claude-code [Claude Code](https://claude.com/product/claude-code) 是一款运行在终端的 AI 编程智能体,它能够直接读取本地代码库,通过自然语言与开发者交互,并自主完成代码阅读、多文件修改、运行测试及执行命令等复杂开发任务。 当您安装 Claude Code 之后,无需登录 Anthropic 账号,按照如下操作配置自定义模型即可使用。 ## 配置步骤 我们以 Claude Code-**v2.1.170** 版本为例。 配置字段对照表 | 环境变量 | 解释 | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | ANTHROPIC_BASE_URL | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | ANTHROPIC_AUTH_TOKEN | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | ANTHROPIC_MODEL | 主会话使用的大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | ANTHROPIC_DEFAULT_OPUS_MODEL | Opus 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | ANTHROPIC_DEFAULT_SONNET_MODEL | Sonnet 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | ANTHROPIC_DEFAULT_HAIKU_MODEL | Haiku 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | [CC Switch](https://github.com/farion1231/cc-switch) 是一款开源的桌面应用,统一管理多个 AI 编程工具的供应商配置。安装方式参考 [CC Switch 配置文档](./cc-switch)。 1. 打开 CC Switch,添加云开发供应商 | 字段 | 说明 | |------|------| | **API Key** | 在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai) 中获取 | | **Base URL** | `https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase` | | **API 格式** | Anthropic Messages(默认) | 2. 切换启用云开发供应商,启动 Claude Code 即可使用 1. 打开或创建你的 Claude Code 配置文件 :::note 如果通过您文件浏览器找到文件,文件浏览器默认会隐藏掉以"."开头的文件名的文件,您可能需要打开文件浏览器的隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开。Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目"打开。如果找不到文件,请确认 Claude Code 已经安装并打开过一次。 ::: Mac/Linux(`~` 代表您当前的用户目录) ```path ~/.claude/settings.json ``` Windows ```path C:\Users\你的用户名\.claude\settings.json ``` 2. 新增或替换 `env` 字段,比如想简单任务使用 haiku 模式时使用 `hy3` 代替,sonnet 模式使用 `deepseek-v4-pro[1m]` 代替,opus 模式使用 `glm-5.2[1m]` 代替,配置示例如下: ```json { "env": { "ANTHROPIC_BASE_URL": "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase", "ANTHROPIC_AUTH_TOKEN": "eyJhbGciOiJSUzI1N...", "ANTHROPIC_DEFAULT_HAIKU_MODEL": "hy3", "ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v4-pro-202606[1m]", "ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-5.2[1m]" } } ``` 如果您只想临时体验云开发大模型的能力,请优先选择在终端命令临时注入环境变量,关闭终端即配置失效。 当您同时通过 **setting.json** 配置 `env` 字段时,Claude Code 会优先读取临时注入的环境变量。 Mac/Linux 用法(当前窗口一直有效) ```bash export ANTHROPIC_BASE_URL="https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" export ANTHROPIC_AUTH_TOKEN="eyJhbGciOiJSUzI1N..." export ANTHROPIC_MODEL="hy3" ``` Windows CMD 用法(当前窗口一直有效) ```bash set ANTHROPIC_BASE_URL=https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase set ANTHROPIC_AUTH_TOKEN=eyJhbGciOiJSUzI1N... set ANTHROPIC_MODEL=hy3 ``` Windows PowerShell 用法(当前窗口一直有效) ```bash $env:ANTHROPIC_BASE_URL = "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" $env:ANTHROPIC_AUTH_TOKEN = "eyJhbGciOiJSUzI1N..." $env:ANTHROPIC_MODEL = "hy3" ``` ## 更多配置 您可以在 [Claude Code](https://code.claude.com/docs/en/settings) 官网查询更多模型的配置指令 :::note 接入第三方模型后,Claude Code 内置的联网搜索功能不可用。如需搜索能力,可通过 MCP 接入[云开发 AI 搜索服务](../cloudbase-ai-toolkit/ide-setup)。 ::: ## 使用方式 终端切到指定目录下,输入 claude 命令即可使用配置的自定义云开发大模型,更多命令和功能,您可参考[官方文档](https://code.claude.com/docs/zh-CN/commands) ```bash claude ``` ![Claude Code](https://qcloudimg.tencent-cloud.cn/raw/c030c599e47bafbc17ad0da6f07b3199.gif) ## 常见错误排查 | 报错信息 | 解决方法 | | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `429` Token usage exceeded quota limit | Token 消耗完,或者未启用 | | `401` Credentials are invalid | API Key 填写错误,或者可能被删除 | | `429` API Error: Request rejected | 配置了错误的大模型名称,或者大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | --- # AI/文本生成/接入 AI 工具/Codex > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/codex-app [Codex](https://openai.com/index/introducing-codex/) 是 OpenAI 推出的桌面端 AI 编程 Agent,支持通过配置文件接入第三方模型提供商。其配置方式与 [Codex CLI](./codex-cli) 一致。 ## 配置步骤 Codex 与 Codex CLI 共享同一份 `config.toml` 配置文件。 配置字段对照表 | 字段 | 解释 | | --- | --- | | model | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | model_provider | 提供商 ID,指向自定义的 `[model_providers]` 配置 | | base_url | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | experimental_bearer_token | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | [CC Switch](https://github.com/farion1231/cc-switch) 是一款开源的桌面应用,统一管理多个 AI 编程工具的供应商配置。安装方式参考 [CC Switch 配置文档](./cc-switch)。 1. 打开 CC Switch,添加云开发供应商 | 字段 | 说明 | |------|------| | **API Key** | 在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai) 中获取 | | **Base URL** | `https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase` | | **API 格式** | Chat Completions(需开启本地路由) | 2. 在高级选项中启用本地路由映射,填入模型 ID 3. 切换启用云开发供应商,重启 Codex 即可使用 1. 打开或创建配置文件 :::note 如果通过您文件浏览器找到文件,文件浏览器默认会隐藏掉以"."开头的文件名的文件,您可能需要打开文件浏览器的隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开,Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目" 打开。 ::: - Mac/Linux 配置路径 ```path ~/.codex/config.toml ``` - Windows 配置路径 ```path C:\Users\<你的用户名>\.codex\config.toml ``` 2. 新增或修改以下配置 ```toml model = "hy3" model_provider = "cloudbase" [model_providers.cloudbase] name = "CloudBase" base_url = "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" experimental_bearer_token = "eyJhbGciOiJSUzI1N..." ``` 3. 重启 Codex 配置完成后重启 Codex,即可使用云开发的模型进行编程对话。 ## 常见错误排查 | 报错信息 | 解决方法 | |---|---| | `404` 模型未找到 | Base URL 填写不正确,请前往 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中复制正确的 Base URL | | `401` Credentials are invalid | API Key 填写错误,或者可能被删除 | | `429` Token usage exceeded quota limit | 配置了错误的大模型名称,或者大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | | 模型不响应 | 确认模型名称是否正确,以及模型是否已在控制台启用 | --- # AI/文本生成/接入 AI 工具/Codex CLI > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/codex-cli [Codex CLI](https://github.com/openai/codex) 是 OpenAI 推出的运行在终端的 AI 编程智能体,能够读取本地代码库,通过自然语言交互完成代码编写、文件操作和命令执行等开发任务。 ## 配置步骤 Codex CLI 支持通过 `config.toml` 配置文件接入自定义模型。 配置字段对照表 | 字段 | 解释 | |----------------|---------------------------------------------------------------------------------------------| | model | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | model_provider | 提供商 ID,指向自定义的 `[model_providers]` 配置 | | base_url | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | env_key | API Key 对应的环境变量名 | [CC Switch](https://github.com/farion1231/cc-switch) 是一款开源的桌面应用,统一管理多个 AI 编程工具的供应商配置。安装方式参考 [CC Switch 配置文档](./cc-switch)。 1. 打开 CC Switch,添加云开发供应商 | 字段 | 说明 | |------|------| | **API Key** | 在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai) 中获取 | | **Base URL** | `https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase` | | **API 格式** | Chat Completions(需开启本地路由) | 2. 在高级选项中启用本地路由映射,填入模型 ID 3. 切换启用云开发供应商,启动 Codex CLI 即可使用 1. 打开或创建 Codex CLI 配置文件 :::note 如果通过您文件浏览器找到文件,文件浏览器默认会隐藏掉以"."开头的文件名的文件,您可能需要打开文件浏览器的隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开,Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目" 打开。 ::: - Mac/Linux 配置路径 ```path ~/.codex/config.toml ``` - Windows 配置路径 ```path C:\Users\<你的用户名>\.codex\config.toml ``` 2. 新增或修改以下配置 ```toml model = "hy3" model_provider = "cloudbase" [model_providers.cloudbase] name = "CloudBase" base_url = "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" env_key = "CLOUDBASE_API_KEY" ``` 3. 设置 API Key 环境变量 Mac/Linux(当前终端窗口有效) ```bash export CLOUDBASE_API_KEY="eyJhbGciOiJSUzI1N..." ``` Windows CMD(当前终端窗口有效) ```bash set CLOUDBASE_API_KEY=eyJhbGciOiJSUzI1N... ``` Windows PowerShell(当前终端窗口有效) ```bash $env:CLOUDBASE_API_KEY = "eyJhbGciOiJSUzI1N..." ``` :::note 以上环境变量为临时生效,关闭终端后失效。如需永久生效,可将环境变量写入 Shell 配置文件(如 `~/.zshrc` 或 `~/.bashrc`)中。 ::: 4. 启动 Codex CLI ```bash codex ``` 无需修改配置文件,通过命令行直接覆盖: ```bash export CLOUDBASE_API_KEY="eyJhbGciOiJSUzI1N..." codex --config model='"hy3"' \ --config model_provider='"cloudbase"' \ --config 'model_providers.cloudbase.base_url="https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase"' \ --config 'model_providers.cloudbase.env_key="CLOUDBASE_API_KEY"' ``` ## 常见错误排查 | 报错信息 | 解决方法 | |----------------------------------------|----------------------------------------------------------------------------------------------------| | `404` 模型未找到 | Base URL 填写不正确,请前往 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中复制正确的 Base URL | | `401` Credentials are invalid | API Key 填写错误,或者可能被删除 | | `429` Token usage exceeded quota limit | 配置了错误的大模型名称,或者大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | --- # AI/文本生成/接入 AI 工具/Cursor > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/cursor [Cursor](https://cursor.com/) 是一款专为与 AI 协同工作而设计的下一代智能代码编辑器。 :::note 由于 Cursor 的限制,只有订阅了 Cursor Pro 及以上套餐的用户才支持自定义配置模型。 ::: ## 配置步骤 填入各字段含义如下 | 填入字段 | 解释 | |--------------------------|---------------------------------------------------------------------------------------------| | OpenAI API Key | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | Override OpenAI Base URL | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | Add Custom Model | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | 1. 打开 Cursor 设置,进入 **Models** 2. 在 **OpenAI API Key** 处填写 CloudBase API Key,启用 **Override OpenAI Base URL** 开关,在 **Override OpenAI Base URL** 处填写 **Base URL** ![cursor.png](https://qcloudimg.tencent-cloud.cn/raw/33e6da394aef19126c8d4694536bd58e.png) 3. 点击 **Add Custom Model**,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入,点击 **Add** ![cursor2.png](https://qcloudimg.tencent-cloud.cn/raw/182fa94611e5da4c1961934f5b9c778d.png) 4. 在模型列表中选择添加的自定义大模型即可开始对话 ![cursor3.png](https://qcloudimg.tencent-cloud.cn/raw/1449eaeda161bdd7c0401cfd8c10784f.png) --- # AI/文本生成/接入 AI 工具/Trae > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/trae [Trae](https://trae.ai/) 是字节跳动推出的 AI 原生 IDE,内置 AI 对话与代码生成能力,支持自定义模型接入。 ## 配置步骤 填入各字段含义如下 | 填入字段 | 解释 | |-----------|---------------------------------------------------------------------------------------------| | Base URL | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | API Key | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | Model ID | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | 1. 打开 Trae,在对话输入框 **模型列表选择** 按钮点击 **添加模型** ![trae.png](https://qcloudimg.tencent-cloud.cn/raw/487a9545527ab7682980007549db72d6.png) 2. 点击 **Custom Config**,填写 `Base URL`、`API Key` 和模型 ID,保存配置 ![trae2.png](https://qcloudimg.tencent-cloud.cn/raw/fca2592d8af24107d837aac1e70ebe72.png) 3. 从 **模型列表** 中选择已启用的模型即可开始对话 ![trae3.png](https://qcloudimg.tencent-cloud.cn/raw/487a9545527ab7682980007549db72d6.png) ## 常见错误排查 | 报错信息 | 解决方法 | |--------|----------------------------------------------------------------------------------------------------| | 添加模型失败 | Base URL 填写不正确,或者 API Key 不正确,或者大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | --- # AI/文本生成/接入 AI 工具/GitHub Copilot > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/copilot [GitHub Copilot](https://github.com/features/copilot) 是 GitHub 推出的 AI 编程助手,深度集成在 VS Code、JetBrains 等主流编辑器中,提供代码补全、聊天对话、Agent 等能力。 ## 配置步骤 GitHub Copilot 既提供了 IDE 插件,也提供了 CLI 工具。 以 VS Code 为例 1. 在对话框的模型列表中,点击**设置图标**,打开 **Language Models** 面板 ![copilot1.png](https://qcloudimg.tencent-cloud.cn/raw/60df302a8b626a1f9b32c6da40cb790f.png) 2. 点击 **Add Models** 按钮 ![copilot2.png](https://qcloudimg.tencent-cloud.cn/raw/e5ee6d966a79bd83e9e162550c7a51c7.png) 3. 在菜单中选择 **Custom Endpoint** ![copilot3.png](https://qcloudimg.tencent-cloud.cn/raw/f8625257d779216616d48e520e4ef5ad.png) 4. 弹出交互式弹窗,输入 **Group Name**(模型组名称),在这里以 `cloudbase` 为示例 ![copilot4.png](https://qcloudimg.tencent-cloud.cn/raw/c9e0674ecbf93aa3cde74abcec1a633c.png) 5. 输入 **API Key**,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 ![copilot5.png](https://qcloudimg.tencent-cloud.cn/raw/f3f96caa99e1b9c2f19ce2b331109886.png) 6. 选择 **Chat Completions** 协议 ![copilot6.png](https://qcloudimg.tencent-cloud.cn/raw/35c772a02c3d4984bfaab3b82f0c7b96.png) 7. 回车之后,会自动打开模型配置文件,编辑 `models` 中的字段 填入各字段含义如下 | 填入字段 | 解释 | |--------|---------------------------------------------------------------------------------------------| | url | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | id | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | name | 大模型展示名称,可以任意填写 | ```json [ { "name": "cloudbase", "vendor": "customendpoint", "apiKey": "${input:chat.lm.secret.-6bd4af55}", "apiType": "chat-completions", "models": [ { "id": "hy3", "name": "hy3", "url": "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase", "toolCalling": true, "vision": true, "maxInputTokens": 128000, "maxOutputTokens": 16000 } ] } ] ``` :::note `apiKey` 字段由 Copilot 自动生成,无需手动修改。`toolCalling`、`vision`、`maxInputTokens`、`maxOutputTokens` 等字段可根据实际使用的模型能力按需调整。 ::: 8. 配置完成后,在对话框的模型列表中,展开 **Other Models**,选择创建的自定义模型即可开始对话 [GitHub Copilot CLI](https://docs.github.com/en/copilot/github-copilot-in-the-cli) 是运行在终端中的 AI 编程助手,支持通过环境变量配置自定义模型。 :::note 使用 Copilot CLI 自定义模型(BYOK)需要模型支持**工具调用**(Tool Calling)和**流式传输**(Streaming)。为获得最佳效果,建议使用上下文窗口不少于 128k tokens 的模型。 ::: #### CLI 配置大模型 配置字段对照表 | 环境变量 | 是否必填 | 解释 | |---------------------------|------|---------------------------------------------------------------------------------------------| | COPILOT_PROVIDER_BASE_URL | 是 | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | COPILOT_PROVIDER_API_KEY | 是 | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | COPILOT_MODEL | 是 | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | COPILOT_PROVIDER_TYPE | 否 | 提供程序类型,CloudBase AI 兼容 OpenAI 协议,使用默认值 `openai` 即可 | 1. 设置环境变量 Mac/Linux(当前终端窗口有效) ```bash export COPILOT_PROVIDER_BASE_URL="https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" export COPILOT_PROVIDER_API_KEY="eyJhbGciOiJSUzI1N..." export COPILOT_MODEL="hy3" ``` Windows CMD(当前终端窗口有效) ```bash set COPILOT_PROVIDER_BASE_URL=https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase set COPILOT_PROVIDER_API_KEY=eyJhbGciOiJSUzI1N... set COPILOT_MODEL=hy3 ``` Windows PowerShell(当前终端窗口有效) ```bash $env:COPILOT_PROVIDER_BASE_URL = "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" $env:COPILOT_PROVIDER_API_KEY = "eyJhbGciOiJSUzI1N..." $env:COPILOT_MODEL = "hy3" ``` 2. 启动 Copilot CLI ```bash copilot ``` :::note 以上环境变量为临时生效,关闭终端后失效。如需永久生效,可将环境变量写入 Shell 配置文件(如 `~/.zshrc` 或 `~/.bashrc`)中。 ::: --- # AI/文本生成/接入 AI 工具/Hermes Agent > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/hermes-agent [Hermes Agent](https://github.com/hermes-ai/hermes) 是一款开源的终端 AI 编程助手,支持通过自定义模型接入进行智能对话与代码辅助。 ## 配置步骤 [CC Switch](https://github.com/farion1231/cc-switch) 是一款开源的桌面应用,统一管理多个 AI 编程工具的供应商配置。安装方式参考 [CC Switch 配置文档](./cc-switch)。 1. 打开 CC Switch,添加云开发供应商 | 字段 | 说明 | |------|------| | **API Key** | 在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai) 中获取 | | **Base URL** | `https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase` | | **API 格式** | Chat Completions | 2. 切换启用云开发供应商即可使用 通过命令行交互菜单,系统会引导你一步步输入 API Key 和模型名称,推荐首次配置使用。 1. 在终端运行以下命令,启动配置向导 ```bash hermes model ``` ![hermes.png](https://qcloudimg.tencent-cloud.cn/raw/930de5e230cb85da4ff1009a606ebc18.png) 2. 选择 **Custom endpoint (enter URL manually)**,按照表格填写如下交互表单 | 字段 | 说明 | |------|------| | API Base URL | 大模型 API 的请求地址,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取,格式为 `https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase` | | API Key | 调用大模型的密钥,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | API compatibility mode | 选择 "1" Auto-detect | | Model Name | 大模型标识,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表中复制已启用的模型 ID 填入,如 `hy3` | ![hermes2.png](https://qcloudimg.tencent-cloud.cn/raw/1b1b51bf8b0cf8b303d83f8039829477.png) 3. 配置完成后即可开始使用 Hermes Agent 的核心设置保存在 `~/.hermes/config.yaml` 文件中,你可以直接修改该文件来接入自定义模型。 1. 打开配置文件 :::note 如果通过您文件浏览器找到文件,文件浏览器默认会隐藏掉以"."开头的文件名的文件,您可能需要打开文件浏览器的隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开,Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目" 打开。 ::: - Mac/Linux 配置路径 ```path ~/.hermes/config.yaml ``` - Windows 配置路径 ```path C:\Users\<你的用户名>\.hermes\config.yaml ``` 2. 填入配置 在 `config.yaml` 中填入自定义模型配置,各字段含义如下: | 字段 | 说明 | |------|------| | model.default | 默认使用的模型名称,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表中复制已启用的模型 ID 填入 | | model.provider | 提供商类型,填写 `custom` 表示自定义接入 | | model.base_url | 大模型 API 的请求地址(Base URL),在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | model.api_key | 调用大模型的密钥(API Key),在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | 配置示例: ```yaml model: default: hy3 provider: custom base_url: https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase api_key: eyJhbGciOiJSUzI1N... ``` 3. 配置完成后即可开始使用 ## 常见错误排查 | 报错信息 | 解决方法 | |----------------------------------|----------------------------------------------------------------------------------------------------| | `404` AI_MODEL_NOT_FOUND | Base URL 填写不正确,请前往 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中复制正确的 Base URL | | `401` 认证失败 | API Key 填写错误,或者可能被删除 | | `429` Token usage exceeded quota | 配置了错误的大模型名称,或者大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | --- # AI/文本生成/接入 AI 工具/Cline > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/cline [Cline](https://cline.bot/) 是一款内置于编辑器和终端的 AI 编码助手。它可以读写文件、运行终端命令、使用浏览器,并通过自然对话帮助您构建功能。 ## 配置步骤 Cline 既提供了 IDE 插件版本,又提供了终端 CLI 版本。 以 VS Code Cline 插件为例 1. 打开 Cline 设置,填写大模型配置信息 填入各字段含义如下 | 填入字段 | 解释 | |---------------------------|---------------------------------------------------------------------------------------------| | API Provider | 模型协议,CloudBase AI 模型支持多种协议,在这里选择最通用的 **OpenAI Compatible** | | Base URL | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | OpenAI Compatible API Key | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | Model ID | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | ![cline1.png](https://qcloudimg.tencent-cloud.cn/raw/2f93ad6ccd4d0ac5593e7522a766099c.png) 2. 点击 **Done**,完成设置,确认模型选择为刚刚创建的自定义模型,即可发起会话 ![cline2.png](https://qcloudimg.tencent-cloud.cn/raw/0b33df9ea53c9e7063791f0d6dff1a4e.png) #### CLI 配置大模型 1. 打开终端,运行命令 ```bash cline auth ``` 2. 选择 **Bring your own provider** ![cline3.png](https://qcloudimg.tencent-cloud.cn/raw/33dc3167336c405bba7b570b396f45ce.png) 3. 选择 **OpenAI Compatible** 4. 填写表单配置 请填入以下大模型配置(通过 `Tab` 按键可切换表单) | 填入字段 | 解释 | |----------|-------------------------------------------------------------------------------------| | Base URL | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | API Key | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | ![cline5.png](https://qcloudimg.tencent-cloud.cn/raw/5064da9e7fbdeac954230cc8d4270077.png) 5. 填写模型 ID 大模型 ID,请在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表获取,填写完回车设置完成即可在终端输入 `cline` 对话 ![cline6.png](https://qcloudimg.tencent-cloud.cn/raw/81dd28ab3c06db34f1f361e45043b342.png) --- # AI/文本生成/接入 AI 工具/RooCode > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/roocode [Roo Code](https://roomote.dev/)(原名 Roo Cline)是一款开源的 VS Code 智能 AI 编程助手扩展插件。 ## 配置步骤 1. 打开 RooCode 设置,进入 **API Provider** ![roocode2.png](https://qcloudimg.tencent-cloud.cn/raw/99bf0566c1f7dbe1937b86abe7390995.png) 2. 填写自定义大模型配置 填入各字段含义如下 | 填入字段 | 解释 | |--------------|---------------------------------------------------------------------------------------------| | API Provider | 模型协议,CloudBase AI 模型支持多种协议,在这里选择最通用的 **OpenAI Compatible** | | Base URL | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | API Key | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | Model | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | ![roocode2.png](https://qcloudimg.tencent-cloud.cn/raw/99bf0566c1f7dbe1937b86abe7390995.png) 3. 保存之后,在对话模型列表中选择创建的自定义模型即可开始对话 ![roocode.png](https://qcloudimg.tencent-cloud.cn/raw/f5ee031e6e0cb9835e5a32c26b7c7fde.png) --- # AI/文本生成/接入 AI 工具/Continue > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/continue [Continue](https://continue.dev/) 是一款开源的 AI 编程助手插件,支持 VS Code 和 JetBrains,可灵活接入任意 OpenAI 兼容的大模型。 ## 配置步骤 1. 打开配置文件 `config.yaml` 你可以通过以下两种方式打开: **在 IDE 中打开(推荐)**:在 Continue 聊天侧边栏顶部,点击模型/助手(Agent)下拉列表,找到 **Local Config**,点击它右侧的**齿轮图标**,即可直接在编辑器中打开 `config.yaml`。 ![continue.png](https://qcloudimg.tencent-cloud.cn/raw/6a321c21b855d8f3e46ef2dd9ed7aa21.png) **在本地文件系统中查找**: :::note 如果通过文件浏览器找到文件,文件浏览器默认会隐藏以"."开头的文件,您可能需要打开隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开,Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目" 打开。 ::: - Mac/Linux 配置路径 ```path ~/.continue/config.yaml ``` - Windows 配置路径 ```path %USERPROFILE%\.continue\config.yaml ``` 2. 在配置文件的 `models` 字段中添加模型配置 | 字段 | 解释 | |----------|---------------------------------------------------------------------------------------------| | name | 大模型展示名称,可以任意填写 | | provider | 固定填写 `openai`,表示使用 OpenAI 兼容协议 | | model | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | apiBase | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | apiKey | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | roles | 模型用途,可选 `chat`(对话)、`edit`(编辑) | ```yaml name: Local Config version: 1.0.0 schema: v1 models: - name: "CloudBase hy3" provider: "openai" model: "hy3" apiBase: "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" apiKey: "eyJhbGciOiJSUzI1N..." roles: - chat - edit ``` 3. 保存文件后,Continue 会自动刷新配置,在对话面板的模型列表中即可看到新增的模型 --- # AI/文本生成/接入 AI 工具/Aider > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/aider [Aider](https://aider.chat/) 是一款运行在终端的开源 AI 编程助手,支持多文件编辑、Git 集成,能够通过自然语言对话直接修改本地代码库。 ## 配置步骤 Aider 支持通过环境变量或命令行参数配置自定义模型。CloudBase AI 兼容 OpenAI 协议,使用 `openai` 前缀即可接入。 配置字段对照表 | 环境变量 | 解释 | |-----------------|-------------------------------------------------------------------------------------| | OPENAI_API_BASE | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | OPENAI_API_KEY | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | 1. 设置环境变量 Mac/Linux(当前终端窗口有效) ```bash export OPENAI_API_BASE="https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" export OPENAI_API_KEY="eyJhbGciOiJSUzI1N..." ``` Windows CMD(当前终端窗口有效) ```bash set OPENAI_API_BASE=https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase set OPENAI_API_KEY=eyJhbGciOiJSUzI1N... ``` Windows PowerShell(当前终端窗口有效) ```bash $env:OPENAI_API_BASE = "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" $env:OPENAI_API_KEY = "eyJhbGciOiJSUzI1N..." ``` 2. 启动 Aider,通过 `--model` 参数指定模型 ID ```bash aider --model openai/hy3 ``` :::note 模型 ID 需要加上 `openai/` 前缀,表示通过 OpenAI 兼容协议调用。大模型 ID 在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表中获取。 ::: 如果不想设置环境变量,也可以直接通过命令行参数启动: ```bash aider --model openai/hy3 \ --openai-api-base "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" \ --openai-api-key "eyJhbGciOiJSUzI1N..." ``` 1. 在项目根目录或用户主目录创建 `.aider.conf.yml` 文件 - 项目级配置(仅当前项目生效) ```path <项目根目录>/.aider.conf.yml ``` - 用户级配置(全局生效) ```path ~/.aider.conf.yml ``` 2. 写入以下配置 ```yaml openai-api-base: https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase openai-api-key: eyJhbGciOiJSUzI1N... model: openai/hy3 ``` 3. 之后在终端直接运行 `aider` 即可使用配置的模型 ```bash aider ``` --- # AI/文本生成/接入 AI 工具/Kilo Code > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/kilo-code [Kilo Code](https://kilocode.ai/) 是一款开源的 AI 编程助手,支持 VS Code 插件和 CLI 两种使用方式。 ## 配置步骤 Kilo Code 既提供了 IDE 插件,也提供了 CLI 工具。如果您安装了 Kilo CLI,可以通过模型配置文件来增加或修改 `provider`。 示例以 VS Code 为例 1. 打开 VS Code,点击侧边栏 Kilo Code 图标,点击面板顶部设置图标,进入 **Model** 设置,选择 **Custom provider**,点击 **Connect** ![kilocode.png](https://qcloudimg.tencent-cloud.cn/raw/5513f214856df01eb85dd7e650e7cf44.png) 2. 填写自定义大模型配置 填入各字段含义如下 | 填入字段 | 解释 | |--------------|---------------------------------------------------------------------------------------------| | Provider ID | 厂商 ID,可以任意填写,截图示例填写了 `cloudbase` 以示区分 | | Display name | 展示名称,可以任意填写,截图示例填写了 `cloudbase` 以示区分 | | BaseURL | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | API key | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | models.ID | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | models.Name | 大模型展示名称,可以任意填写 | ![kilocode2.png](https://qcloudimg.tencent-cloud.cn/raw/69e82a688c885186a4a12d0f0d2dfadd.png) 3. 点击**保存**,在对话模型列表中选择添加的自定义模型即可开始对话 ![kilocode3.png](https://qcloudimg.tencent-cloud.cn/raw/37042c13e1732dbade8b3513efed62f0.png) 1. 打开配置文件 :::note 如果通过您文件浏览器找到文件,文件浏览器默认会隐藏掉以"."开头的文件名的文件,您可能需要打开文件浏览器的隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开,Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目" 打开。 ::: - Windows 配置路径 ```path C:\Users\<你的用户名>\.config\kilo\kilo.jsonc ``` - Mac/Linux 配置路径 ```path ~/.config/kilo/kilo.jsonc ``` 2. 编辑或新增 `provider` 字段 | 字段 | 解释 | |--------------------------------------------|---------------------------------------------------------------------------------------------| | provider 对象的子字段名 | 厂商 ID,可以任意填写,示例填写了 `cloudbase` 以示区分 | | provider.cloudbase.name | 展示名称,可以任意填写,示例填写了 `cloudbase` 以示区分 | | provider.cloudbase.options.baseURL | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | provider.cloudbase.options.apiKey | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | provider.cloudbase.models 对象的子字段名 | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | provider.cloudbase.models.hy3.name | 大模型展示名称 | ```json { "provider": { "cloudbase": { "name": "cloudbase", "npm": "@ai-sdk/openai-compatible", "options": { "baseURL": "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase", "apiKey": "eyJhbGciOiJSUzI1..." }, "models": { "hy3": { "name": "hy3" } } } } } ``` --- # AI/文本生成/接入 AI 工具/OpenClaw > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/openclaw [OpenClaw](https://openclaw.ai/) 是一款备受瞩目的开源、自主式人工智能(AI)虚拟助理平台,因其默认的应用程序图标是一只红色小龙虾,被技术社区广泛昵称为"小龙虾"。 ## 配置步骤 我们以 OpenClaw-**2026.6.5 (5181e4f)** 为例 [CC Switch](https://github.com/farion1231/cc-switch) 是一款开源的桌面应用,统一管理多个 AI 编程工具的供应商配置。安装方式参考 [CC Switch 配置文档](./cc-switch)。 1. 打开 CC Switch,添加云开发供应商 | 字段 | 说明 | |------|------| | **API Key** | 在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai) 中获取 | | **Base URL** | `https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase` | | **API 格式** | Chat Completions | 2. 切换启用云开发供应商即可使用 1. 打开 DashBoard 面板,如果你的 OpenClaw 运行在本地电脑上,可以直接在终端运行以下快捷命令,系统会自动复制链接并尝试在浏览器中打开 ```bash openclaw dashboard ``` 2. 点击**设置** - **AI 与代理** - **Models** - **Model Provides** 3. 展开 **Model Provides** 面板,点击 **Add Entry** ![openclaw2.png](https://qcloudimg.tencent-cloud.cn/raw/3ea41ced1397261d874d866534e8be54.png) 4. **Custom entries** 填入如下信息 本示例以最小范围填入信息,您可以根据自己需求自定义填写字段,填入各字段含义如下 | 填入字段 | 解释 | |-------------------------|-------------------------------------------------------------------------------------| | Provider Name | 厂商名称,可以任意填写,截图示例里填写了 **cloudbase** | | Model Provider API Key | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | Model Provider Base URL | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | ![openclaw3.png](https://qcloudimg.tencent-cloud.cn/raw/0c4a5f18fbb492a3bbd1492b16448f6e.png) 5. 展开 **Custom entries** 的子面板 **Model Provider Model List** 面板,点击 **Add** 添加模型 填入各字段含义如下(其它字段无需填写,以 **Custom entries** 配置的信息为准) | 填入字段 | 解释 | |------|---------------------------------------------------------------------------------------------| | Id | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | Name | 大模型名称,可以任意填写,示例里将大模型 Id 和大模型名称保持一致,以免混淆 | ![openclaw4.png](https://qcloudimg.tencent-cloud.cn/raw/671baa76fd68acd8d38f26bd2c03207c.png) 6. 点击 **Save** 返回聊天即可测试 1. 打开配置文件 :::note 如果通过您文件浏览器找到文件,文件浏览器默认会隐藏掉以"."开头的文件名的文件,您可能需要打开文件浏览器的隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开,Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目" 打开。 ::: - Windows 配置路径 ```path C:\Users\<你的用户名>\.openclaw\openclaw.json ``` - Mac/Linux 配置路径 ```path ~/.openclaw/openclaw.json ``` 2. 填入配置 本示例以最小范围填入大模型配置,您可以根据自己需求自定义填写字段。本示例新增了一个自定义大模型提供商 `cloudbase`, `models.providers.cloudbase` 填入各字段含义如下 | 填入字段 | 解释 | |-------------|---------------------------------------------------------------------------------------------| | baseUrl | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | apiKey | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | models.name | 厂商名称,可以任意填写,截图示例里填写了 **cloudbase** | | models.id | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | ```json { "models": { "providers": { "cloudbase": { "baseUrl": "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase", "apiKey": "eyJhbGciOiJSUzI1N...", "models": [ { "id": "hy3", "name": "hy3" } ] } } } } ``` ## 常见错误排查 | 报错信息 | 解决方法 | |----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | All models are temporarily rate-limited. Please try again in a few minutes | Base URL 填写不正确,请前往 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中复制正确的 Base URL。配置了错误的大模型 ID ,大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | | `401` gent failed before reply: 401 status code | API Key 填写错误,或者可能被删除 | --- # AI/文本生成/接入 AI 工具/QClaw > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/qclaw [QClaw](https://qclaw.qq.com/) 是腾讯电脑管家基于 OpenClaw 开源生态打造的本地化 AI Agent 助手。 ## 配置步骤 我们以 QClaw-**v0.2.25** 为例 1. 打开 QClaw 面板,在对话框中单击模型选择按钮,点击**自定义大模型** ![qclaw.png](https://qcloudimg.tencent-cloud.cn/raw/558f66b181c019ceadfbeaafd3ef6424.png) 2. 点击**自定义配置** ![qclaw1.png](https://qcloudimg.tencent-cloud.cn/raw/583245086fc6e283b7bf0105b9bfe5c6.png) 3. 在弹出的**添加自定义大模型**对话框中,填入如下表单信息: | 字段 | 说明 | 示例值 | |----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------| | 接口地址 | 大模型 API 的请求地址(Base URL),在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取,格式为 `https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase` | `https://your-env-id.api.tcloudbasegateway.com/v1/ai/cloudbase` | | API 协议类型 | 接口协议类型,选择 **OpenAI兼容协议** 即可 | OpenAI兼容协议 | | API Key | 调用大模型的密钥,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | `eyJhbGciOiJSUzI1N...` | | 模型名称 | 要使用的大模型标识,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表中复制已启用的模型 ID 填入 | `hy3` | ![qclaw2.png](https://qcloudimg.tencent-cloud.cn/raw/cdb20002644ec1b93e6dcab4d78f2b2a.png) 4. 填写完成后,点击**确定**保存配置即可开始使用 ![qclaw5.png](https://qcloudimg.tencent-cloud.cn/raw/a8397c5faae3a496bd2d97462c647085.png) 1. 打开配置文件 :::note 如果通过您文件浏览器找到文件,文件浏览器默认会隐藏掉以"."开头的文件名的文件,您可能需要打开文件浏览器的隐藏文件选项才能看到。 Mac 通过访达快捷键 `Command + Shift + .` 打开,Windows 可以通过勾选 "文件资源管理器 - 查看 - 显示 - 隐藏项目" 打开。 ::: - Windows 配置路径 ```path C:\Users\<你的用户名>\.qclaw\openclaw.json ``` - Mac/Linux 配置路径 ```path ~/.qclaw/openclaw.json ``` 2. 填入配置 打开 `openclaw.json` 文件,在 `models.providers` 字段下新增一个自定义大模型提供商(如 `cloudbase`),各字段含义如下: | 字段 | 说明 | |-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | baseUrl | 大模型 API 的请求地址(Base URL),在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取,格式为 `https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase` | | apiKey | 调用大模型的密钥(API Key),在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | api | API 协议类型,填写 `openai-completions` 表示使用 OpenAI 兼容协议 | | models.id | 大模型标识,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表中复制已启用的模型 ID 填入 | | models.name | 模型显示名称,可自定义填写,一般与 `models.id` 保持一致即可 | 配置示例: ```json { "models": { "providers": { "cloudbase": { "baseUrl": "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase", "apiKey": "eyJhbGciOiJSUzI1N...", "api": "openai-completions", "models": [ { "id": "hy3", "name": "hy3" } ] } } } } ``` 3. 重启 QClaw,在模型对话框选择新增的自定义模型 ![qclaw4.png](https://qcloudimg.tencent-cloud.cn/raw/55054dfc5898063b83cd0c8de10aed10.png) ## 常见错误排查 | 报错信息 | 解决方法 | |----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | All models are temporarily rate-limited. Please try again in a few minutes | Base URL 填写不正确,请前往 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中复制正确的 Base URL。配置了错误的大模型 ID ,大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | | `401` gent failed before reply: 401 status code | API Key 填写错误,或者可能被删除 | --- # AI/文本生成/接入 AI 工具/OpenHands > 当前文档链接: https://docs.cloudbase.net/ai/ai-tools/openhands [OpenHands](https://www.openhands.ai/)(原 OpenDevin)是一款开源的 AI 驱动软件开发平台,支持通过 Docker 一键部署,提供 Web UI 和 CLI 两种交互方式,可自主完成代码编写、调试、终端操作等开发任务。 ## 配置步骤 OpenHands 支持通过 Docker 环境变量或 `config.toml` 配置文件接入自定义模型。 配置字段对照表 | 字段 | 解释 | |----------|---------------------------------------------------------------------------------------------| | model | 大模型 ID,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 模型列表复制已启用的大模型标识填入 | | base_url | 大模型 Base URL,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | | api_key | 调用大模型 API Key,在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中获取 | 通过 `-e` 参数传递环境变量: ```bash docker run -it --rm --pull=always \ -e LLM_MODEL="hy3" \ -e LLM_API_KEY="eyJhbGciOiJSUzI1N..." \ -e LLM_BASE_URL="https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" \ -p 3000:3000 \ --add-host host.docker.internal:host-gateway \ -v /var/run/docker.sock:/var/run/docker.sock \ ghcr.io/openhandsai/openhands:main ``` 启动后访问 `http://localhost:3000` 即可使用。 :::note 此方式仅在开发模式(通过 `main.py` 或 `cli.py` 启动)下可用。Docker 方式请使用"Docker 启动时配置"的环境变量。 ::: 1. 打开或创建 `config.toml` 配置文件 ```path ~/.openhands/config.toml ``` 2. 在 `[llm]` 字段中填入大模型配置 ```toml [llm] model = "hy3" api_key = "eyJhbGciOiJSUzI1N..." base_url = "https://{{cloudBase环境ID}}.api.tcloudbasegateway.com/v1/ai/cloudbase" ``` 3. 启动 OpenHands 即可使用配置的自定义模型 ## 常见错误排查 | 报错信息 | 解决方法 | |----------------------------------|----------------------------------------------------------------------------------------------------| | `404` 模型未找到 | Base URL 填写不正确,请前往 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai)中复制正确的 Base URL | | `401` 认证失败 | API Key 填写错误,或者可能被删除 | | `429` Token usage exceeded quota | 配置了错误的大模型名称,或者大模型未在 [AI 控制台](https://tcb.cloud.tencent.com/dev#/ai?tab=text-aiModel) 中启用 | --- # AI/图片生成/概述 > 当前文档链接: https://docs.cloudbase.net/ai/image-model/overview 云开发 AI 提供基于混元大模型的文生图和图生图能力,通过文本描述或参考图片即可生成高质量图片。 ## 支持的模型 {#models} | 模型名称 | 说明 | |----------|------| | `HY-Image-3.0-Plus-4090-Tob-v1.0` | 混元文生图 v3.0,支持自定义尺寸、prompt 改写、thinking 模式 | | `HY-Image-v3.0-I2I-ToB-v1.0.1` | 混元图生图 v3.0,支持传入垫图作为参考进行图像生成 | :::warning 旧模型下线通知 `hunyuan-image` 旧模型将于 **2026 年 7 月 15 日 0 点下线**,请尽快切换到 `HY-Image-3.0-Plus-4090-Tob-v1.0`,详见[升级指南](../ai-inspire-plan-image-upgrade)。 ::: ## 支持的尺寸 {#sizes} | 尺寸 | 比例 | 说明 | |------|------|------| | `1024x1024` | 1:1 | 正方形(默认) | | `1280x720` | 16:9 | 横向宽屏 | | `720x1280` | 9:16 | 纵向竖屏 | | `1280x1280` | 1:1 | 正方形(大尺寸) | ## 参数说明 {#parameters} | 参数 | 类型 | 必选 | 说明 | |------|------|------|------| | `model` | string | 是 | 模型名称,使用 `HY-Image-3.0-Plus-4090-Tob-v1.0` | | `prompt` | string | 是 | 图片描述文本,最多 500 字 | | `size` | string | 否 | 图片尺寸,默认 `1024x1024`,可选值见上表 | | `revise` | `{ value: boolean }` | 否 | 是否开启 prompt 改写,开启后模型会自动优化提示词以提升生图效果 | | `enable_thinking` | `{ value: boolean }` | 否 | 是否开启 thinking 模式,开启后生图质量更高但耗时更长(约增加 60 秒) | ### 图生图参数 {#i2i-parameters} `HY-Image-v3.0-I2I-ToB-v1.0.1` 模型在文生图参数基础上,额外支持以下垫图参数: | 参数 | 类型 | 必选 | 说明 | |------|------|------|------| | `image_urls` | `list` | 否 | 垫图 url 列表,与 image 二选一,优先选 image,大小不超过 10MB,支持 jpg、jpeg、png 格式,最多 1 张图,`image_urls` 和 `images` 必有一个非空 | | `images` | `list` | 否 | 垫图 base64 列表,与 image_url 二选一,优先选 image,大小不超过 10MB,支持 jpg、jpeg、png 格式,最多 1 张图 | ## 调用方式 {#access} 图片生成支持以下调用方式: - [wx-server-sdk 调用](./wx-server-sdk) — 适合微信小程序云函数场景 - [Node SDK 调用](./node-sdk) — 适合云函数、云托管等 Node.js 服务端场景 ## 使用限制 {#limits} - 图片生成**仅支持服务端调用**(云函数/云托管),小程序端和 Web 端需通过云函数中转 - 生成的图片 URL **24 小时后失效**,如需永久保存请上传至云存储 - prompt 最长 500 字 - 建议将云函数超时时间设置为 **900 秒**(开启 revise 约增加 10 秒,开启 thinking 最长增加 60 秒) ## 计费说明 {#pricing} - **小程序成长计划**用户:使用计划赠送的生图资源,无额外费用 - **资源点套餐**用户:使用套餐内资源点抵扣 ## 常见问题 {#faq} ### 如何提升生图质量? - 开启 `revise` 让模型优化你的 prompt - 开启 `enable_thinking` 让模型进行更深入的理解(耗时更长) - 编写更详细具体的 prompt,包含风格、色调、构图等描述 ### 为什么生成时间较长? 图片生成本身耗时较长,通常需要 10-30 秒。开启 `revise` 或 `enable_thinking` 会额外增加耗时。请确保云函数超时时间设置足够长。 ### 生成的图片 URL 过期了怎么办? 图片 URL 有效期为 24 小时。建议在云函数中生成图片后立即上传至云存储获取永久 fileID,具体方法参见各 SDK 调用文档中的"保存图片到云存储"章节。 --- # AI/图片生成/文生图 {#text-to-image} > 当前文档链接: https://docs.cloudbase.net/ai/image-model/text-to-image 混元文生图模型可根据文本描述生成高质量图片,支持自定义尺寸、prompt 改写和 thinking 模式。 ## 模型 {#model} 使用 `HY-Image-3.0-Plus-4090-Tob-v1.0` 模型进行文生图调用。 ## 参数说明 {#parameters} | 参数 | 类型 | 必选 | 说明 | |------|------|------|------| | `model` | string | 是 | 模型名称,使用 `HY-Image-3.0-Plus-4090-Tob-v1.0` | | `prompt` | string | 是 | 图片描述文本,最多 500 字 | | `size` | string | 否 | 图片尺寸,默认 `1024x1024` | | `revise` | `{ value: boolean }` | 否 | 是否开启 prompt 改写 | ## 支持的尺寸 {#sizes} | 尺寸 | 比例 | 说明 | |------|------|------| | `1024x1024` | 1:1 | 正方形(默认) | | `1280x720` | 16:9 | 横向宽屏 | | `720x1280` | 9:16 | 纵向竖屏 | | `1280x1280` | 1:1 | 正方形(大尺寸) | ## 使用示例 {#usage-example} ```js const imageModel = ai.createImageModel('hunyuan-image') const res = await imageModel.generateImage({ model: 'HY-Image-3.0-Plus-4090-Tob-v1.0', prompt: '一只胖胖的橘猫坐在窗台上打盹,水彩风格,温暖色调', size: '1024x1024', revise: { value: true }, }) console.log(res.data[0].url) console.log(res.data[0].revised_prompt) ``` ## 调用方式 {#access} - [wx-server-sdk 调用](./wx-server-sdk) — 微信小程序云函数 - [Node SDK 调用](./node-sdk) — 云函数 / 云托管 - [JS SDK 调用](./js-sdk-usage) — Node.js 服务端 --- # AI/图片生成/图生图 {#image-to-image} > 当前文档链接: https://docs.cloudbase.net/ai/image-model/image-to-image 混元图生图模型可在文生图的基础上,传入垫图(参考图片)作为生成依据,让生成结果更贴近预期的构图和风格。 ## 模型 {#model} 使用 `HY-Image-v3.0-I2I-ToB-v1.0.1` 模型进行图生图调用。 ## 参数说明 {#parameters} 图生图在[文生图](./text-to-image)参数基础上,额外支持以下垫图参数: | 参数 | 类型 | 必选 | 说明 | |------|------|------|------| | `image_urls` | `list` | 否 | 垫图 url 列表,与 `images` 二选一 | | `images` | `list` | 否 | 垫图 base64 列表,与 `image_urls` 二选一 | :::tip 垫图要求 - `image_urls` 与 `images`(base64)二选一,`images` 优先级更高 - 最多 1 张图,大小不超过 10MB - 支持 jpg、jpeg、png 格式 ::: ## 使用示例 {#usage-example} ```js const imageModel = ai.createImageModel('hunyuan-image') const res = await imageModel.generateImage({ model: 'HY-Image-v3.0-I2I-ToB-v1.0.1', prompt: '将这张照片转换为油画风格', image_urls: ['https://example.com/reference.jpg'], revise: { value: true }, }) console.log(res.data[0].url) ``` ## 调用方式 {#access} - [wx-server-sdk 调用](./wx-server-sdk) — 微信小程序云函数 - [Node SDK 调用](./node-sdk) — 云函数 / 云托管 - [JS SDK 调用](./js-sdk-usage) — Node.js 服务端 --- # AI/图片生成/自定义水印 {#custom-watermark} > 当前文档链接: https://docs.cloudbase.net/ai/image-model/custom-watermark 混元生图模型支持在生成图片时直接添加自定义水印,通过 SDK 的 `footnote` 参数指定水印文字内容,无需自行实现图像合成逻辑。 ## 参数说明 {#parameters} | 参数 | 类型 | 必选 | 说明 | |------|------|------|------| | `footnote` | string | 否 | 水印文字内容 | ## 使用示例 {#usage-example} 以 Node SDK 为例,在调用 `generateImage` 时传入 `footnote` 参数即可为生成的图片添加水印: ```js const cloudbase = require('@cloudbase/node-sdk') const app = cloudbase.init({ env: process.env.ENV_ID }) const ai = app.ai() const imageModel = ai.createImageModel('hunyuan-image') const res = await imageModel.generateImage({ model: 'HY-Image-3.0-Plus-4090-Tob-v1.0', prompt: '一只胖胖的橘猫坐在窗台上打盹,水彩风格,温暖色调', footnote: 'CloudBase AI', }) console.log(res.data[0].url) // 返回的图片已包含水印 ``` ## 调用方式 {#access} - [wx-server-sdk 调用](./wx-server-sdk) — 微信小程序云函数 - [Node SDK 调用](./node-sdk) — 云函数 / 云托管 - [JS SDK 调用](./js-sdk-usage) — Node.js 服务端 ## 注意事项 {#notes} - 水印是服务端在生图过程中同步添加的,不会额外增加生图时间 - 三种 SDK 的 `generateImage` 均支持 `footnote` 参数 --- # AI/图片生成/SDK 调用/JS SDK 调用 > 当前文档链接: https://docs.cloudbase.net/ai/image-model/js-sdk-usage 通过 `@cloudbase/js-sdk` 直接调用混元生图模型,无需通过云函数中转。 :::caution 注意 图片生成功能依赖 `@cloudbase/js-sdk` v3.17.0 或更高版本,且**仅限在服务端环境(Node.js)中使用**。Web 端浏览器环境不直接支持生图调用,如需在 Web 端生图,请使用 [Node SDK 创建云函数中](./node-sdk)提供服务端接口后,通过 Web 端调用云函数。 ::: ## 前置条件 {#prerequisites} - 已开通云开发环境 - `@cloudbase/js-sdk` **v3.17.0 或更高版本** - 生图资源:小程序成长计划赠送的生图资源 ## 安装 {#installation} ```bash npm install @cloudbase/js-sdk ``` 如需按需引入,还需注册 AI 模块: ```js import cloudbase from "@cloudbase/js-sdk/app"; import { registerAi } from "@cloudbase/js-sdk/ai"; registerAi(cloudbase); ``` ## 使用示例 {#usage-example} ### 基本生图 {#basic-image-generation} ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", }); const ai = app.ai(); const imageModel = ai.createImageModel("hunyuan-image"); imageModel.generateImageSubUrlConfig["hunyuan-image"]["HY-Image-3.0-Plus-4090-Tob-v1.0"] = "images/ar/generations"; const res = await imageModel.generateImage({ model: "HY-Image-3.0-Plus-4090-Tob-v1.0", prompt: "一只胖胖的橘猫坐在窗台上打盹,水彩风格,温暖色调", size: "1024x1024", revise: { value: true }, }); console.log(res.data[0].url); // 图片 URL,24 小时有效 console.log(res.data[0].revised_prompt); // 改写后的 prompt ``` ### 自定义参数 {#custom-parameters} 支持自定义图片尺寸、prompt 改写和 thinking 模式等参数: ```js const app = cloudbase.init({ env: "your-env-id" }); const ai = app.ai(); const imageModel = ai.createImageModel("hunyuan-image"); imageModel.generateImageSubUrlConfig["hunyuan-image"]["HY-Image-3.0-Plus-4090-Tob-v1.0"] = "images/ar/generations"; const res = await imageModel.generateImage({ model: "HY-Image-3.0-Plus-4090-Tob-v1.0", prompt: "赛博朋克风格的未来城市夜景,霓虹灯光,雨夜", size: "768x1024", // 自定义尺寸,竖屏 seed: 42, // 固定种子,相同种子可复现相同图片 footnote: "Generated by Cloudbase", // 自定义水印 revise: { value: true }, // 开启 prompt 改写 }); ``` ## 参数说明 {#parameter-reference} ### ImageModel.generateImage #### generateImageSubUrlConfig 在调用 `generateImage` 前,需要配置子路径映射: ```js imageModel.generateImageSubUrlConfig["hunyuan-image"]["HY-Image-3.0-Plus-4090-Tob-v1.0"] = "images/ar/generations"; ``` #### 输入参数 ```ts interface HunyuanARGenerateImageInput { model: "HY-Image-3.0-Plus-4090-Tob-v1.0"; prompt: string; // 生成图片使用的文本,不超过 8192 字符 size?: string; // 图片尺寸,格式 "${宽}x${高}",默认 "1024x1024" seed?: number; // 生成种子,范围 [1, 4294967295] footnote?: string; // 自定义水印,限制 16 字符 revise?: { value: boolean }; // 是否对 prompt 改写,默认开启 } ``` #### 返回值 ```ts interface HunyuanARGenerateImageOutput { id: string; created: number; data: Array<{ url: string; // 图片 URL,24 小时有效 revised_prompt?: string; // 改写后的 prompt }>; } ``` ## 完整示例 {#full-example} ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: process.env.ENV_ID, }); const ai = app.ai(); async function generateImage(prompt, options = {}) { const imageModel = ai.createImageModel("hunyuan-image"); imageModel.generateImageSubUrlConfig["hunyuan-image"]["HY-Image-3.0-Plus-4090-Tob-v1.0"] = "images/ar/generations"; const res = await imageModel.generateImage({ model: "HY-Image-3.0-Plus-4090-Tob-v1.0", prompt, size: options.size || "1024x1024", seed: options.seed, footnote: options.footnote, revise: { value: options.revise !== false }, }); return { url: res.data[0].url, revisedPrompt: res.data[0].revised_prompt, }; } // 使用 const { url, revisedPrompt } = await generateImage( "一幅水墨山水画,远山近水,意境悠远", { size: "1024x768" } ); console.log("图片地址:", url); console.log("改写后的 prompt:", revisedPrompt); ``` ## 保存图片到云存储 {#save-to-storage} 生成的图片 URL **24 小时后失效**,建议将图片下载后保存至云存储: ```js import https from "https"; import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: process.env.ENV_ID }); const ai = app.ai(); async function generateAndSave(prompt) { // 1. 生成图片 const imageModel = ai.createImageModel("hunyuan-image"); imageModel.generateImageSubUrlConfig["hunyuan-image"]["HY-Image-3.0-Plus-4090-Tob-v1.0"] = "images/ar/generations"; const res = await imageModel.generateImage({ model: "HY-Image-3.0-Plus-4090-Tob-v1.0", prompt, revise: { value: true }, }); // 2. 下载图片 const imageBuffer = await downloadImage(res.data[0].url); // 3. 上传到云存储 const uploadRes = await app.uploadFile({ cloudPath: `ai-images/${Date.now()}.jpg`, fileContent: imageBuffer, }); return { fileID: uploadRes.fileID, revisedPrompt: res.data[0].revised_prompt, }; } function downloadImage(url) { return new Promise((resolve, reject) => { https.get(url, (response) => { const chunks = []; response.on("data", (chunk) => chunks.push(chunk)); response.on("end", () => resolve(Buffer.concat(chunks))); response.on("error", reject); }); }); } ``` :::tip 超时提示 图片生成耗时较长(开启 revise 改写约增加 30 秒,开启 thinking 模式最长增加 60 秒),在云函数中调用时建议将超时时间设置为 **900 秒**。 ::: ## 相关文档 {#related} - [混元文生图模型概述](./overview) — 模型参数与尺寸说明 - [Node SDK 调用](./node-sdk) — 在云函数中使用 Node SDK 生图 - [JS SDK AI 参考 → ImageModel](../../api-reference/webv3/ai#imagemodel) — ImageModel 完整 API 文档 - [JS SDK 初始化](../../api-reference/webv3/initialization) — 初始化与鉴权 - [混元文生图模型升级指南](../ai-inspire-plan-image-upgrade) --- # AI/图片生成/SDK 调用/wx-server-sdk 调用 > 当前文档链接: https://docs.cloudbase.net/ai/image-model/wx-server-sdk 在微信小程序云函数中通过内置的 `wx-server-sdk` 调用混元生图模型。`wx-server-sdk` 已预装在微信云函数环境中,无需额外安装。 :::tip 说明 图片生成功能仅支持在云函数中调用,小程序端不支持直接调用。 ::: ## 前置条件 {#prerequisites} - 已开通微信云开发环境 - wx-server-sdk **4.0.2 或更高版本** - 生图资源:小程序成长计划赠送的生图资源 :::tip 版本说明 使用 `HY-Image-3.0-Plus-4090-Tob-v1.0` 模型需要 wx-server-sdk **4.0.2** 或更高版本(其子依赖 @cloudbase/ai >= 2.30.0)。可在云函数的 `package.json` 中指定版本: ```json { "dependencies": { "wx-server-sdk": "4.0.2" } } ``` ::: ## 创建生图云函数 {#create-cloud-function} 新建云函数,在 `index.js` 中填入以下代码: ```js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, timeout: 150000, // 单次 HTTP 请求超时 150s,覆盖默认的 ~15s }) const ALLOWED_SIZES = ['1024x1024', '1280x720', '720x1280', '1280x1280'] exports.main = async (event, context) => { const prompt = (event.prompt || '').trim() if (!prompt) { return { error: '请输入提示词' } } if (prompt.length > 500) { return { error: '提示词最多 500 字' } } const size = ALLOWED_SIZES.includes(event.size) ? event.size : '1024x1024' const imageModel = cloud.ai().createImageModel('hunyuan-image') const res = await imageModel.generateImage({ model: 'HY-Image-3.0-Plus-4090-Tob-v1.0', prompt, size, revise: { value: true }, }) return { url: res.data[0].url, // 图片 URL,24 小时有效 revisedPrompt: res.data[0].revised_prompt, // 优化后的提示词 } } ``` :::tip 超时时间 图片生成耗时较长(开启 revise 改写约增加 10 秒,开启 thinking 模式最长增加 60 秒),建议将云函数超时时间设置为 **900 秒**。 ::: ## 在小程序中调用 {#call-from-miniprogram} 云函数部署后,在小程序代码中调用: ```js const res = await wx.cloud.callFunction({ name: 'generateImage', data: { prompt: '一只胖胖的橘猫坐在窗台上打盹,水彩风格,温暖色调', size: '1024x1024', }, }) console.log(res.result.url) // 图片 URL console.log(res.result.revisedPrompt) // 改写后的 prompt ``` ## 保存图片到云存储 {#save-to-storage} 生成的图片 URL **24 小时后失效**。如需永久保存,可在云函数中直接下载并上传到云存储: ```js const https = require('https') const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, timeout: 150000, }) exports.main = async (event, context) => { const prompt = (event.prompt || '').trim() const imageModel = cloud.ai().createImageModel('hunyuan-image') const res = await imageModel.generateImage({ model: 'HY-Image-3.0-Plus-4090-Tob-v1.0', prompt, revise: { value: true }, }) // 下载图片并上传到云存储 const imageBuffer = await downloadImage(res.data[0].url) const uploadRes = await cloud.uploadFile({ cloudPath: `ai-images/${Date.now()}.jpg`, fileContent: imageBuffer, }) return { fileID: uploadRes.fileID, revisedPrompt: res.data[0].revised_prompt, } } function downloadImage(url) { return new Promise((resolve, reject) => { https.get(url, (response) => { const chunks = [] response.on('data', (chunk) => chunks.push(chunk)) response.on('end', () => resolve(Buffer.concat(chunks))) response.on('error', reject) }) }) } ``` ## 图生图调用(I2I) {#image-to-image} `HY-Image-v3.0-I2I-ToB-v1.0.1` 为图生图模型,可在文生图参数基础上传入垫图作为生成参考。垫图通过 `image_urls`(URL 列表)或 `images`(base64 列表)传入,二者必有一个非空,最多 1 张图,大小不超过 10MB,支持 jpg、jpeg、png 格式。 ```js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, timeout: 150000, }) exports.main = async (event, context) => { const prompt = (event.prompt || '').trim() const imageUrls = event.imageUrls || [] const imageModel = cloud.ai().createImageModel('hunyuan-image') const res = await imageModel.generateImage({ model: 'HY-Image-v3.0-I2I-ToB-v1.0.1', prompt, image_urls: imageUrls, // 或 images: [base64...] revise: { value: true }, }) return { url: res.data[0].url, revisedPrompt: res.data[0].revised_prompt, } } ``` ## 相关文档 {#related} - [混元文生图模型升级指南](../ai-inspire-plan-image-upgrade) - [Node SDK AI 参考 → ImageModel](../../api-reference/server/node-sdk/ai#imagemodel) --- # AI/图片生成/SDK 调用/Node SDK 调用 > 当前文档链接: https://docs.cloudbase.net/ai/image-model/node-sdk 通过 `@cloudbase/node-sdk` 在云函数或云托管中调用混元生图模型,支持自定义尺寸、prompt 改写和 thinking 模式。 ::::tip 在线示例 在 CodeSandbox 中打开完整示例代码 → :::: :::tip 说明 图片生成功能仅支持在服务端(云函数/云托管)中调用,小程序和 Web 端不支持直接调用。 ::: ## 前置条件 {#prerequisites} - 已开通云开发环境 - `@cloudbase/node-sdk` **3.18.3 或更高版本** - 生图资源:小程序成长计划赠送的生图资源 ## 安装 {#installation} ```bash npm install @cloudbase/node-sdk ``` :::tip 版本要求 `@cloudbase/node-sdk` 需 **>= 3.18.3**,并重新安装依赖,保证 `@cloudbase/ai` >= 2.30.0。该版本已内置混元生图模型 `HY-Image-3.0-Plus-4090-Tob-v1.0` 的默认配置。 ::: ## 创建生图云函数 {#create-cloud-function} 新建云函数,安装依赖后填入以下代码: ```js const cloudbase = require('@cloudbase/node-sdk') const app = cloudbase.init({ env: process.env.ENV_ID }) const ai = app.ai() exports.main = async (event) => { const { prompt, size = '1024x1024' } = event const imageModel = ai.createImageModel('hunyuan-image') const res = await imageModel.generateImage({ model: 'HY-Image-3.0-Plus-4090-Tob-v1.0', prompt, size, revise: { value: true }, }) return { url: res.data[0].url, // 图片 URL,24 小时有效 revisedPrompt: res.data[0].revised_prompt, } } ``` :::tip 超时时间 图片生成耗时较长(开启 revise 改写约增加 10 秒,开启 thinking 模式最长增加 60 秒),建议将云函数超时时间设置为 **900 秒**。 ::: ## 在 Web 页面中调用 {#call-from-web} Web 页面通过 `@cloudbase/js-sdk` 调用云函数: ```bash npm install @cloudbase/js-sdk ``` ```js import cloudbase from '@cloudbase/js-sdk' const app = cloudbase.init({ env: 'your-env-id' }) await app.auth().anonymousAuthProvider().signIn() const res = await app.callFunction({ name: 'generateImage', data: { prompt: '一只胖胖的橘猫坐在窗台上打盹,水彩风格,温暖色调', size: '1024x1024', }, }) console.log(res.result.url) // 图片 URL console.log(res.result.revisedPrompt) // 改写后的 prompt ``` ## 保存图片到云存储 {#save-to-storage} 生成的图片 URL **24 小时后失效**,建议在云函数中直接将图片上传至云存储: ```js const https = require('https') const cloudbase = require('@cloudbase/node-sdk') const app = cloudbase.init({ env: process.env.ENV_ID }) const ai = app.ai() exports.main = async (event) => { const { prompt } = event // 生成图片 const imageModel = ai.createImageModel('hunyuan-image') const res = await imageModel.generateImage({ model: 'HY-Image-3.0-Plus-4090-Tob-v1.0', prompt, revise: { value: true }, }) // 下载图片并上传到云存储 const imageBuffer = await downloadImage(res.data[0].url) const uploadRes = await app.uploadFile({ cloudPath: `ai-images/${Date.now()}.jpg`, fileContent: imageBuffer, }) return { fileID: uploadRes.fileID, revisedPrompt: res.data[0].revised_prompt, } } function downloadImage(url) { return new Promise((resolve, reject) => { https.get(url, (response) => { const chunks = [] response.on('data', (chunk) => chunks.push(chunk)) response.on('end', () => resolve(Buffer.concat(chunks))) response.on('error', reject) }) }) } ``` ## 图生图调用(I2I) {#image-to-image} `HY-Image-v3.0-I2I-ToB-v1.0.1` 为图生图模型,可在文生图参数基础上传入垫图作为生成参考。垫图通过 `image_urls`(URL 列表)或 `images`(base64 列表)传入,二者必有一个非空,最多 1 张图,大小不超过 10MB,支持 jpg、jpeg、png 格式。 ```js const cloudbase = require('@cloudbase/node-sdk') const app = cloudbase.init({ env: process.env.ENV_ID }) const ai = app.ai() exports.main = async (event) => { const { prompt, imageUrls } = event const imageModel = ai.createImageModel('hunyuan-image') const res = await imageModel.generateImage({ model: 'HY-Image-v3.0-I2I-ToB-v1.0.1', prompt, image_urls: imageUrls, // 或 images: [base64...] revise: { value: true }, }) return { url: res.data[0].url, revisedPrompt: res.data[0].revised_prompt, } } ``` ## 相关文档 {#related} - [混元文生图模型升级指南](../ai-inspire-plan-image-upgrade) - [Node SDK AI 参考 → ImageModel](../../api-reference/server/node-sdk/ai#imagemodel) --- # AI/Agent 开发/概述 > 当前文档链接: https://docs.cloudbase.net/ai/agent-development/index ## 什么是 Agent Agent(智能体)是一种能够自主执行任务、与用户交互并调用外部工具的 AI 应用。与传统的对话式 AI 不同,Agent 具备以下核心能力: - **自主决策**:根据用户输入和上下文自主规划执行步骤 - **工具调用**:调用外部 API、数据库、搜索引擎等工具完成任务 - **多轮交互**:支持复杂的多轮对话和状态管理 - **流式输出**:实时流式返回思考过程和执行结果 ## 技术选型 CloudBase 目前对以下 Agent 框架进行了适配: ### 框架选择 | 框架 | 语言支持 | 特点 | 适用场景 | |------|---------|------|---------| | **LangChain** | TypeScript/Python | 成熟稳定、生态丰富 | 通用 Agent 开发 | | **LangGraph** | TypeScript/Python | 图结构工作流、精细控制 | 复杂多步骤任务 | | **CrewAI** | Python | 多 Agent 协作 | 团队协作型任务 | ### 部署方式 | 方式 | 特点 | 开发语言 | 适用场景 | |------|------|---------|----| | **HTTP 云函数** | 快速部署、按量计费、自动扩缩容 | JavaScript/TypeScript/Python | 轻量级 Agent、低频调用 | | **云托管** | 长连接支持、自定义运行时 | 基于容器,支持任意编程语言 | 复杂 Agent、高并发场景 | ## 开发流程 ### 1. 选择开发框架 根据项目需求选择合适的 Agent 框架: - 需要快速开发通用 Agent → [LangChain](./frameworks/langchain.md) - 需要复杂工作流控制 → [LangGraph](./frameworks/langgraph.md) - 需要多 Agent 协作 → [CrewAI](./frameworks/crewai.md) ### 2. 开发 Agent 逻辑 使用云开发提供的 Agent 适配器,将框架与 AG-UI 协议对接: ```typescript // LangChain 示例 import { LangchainAgent } from "@cloudbase/agent-adapter-langchain"; import { createAgent as createLangchainAgent } from "langchain"; // 创建 LangChain Agent const lcAgent = createLangchainAgent({ model, checkpointer }); // 包装为 AG-UI 兼容的 Agent const agent = new LangchainAgent({ agent: lcAgent, }); ``` ### 3. 部署到云开发 选择合适的部署方式: - [HTTP 云函数部署](./deployment/cloud-function.md) - [云托管部署](./deployment/cloud-run.md) ### 4. 客户端集成 在各端调用 Agent: - [小程序调用](./integration/miniprogram.md) - [Web 调用](./integration/web.md) - [Node.js 调用](./integration/nodejs.md) - [cURL 调用](./integration/curl.md) ## AG-UI 协议 云开发 Agent 完全兼容 [AG-UI 协议](./protocol.md),这是一个标准化的前后端通信协议,支持: - **SSE 流式传输**:实时推送 Agent 执行过程 - **工具调用**:前端和服务端工具的统一调用规范 - **人机交互**:支持用户确认、输入等交互场景 - **状态同步**:Agent 状态与客户端实时同步 ## 快速开始 ### 前置条件 1. 已创建云开发环境 2. 已安装 Node.js 18+ 或 Python 3.9+ 3. 已配置大模型(见 [接入大模型](/ai/model/overview)) 4. 已创建 API Key([获取地址](https://tcb.cloud.tencent.com/dev#/env/apikey)) ### 5 分钟快速体验 ```bash # 1. 安装依赖 npm install @cloudbase/agent-adapter-langchain @cloudbase/agent-server langchain @langchain/openai @langchain/langgraph express # 2. 设置环境变量 export TCB_ENV_ID=your-env-id # 云开发环境 ID export TCB_API_KEY=your-api-key # 云开发 API Key export TCB_AI_MODEL=deepseek-v4-flash # 模型名称 # 3. 创建 Agent cat > index.js << 'EOF' const { LangchainAgent } = require("@cloudbase/agent-adapter-langchain"); const { createExpressRoutes } = require("@cloudbase/agent-server"); const { createAgent: createLangchainAgent } = require("langchain"); const { ChatOpenAI } = require("@langchain/openai"); const { MemorySaver } = require("@langchain/langgraph"); const express = require("express"); const checkpointer = new MemorySaver(); function createAgent() { // 使用云开发内置的大模型端点 const model = new ChatOpenAI({ model: process.env.TCB_AI_MODEL || "deepseek-v4-flash", apiKey: process.env.TCB_API_KEY, configuration: { baseURL: `https://${process.env.TCB_ENV_ID}.api.tcloudbasegateway.com/v1/ai/cloudbase`, }, }); const lcAgent = createLangchainAgent({ model, checkpointer }); return { agent: new LangchainAgent({ agent: lcAgent }), }; } const app = express(); createExpressRoutes({ createAgent, express: app }); app.listen(3000, () => console.log("Agent running on http://localhost:3000")); EOF # 4. 本地运行 node index.js ``` 访问 `http://localhost:3000` 即可体验 Agent。 :::tip 云开发内置了腾讯混元和 DeepSeek 大模型,无需额外申请外部 API Key。详见 [接入大模型](/ai/model/overview)。 ::: ## 下一步 - 了解 [AG-UI 协议规范](./protocol.md) - 选择框架开始开发:[LangChain](./frameworks/langchain.md) | [LangGraph](./frameworks/langgraph.md) | [CrewAI](./frameworks/crewai.md) - 部署 Agent:[云函数部署](./deployment/cloud-function.md) | [云托管部署](./deployment/cloud-run.md) --- # AI/Agent 开发/AG-UI 协议 > 当前文档链接: https://docs.cloudbase.net/ai/agent-development/protocol 云开发 Agent 完全兼容 [AG-UI 协议](https://docs.ag-ui.com/concepts/events),这是一个标准化的前后端通信协议,用于 Agent 与客户端之间的实时交互。 ## 协议概述 AG-UI 协议基于 SSE(Server-Sent Events)实现流式通信,支持: - 实时推送 Agent 执行过程 - 工具调用和结果返回 - 人机交互场景 - 状态同步 ## 请求格式 ### HTTP 请求 ```http POST /agent HTTP/1.1 Content-Type: application/json Accept: text/event-stream { "threadId": "thread-xxx", "runId": "run-xxx", "messages": [ { "id": "msg-1", "role": "user", "content": "你好" } ], "tools": [], "context": [], "forwardedProps": {} } ``` ### 请求参数说明 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `threadId` | string | 否 | 会话 ID,用于多轮对话 | | `runId` | string | 否 | 本次运行 ID | | `messages` | array | 是 | 消息列表 | | `tools` | array | 否 | 前端工具定义 | | `context` | array | 否 | 上下文信息 | | `forwardedProps` | object | 否 | 透传参数 | ### 消息格式 ```typescript interface Message { id: string; // 消息 ID role: "user" | "assistant" | "system" | "tool"; content: string; // 消息内容 name?: string; // 工具名称(role 为 tool 时) toolCallId?: string; // 工具调用 ID(role 为 tool 时) } ``` ## 响应格式 响应采用 SSE 格式,每个事件以 `data:` 开头: ``` data: {"type":"TEXT_MESSAGE_START","messageId":"msg-1"} data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg-1","delta":"你好"} data: {"type":"TEXT_MESSAGE_END","messageId":"msg-1"} data: {"type":"RUN_FINISHED"} ``` ## 事件类型 ### 生命周期事件 | 事件类型 | 说明 | |---------|------| | `RUN_STARTED` | Agent 开始运行 | | `RUN_FINISHED` | Agent 运行结束 | | `RUN_ERROR` | Agent 运行出错 | ### 消息事件 | 事件类型 | 说明 | |---------|------| | `TEXT_MESSAGE_START` | 文本消息开始 | | `TEXT_MESSAGE_CONTENT` | 文本消息内容(增量) | | `TEXT_MESSAGE_END` | 文本消息结束 | ### 工具调用事件 | 事件类型 | 说明 | |---------|------| | `TOOL_CALL_START` | 工具调用开始 | | `TOOL_CALL_ARGS` | 工具调用参数(增量) | | `TOOL_CALL_END` | 工具调用结束 | ### 状态事件 | 事件类型 | 说明 | |---------|------| | `STATE_SNAPSHOT` | 状态快照 | | `STATE_DELTA` | 状态增量更新 | | `MESSAGES_SNAPSHOT` | 消息快照 | ### 步骤事件 | 事件类型 | 说明 | |---------|------| | `STEP_STARTED` | 步骤开始 | | `STEP_FINISHED` | 步骤结束 | ## 工具调用 ### 服务端工具 服务端工具由 Agent 后端直接执行: ```mermaid sequenceDiagram participant Client as 客户端 participant Agent as Agent 服务 participant Tool as 工具服务 Client->>Agent: 发送消息 Agent->>Agent: TOOL_CALL_START Agent->>Tool: 调用工具 Tool-->>Agent: 返回结果 Agent->>Agent: TOOL_CALL_END Agent-->>Client: 返回结果 ``` ### 前端工具 前端工具需要客户端执行后返回结果: ```mermaid sequenceDiagram participant Client as 客户端 participant Agent as Agent 服务 Client->>Agent: 发送消息(包含 tools 定义) Agent-->>Client: TOOL_CALL_START Agent-->>Client: TOOL_CALL_ARGS Agent-->>Client: TOOL_CALL_END Note over Client: 客户端执行工具 Client->>Agent: 发送工具结果 Agent-->>Client: 继续处理 ``` 前端工具定义示例: ```javascript const tools = [ { name: "get_location", description: "获取用户当前位置", parameters: { type: "object", properties: {}, required: [] } } ]; ``` ## 人机交互 AG-UI 协议支持多种人机交互场景: ### 用户确认 Agent 可以请求用户确认后再执行操作: ```javascript // 服务端发送确认请求 { type: "TOOL_CALL_START", toolCallId: "call-1", toolCallName: "confirm_action" } // 客户端返回确认结果 { role: "tool", toolCallId: "call-1", content: JSON.stringify({ confirmed: true }) } ``` ### 用户输入 Agent 可以请求用户提供额外输入: ```javascript // 服务端请求输入 { type: "TOOL_CALL_START", toolCallId: "call-2", toolCallName: "request_input", args: { prompt: "请输入您的邮箱地址" } } // 客户端返回输入 { role: "tool", toolCallId: "call-2", content: JSON.stringify({ input: "user@example.com" }) } ``` ## 状态管理 ### 状态快照 Agent 可以发送完整状态快照: ```javascript { type: "STATE_SNAPSHOT", snapshot: { currentStep: "analyzing", progress: 50, results: [] } } ``` ### 状态增量 Agent 可以发送状态增量更新: ```javascript { type: "STATE_DELTA", delta: [ { op: "replace", path: "/progress", value: 75 } ] } ``` ## 错误处理 ### 错误事件 ```javascript { type: "RUN_ERROR", error: { code: "TOOL_EXECUTION_ERROR", message: "工具执行失败" } } ``` ### 常见错误码 | 错误码 | 说明 | |-------|------| | `INVALID_REQUEST` | 请求格式错误 | | `TOOL_NOT_FOUND` | 工具不存在 | | `TOOL_EXECUTION_ERROR` | 工具执行失败 | | `MODEL_ERROR` | 模型调用失败 | | `TIMEOUT` | 请求超时 | ## 最佳实践 ### 1. 消息 ID 管理 确保每条消息都有唯一的 ID,便于追踪和调试: ```javascript const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; ``` ### 2. 会话管理 使用 `threadId` 管理多轮对话: ```javascript // 新会话 const threadId = `thread-${Date.now()}`; // 后续请求使用相同的 threadId { threadId: threadId, messages: [...] } ``` ### 3. 超时处理 设置合理的超时时间: ```javascript const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 60000); fetch('/agent', { signal: controller.signal, // ... }); ``` ### 4. 重试机制 实现指数退避重试: ```javascript async function fetchWithRetry(url, options, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fetch(url, options); } catch (error) { if (i === maxRetries - 1) throw error; await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000)); } } } ``` ## 相关文档 - [LangChain 开发指南](./frameworks/langchain.md) - [LangGraph 开发指南](./frameworks/langgraph.md) - [CrewAI 开发指南](./frameworks/crewai.md) --- # AI/Agent 开发/前后端通信协议 > 当前文档链接: https://docs.cloudbase.net/ai/agent/http-agent-protocol HTTP Agent 完全兼容 [AG-UI 协议](https://github.com/ag-ui-protocol/ag-ui),使用 **SSE** 实现前后端实时流式通信。 可直接使用 AG-UI TypeScript SDK 的 `HttpAgent` 进行接入: ```typescript import { HttpAgent } from "@ag-ui/client"; const agent = new HttpAgent({ url: "https://your-endpoint/send-message", headers: { Authorization: "Bearer your-api-key", }, }); ``` ## `POST /send-message` ### 请求体 请求体遵循 AG-UI 协议的 `RunAgentInput` 结构: ```typescript interface RunAgentInput { threadId: string; // 会话线程 ID runId: string; // 本次运行的唯一 ID parentRunId?: string; // 可选,父运行 ID(用于分支/回溯场景) state?: any; // 可选,当前状态 messages: Message[]; // 消息历史 tools: Tool[]; // 客户端工具列表 context: Context[]; // 上下文信息 forwardedProps?: any; // 可选,透传属性 } ``` **说明:** - `threadId` 整个对话保持一致,服务端通过该 ID 维护会话状态 - `runId` 每次调用生成新的唯一 ID - `messages` 消息列表 #### 消息类型 ```typescript type Message = | DeveloperMessage | SystemMessage | AssistantMessage | UserMessage | ToolMessage; interface DeveloperMessage { id: string; role: "developer"; content: string; name?: string; } interface SystemMessage { id: string; role: "system"; content: string; name?: string; } interface AssistantMessage { id: string; role: "assistant"; content?: string; name?: string; toolCalls?: ToolCall[]; } interface UserMessage { id: string; role: "user"; content: string | InputContent[]; name?: string; } interface ToolMessage { id: string; role: "tool"; content: string; toolCallId: string; error?: string; } interface ToolCall { id: string; type: "function"; function: { name: string; arguments: string }; } // 多模态输入内容 type InputContent = | { type: "text"; text: string } | { type: "binary"; mimeType: string; id?: string; url?: string; data?: string; filename?: string }; ``` #### 工具定义 ```typescript interface Tool { name: string; description: string; parameters: any; // JSON Schema } ``` #### 上下文定义 ```typescript interface Context { description: string; value: string; } ``` **示例请求:** ```json { "threadId": "550e8400-e29b-41d4-a716-446655440000", "runId": "run_001", "messages": [ { "id": "msg_1", "role": "user", "content": "今天北京天气怎么样?" } ], "tools": [ { "name": "get_weather", "description": "获取指定城市的天气", "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] } } ], "context": [], "state": {} } ``` --- ### 响应事件 响应为 **SSE(Server-Sent Events)** 流,格式如下: - 每个事件:`data: {json}\n\n`(JSON 后跟两个换行符) AG-UI 协议定义了以下事件类型: | 事件类型 | 说明 | |----------|------| | `RUN_STARTED` | Agent 运行开始 | | `RUN_FINISHED` | Agent 运行结束 | | `RUN_ERROR` | Agent 运行错误 | | `TEXT_MESSAGE_START` | 文本消息开始 | | `TEXT_MESSAGE_CONTENT` | 文本内容增量 | | `TEXT_MESSAGE_END` | 文本消息结束 | | `TOOL_CALL_START` | 工具调用开始 | | `TOOL_CALL_ARGS` | 工具参数增量 | | `TOOL_CALL_END` | 工具调用结束 | | `TOOL_CALL_RESULT` | 工具执行结果 | | `STATE_SNAPSHOT` | 状态快照 | | `STATE_DELTA` | 状态增量更新(JSON Patch) | | `MESSAGES_SNAPSHOT` | 消息历史快照 | | `STEP_STARTED` | 步骤开始 | | `STEP_FINISHED` | 步骤结束 | **`TOOL_CALL_ARGS` 拼接规则:** 工具参数以流式增量传输,客户端需将同一 `toolCallId` 的多个 `TOOL_CALL_ARGS` 事件的 `delta` 字段**按顺序拼接**,最终得到完整的 JSON 字符串。 ``` // 事件流 data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_1","delta":"{\"cit"} data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_1","delta":"y\":\"北"} data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_1","delta":"京\"}"} // 拼接结果 "{\"city\":\"北京\"}" → JSON.parse() → { city: "北京" } ``` **注意:** 当存在多个并行工具调用时,不同 `toolCallId` 的事件可能**交错**出现,客户端应使用 `toolCallId` 区分和追踪每个工具调用。 #### 事件结构 ```typescript // 生命周期事件 interface RunStartedEvent { type: "RUN_STARTED"; threadId: string; runId: string; parentRunId?: string; timestamp?: number; } interface RunFinishedEvent { type: "RUN_FINISHED"; threadId: string; runId: string; result?: any; timestamp?: number; } interface RunErrorEvent { type: "RUN_ERROR"; message: string; code?: string; timestamp?: number; } // 文本消息事件 interface TextMessageStartEvent { type: "TEXT_MESSAGE_START"; messageId: string; role: "developer" | "system" | "assistant" | "user"; } interface TextMessageContentEvent { type: "TEXT_MESSAGE_CONTENT"; messageId: string; delta: string; // 非空文本增量 } interface TextMessageEndEvent { type: "TEXT_MESSAGE_END"; messageId: string; } // 工具调用事件 interface ToolCallStartEvent { type: "TOOL_CALL_START"; toolCallId: string; toolCallName: string; parentMessageId?: string; } interface ToolCallArgsEvent { type: "TOOL_CALL_ARGS"; toolCallId: string; delta: string; } interface ToolCallEndEvent { type: "TOOL_CALL_END"; toolCallId: string; } interface ToolCallResultEvent { type: "TOOL_CALL_RESULT"; messageId: string; toolCallId: string; content: string; } // 状态管理事件 interface StateSnapshotEvent { type: "STATE_SNAPSHOT"; snapshot: any; } interface StateDeltaEvent { type: "STATE_DELTA"; delta: JsonPatchOperation[]; // RFC 6902 JSON Patch } interface MessagesSnapshotEvent { type: "MESSAGES_SNAPSHOT"; messages: Message[]; } // 步骤事件 interface StepStartedEvent { type: "STEP_STARTED"; stepName: string; } interface StepFinishedEvent { type: "STEP_FINISHED"; stepName: string; } ``` #### 响应示例 ``` data: {"type":"RUN_STARTED","threadId":"thread_001","runId":"run_001"} data: {"type":"TEXT_MESSAGE_START","messageId":"msg_1","role":"assistant"} data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_1","delta":"让我查询一下"} data: {"type":"TEXT_MESSAGE_END","messageId":"msg_1"} data: {"type":"TOOL_CALL_START","toolCallId":"call_123","toolCallName":"get_weather","parentMessageId":"msg_1"} data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_123","delta":"{\"city\":\"北京\"}"} data: {"type":"TOOL_CALL_END","toolCallId":"call_123"} data: {"type":"TOOL_CALL_RESULT","messageId":"msg_2","toolCallId":"call_123","content":"晴天,25°C"} data: {"type":"TEXT_MESSAGE_START","messageId":"msg_3","role":"assistant"} data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_3","delta":"北京今天是晴天,温度25°C。"} data: {"type":"TEXT_MESSAGE_END","messageId":"msg_3"} data: {"type":"RUN_FINISHED","threadId":"thread_001","runId":"run_001"} ``` --- ## 前端工具 vs 服务端工具 Agent 可以同时使用**前端工具**和**服务端工具**: | 特征 | 前端工具 | 服务端工具 | |------|----------|-----------| | 定义位置 | 前端,通过 `tools` 参数传入 | 服务端 Agent 内部 | | 执行位置 | 前端(浏览器) | 服务端 | | 结果传递 | 前端通过 `ToolMessage` 发回 | 服务端返回 `TOOL_CALL_RESULT` 事件 | **前端判断逻辑:** 1. 收到 `TOOL_CALL_END` 后,检查工具名称是否在前端传入的 `tools` 列表中 2. 如果**在列表中**:这是前端工具,等待 `RUN_FINISHED`,然后前端执行工具,将结果作为 `ToolMessage` 发送 3. 如果**不在列表中**:这是服务端工具,等待 `TOOL_CALL_RESULT` 事件 --- ## 时序图 ### 基本文本响应 ```mermaid sequenceDiagram participant 前端 participant 后端 前端->>后端: POST /send-message
{threadId, runId, messages} 后端-->>前端: RUN_STARTED 后端-->>前端: TEXT_MESSAGE_START 后端-->>前端: TEXT_MESSAGE_CONTENT (增量) 后端-->>前端: TEXT_MESSAGE_END 后端-->>前端: RUN_FINISHED ``` ### 前端工具调用流程 前端通过 `tools` 参数传递工具定义。当 Agent 调用前端工具时,前端负责执行并将结果发回。 ```mermaid sequenceDiagram participant 前端 participant 后端 前端->>后端: POST /send-message
{threadId, runId, messages, tools} 后端-->>前端: RUN_STARTED 后端-->>前端: TOOL_CALL_START 后端-->>前端: TOOL_CALL_ARGS (增量) 后端-->>前端: TOOL_CALL_END 后端-->>前端: RUN_FINISHED Note over 前端: 检查工具名在 tools 列表中
→ 前端工具,执行并获得结果 前端->>后端: POST /send-message
{threadId, runId, messages: [
...历史消息,
{role:"assistant", toolCalls},
{role:"tool", toolCallId, content}
]} 后端-->>前端: RUN_STARTED 后端-->>前端: TEXT_MESSAGE_* (基于结果的响应) 后端-->>前端: RUN_FINISHED ``` ### 服务端工具调用流程 服务端工具由服务端定义和执行,前端无需处理工具执行逻辑。 ```mermaid sequenceDiagram participant 前端 participant 后端 participant 工具 前端->>后端: POST /send-message
{threadId, runId, messages} 后端-->>前端: RUN_STARTED 后端-->>前端: TOOL_CALL_START 后端-->>前端: TOOL_CALL_ARGS (增量) 后端-->>前端: TOOL_CALL_END 后端->>工具: 服务端执行工具 工具-->>后端: 返回结果 后端-->>前端: TOOL_CALL_RESULT 后端-->>前端: TEXT_MESSAGE_START 后端-->>前端: TEXT_MESSAGE_CONTENT 后端-->>前端: TEXT_MESSAGE_END 后端-->>前端: RUN_FINISHED ``` ### 人机交互(Human-in-the-Loop) 通过前端工具实现人机交互。当需要用户输入时,Agent 调用一个前端定义的工具,前端执行该工具时向用户展示 UI 并收集输入。 ```mermaid sequenceDiagram participant 前端 participant 后端 前端->>后端: POST /send-message
{tools: [{name: "confirmAction", ...}]} 后端-->>前端: RUN_STARTED 后端-->>前端: TEXT_MESSAGE_* (部分响应) 后端-->>前端: TOOL_CALL_START (confirmAction) 后端-->>前端: TOOL_CALL_ARGS 后端-->>前端: TOOL_CALL_END 后端-->>前端: RUN_FINISHED Note over 前端: 执行 confirmAction 工具
显示确认对话框,收集用户输入 前端->>后端: POST /send-message
{messages: [..., {role:"tool", content:"confirmed"}]} 后端-->>前端: RUN_STARTED 后端-->>前端: TEXT_MESSAGE_* (继续响应) 后端-->>前端: RUN_FINISHED ``` **示例:确认工具定义** ```typescript const confirmTool = { name: "confirmAction", description: "请求用户确认操作", parameters: { type: "object", properties: { action: { type: "string", description: "需要确认的操作" }, importance: { type: "string", enum: ["low", "medium", "high", "critical"] } }, required: ["action"] } }; ``` --- ## 场景说明 ### 场景 1:纯对话 **请求:** ```json { "threadId": "thread_001", "runId": "run_001", "messages": [ { "id": "msg_1", "role": "user", "content": "你好" } ], "tools": [], "context": [] } ``` **响应:** ``` data: {"type":"RUN_STARTED","threadId":"thread_001","runId":"run_001"} data: {"type":"TEXT_MESSAGE_START","messageId":"msg_2","role":"assistant"} data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"你好"} data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"!有什么可以帮你的吗?"} data: {"type":"TEXT_MESSAGE_END","messageId":"msg_2"} data: {"type":"RUN_FINISHED","threadId":"thread_001","runId":"run_001"} ``` --- ### 场景 2:前端工具调用 前端定义了 `search_local_files` 工具(只有浏览器能访问本地文件)。 **第一轮请求:** ```json { "threadId": "thread_003", "runId": "run_003", "messages": [ { "id": "msg_1", "role": "user", "content": "帮我搜索本地的报告文件" } ], "tools": [ { "name": "search_local_files", "description": "搜索用户本地文件", "parameters": { "type": "object", "properties": { "keyword": { "type": "string" } } } } ], "context": [] } ``` **第一轮响应:** ``` data: {"type":"RUN_STARTED","threadId":"thread_003","runId":"run_003"} data: {"type":"TOOL_CALL_START","toolCallId":"call_002","toolCallName":"search_local_files"} data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_002","delta":"{\"keyword\":\"报告\"}"} data: {"type":"TOOL_CALL_END","toolCallId":"call_002"} data: {"type":"RUN_FINISHED","threadId":"thread_003","runId":"run_003"} ``` **前端处理:** 1. 从事件中收集 `toolCalls`:`{id: "call_002", type: "function", function: {name: "search_local_files", arguments: "{\"keyword\":\"报告\"}"}}` 2. 执行工具,获得结果 3. 发送第二轮请求 **第二轮请求:** ```json { "threadId": "thread_003", "runId": "run_004", "messages": [ { "id": "msg_1", "role": "user", "content": "帮我搜索本地的报告文件" }, { "id": "msg_2", "role": "assistant", "toolCalls": [ { "id": "call_002", "type": "function", "function": { "name": "search_local_files", "arguments": "{\"keyword\":\"报告\"}" } } ] }, { "id": "msg_3", "role": "tool", "toolCallId": "call_002", "content": "[\"2024年度报告.pdf\", \"Q3报告.docx\"]" } ], "tools": [...], "context": [] } ``` **第二轮响应:** ``` data: {"type":"RUN_STARTED","threadId":"thread_003","runId":"run_004"} data: {"type":"TEXT_MESSAGE_START","messageId":"msg_4","role":"assistant"} data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_4","delta":"找到了 2 个文件:2024年度报告.pdf 和 Q3报告.docx"} data: {"type":"TEXT_MESSAGE_END","messageId":"msg_4"} data: {"type":"RUN_FINISHED","threadId":"thread_003","runId":"run_004"} ``` --- ### 场景 3:服务端工具调用 服务端定义了 `get_weather` 工具,前端无需传递该工具。 **请求:** ```json { "threadId": "thread_002", "runId": "run_002", "messages": [ { "id": "msg_1", "role": "user", "content": "北京天气怎么样?" } ], "tools": [], "context": [] } ``` **响应:** ``` data: {"type":"RUN_STARTED","threadId":"thread_002","runId":"run_002"} data: {"type":"TEXT_MESSAGE_START","messageId":"msg_2","role":"assistant"} data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"让我查一下"} data: {"type":"TEXT_MESSAGE_END","messageId":"msg_2"} data: {"type":"TOOL_CALL_START","toolCallId":"call_001","toolCallName":"get_weather","parentMessageId":"msg_2"} data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_001","delta":"{\"city\":\"北京\"}"} data: {"type":"TOOL_CALL_END","toolCallId":"call_001"} data: {"type":"TOOL_CALL_RESULT","messageId":"msg_tool_1","toolCallId":"call_001","content":"晴天,25°C"} data: {"type":"TEXT_MESSAGE_START","messageId":"msg_3","role":"assistant"} data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_3","delta":"北京今天晴天,25°C。"} data: {"type":"TEXT_MESSAGE_END","messageId":"msg_3"} data: {"type":"RUN_FINISHED","threadId":"thread_002","runId":"run_002"} ``` **说明:** - 前端收到 `TOOL_CALL_START`,工具名 `get_weather` 不在前端 `tools` 列表中 - 前端等待 `TOOL_CALL_RESULT` 事件,直接展示结果 - 无需前端执行工具或发送额外请求 --- ### 场景 4:人机交互(Human-in-the-Loop) 通过前端工具实现用户确认。 **第一轮请求:** ```json { "threadId": "thread_004", "runId": "run_005", "messages": [ { "id": "msg_1", "role": "user", "content": "删除所有临时文件" } ], "tools": [ { "name": "confirmAction", "description": "请求用户确认危险操作", "parameters": { "type": "object", "properties": { "action": { "type": "string" }, "count": { "type": "number" } }, "required": ["action"] } } ], "context": [] } ``` **第一轮响应:** ``` data: {"type":"RUN_STARTED","threadId":"thread_004","runId":"run_005"} data: {"type":"TEXT_MESSAGE_START","messageId":"msg_2","role":"assistant"} data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"即将删除 15 个临时文件"} data: {"type":"TEXT_MESSAGE_END","messageId":"msg_2"} data: {"type":"TOOL_CALL_START","toolCallId":"call_003","toolCallName":"confirmAction","parentMessageId":"msg_2"} data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_003","delta":"{\"action\":\"删除临时文件\",\"count\":15}"} data: {"type":"TOOL_CALL_END","toolCallId":"call_003"} data: {"type":"RUN_FINISHED","threadId":"thread_004","runId":"run_005"} ``` **客户端处理:** 1. 解析 `confirmAction` 工具调用参数 2. 显示确认对话框 3. 用户点击"确认" 4. 发送包含工具结果的新请求 **第二轮请求:** ```json { "threadId": "thread_004", "runId": "run_006", "messages": [ { "id": "msg_1", "role": "user", "content": "删除所有临时文件" }, { "id": "msg_2", "role": "assistant", "content": "即将删除 15 个临时文件", "toolCalls": [ { "id": "call_003", "type": "function", "function": { "name": "confirmAction", "arguments": "{\"action\":\"删除临时文件\",\"count\":15}" } } ] }, { "id": "msg_3", "role": "tool", "toolCallId": "call_003", "content": "confirmed" } ], "tools": [...], "context": [] } ``` **第二轮响应:** ``` data: {"type":"RUN_STARTED","threadId":"thread_004","runId":"run_006"} data: {"type":"TEXT_MESSAGE_START","messageId":"msg_4","role":"assistant"} data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_4","delta":"已删除 15 个临时文件。"} data: {"type":"TEXT_MESSAGE_END","messageId":"msg_4"} data: {"type":"RUN_FINISHED","threadId":"thread_004","runId":"run_006"} ``` --- --- # AI/Agent 开发/本地开发 > 当前文档链接: https://docs.cloudbase.net/ai/agent-development/local-development 本文介绍如何在本地开发和调试 Agent。 ## 快速开始 ### 第一步:获取项目代码 #### 方式一:从 GitHub 模板获取(推荐) 访问 [awesome-cloudbase-examples](https://github.com/TencentCloudBase/awesome-cloudbase-examples) 获取完整的示例项目集合。 ```bash # 克隆示例仓库 git clone https://github.com/TencentCloudBase/awesome-cloudbase-examples.git cd awesome-cloudbase-examples/httpfunctions # 选择你需要的模板 cd ``` **可用模板列表**: | 模板名称 | 语言 | 框架 | 说明 | | ------------------------------------------------------------------------------------------------------------------------------- | ---------- | --------- | ------------------------------- | | [`langchain-js`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/langchain-js) | JavaScript | LangChain | LangChain Agent 示例(JS) | | [`langchain-ts`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/langchain-ts) | TypeScript | LangChain | LangChain Agent 示例(TS) | | [`langgraph-js`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/langgraph-js) | JavaScript | LangGraph | LangGraph Agent 示例(JS) | | [`langgraph-ts`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/langgraph-ts) | TypeScript | LangGraph | LangGraph Agent 示例(TS) | | [`langgraph-python`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/langgraph-python) | Python | LangGraph | LangGraph Agent 示例(Python) | | [`crewai-python`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/crewai-python) | Python | CrewAI | CrewAI 多 Agent 协作示例 | | [`adk-python`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/adk-python) | Python | ADK | Agent Development Kit 示例 | | [`adp-js`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/adp-js) | JavaScript | ADP | Agent Development Platform 示例 | | [`adp-ts`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/adp-ts) | TypeScript | ADP | Agent Development Platform 示例 | | [`coze-python`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/coze-python) | Python | Coze | Coze Agent 示例 | | [`n8n-js`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/n8n-js) | JavaScript | n8n | n8n Agent 示例 | | [`n8n-python`](https://github.com/TencentCloudBase/awesome-cloudbase-examples/tree/master/httpfunctions/n8n-python) | Python | n8n | n8n Agent 示例 | :::tip 框架接入指南 如需了解如何接入特定框架,请参考: - [LangChain 开发指南](./frameworks/langchain.md) - [LangGraph 开发指南](./frameworks/langgraph.md) - [CrewAI 开发指南](./frameworks/crewai.md) ::: #### 方式二:同步线上服务到本地 如果你已经在云端部署了 Agent 服务,可以将代码同步到本地进行开发。 **前置条件**:安装 CloudBase CLI ```bash npm install -g @cloudbase/cli # 登录云开发 cloudbase login ``` **同步 HTTP 云函数**: ```bash # 查看云函数列表 cloudbase fn list -e # 下载云函数代码到本地 mkdir -p ./my-agent && cloudbase fn code download -e ./my-agent cd ./my-agent ``` **同步云托管服务**: ```bash # 查看云托管服务列表 cloudbase cloudrun list # 下载云托管代码到本地 cloudbase cloudrun download -s --targetPath ./my-agent cd my-agent ``` :::info CLI 文档 更多 CLI 命令和用法,请参考 [CloudBase CLI 文档](/cli-v1/intro)。 ::: ### 第二步:安装依赖 根据项目类型安装依赖。每个模板的 README 文件中都有详细的安装指引。 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; ```bash # 使用 npm npm install # 或使用 yarn yarn install # 或使用 pnpm pnpm install ``` ```bash # 使用 pip pip install -r requirements.txt ``` :::tip 查看模板文档 每个模板都包含详细的 README 文档,说明了依赖安装、配置和运行方式。建议先阅读模板的 README 文件。 ::: ### 第三步:配置环境变量 不同模板的环境变量配置可能不同,以下以 LangGraph 模板为例。 创建 `.env` 文件: ```bash # 配置 agent 中调用的模型信息 OPENAI_API_KEY=xxx OPENAI_BASE_URL=xxx OPENAI_MODEL=xxx # Temperature (default: 0.7) OPENAI_TEMPERATURE=0.7 # 可选:启用 CORS(本地开发跨域调试时可设为 true) # ENABLE_CORS=true # 服务端口(默认 3000) ``` :::tip 获取配置信息 - **API Key**:在[云开发控制台 - API 密钥](https://tcb.cloud.tencent.com/dev#/env/apikey)创建 - **模型列表**:参考 [接入大模型](/ai/model/overview) ::: ### 第四步:启动服务 ```bash node src/index.js ``` ```bash # 直接运行 python -u app.py ``` 服务启动后,默认在 `http://localhost:9000` 提供服务。 ## 使用 Docker 运行 对于云托管项目,可以使用 Docker 进行本地开发,确保本地环境与线上环境一致。 ```bash # 构建镜像 docker build -t my-agent . # 运行容器 docker run -p 9000:9000 \ -e OPENAI_API_KEY=your-api-key \ -e OPENAI_BASE_URL=model-base-url \ -e OPENAI_MODEL=model \ -e OPENAI_TEMPERATURE=0.7 \ my-agent ``` ## 本地调试 ### 方式一:使用 cURL 调试 ```bash curl 'http://localhost:9000/send-message' \ -H 'Accept: text/event-stream' \ -H 'Content-Type: application/json' \ --data-raw '{ "threadId": "550e8400-e29b-41d4-a716-446655440000", "messages": [ { "id": "msg-1", "role": "user", "content": "你好" } ], "tools": [], "context": [], "state": {}, "forwardedProps": {} }' ``` ### 方式二:代理线上请求到本地 此方法适用于将 Agent UI 或 SDK 对线上 Agent 的请求代理到本地,实现不更改客户端代码的情况下本地调试。 **线上地址示例**: ``` https://lowcode-3geceaptb6c8835b.api.tcloudbasegateway.com/v1/aibot/bots/agent-kkk-5gxr7dys9d0c5427/send-message ``` #### 1. 安装 Whistle ```bash npm install -g whistle # 启动 Whistle w2 start ``` #### 2. 配置代理规则 访问 `http://localhost:8899`,在 Rules 中添加: ``` # 将线上 Agent 请求代理到本地 https://lowcode-3geceaptb6c8835b.api.tcloudbasegateway.com/v1/aibot/bots/agent-kkk-5gxr7dys9d0c5427/send-message resCross://* http://localhost:9000/send-message ``` #### 3. 配置代理 根据使用方式不同,在不同位置配置代理: **在微信开发者工具中**: 1. 打开微信开发者工具 2. 设置 → 代理设置 → 手动设置代理 3. 代理服务器:`127.0.0.1` 4. 端口:`8899` 5. 勾选"使用代理" **在浏览器中**: 可以通过浏览器插件将网页中的请求代理到 8899 端口: - Chrome:使用 [SwitchyOmega](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif) 插件 1. 安装 SwitchyOmega 插件 2. 新建情景模式 → 代理服务器 3. 代理协议:HTTP 4. 代理服务器:`127.0.0.1` 5. 代理端口:`8899` 6. 应用选项并切换到该情景模式 #### 4. 安装 HTTPS 证书 访问 `http://localhost:8899`,点击 HTTPS → 下载根证书并安装。 :::tip 使用场景 - 使用 Agent UI 调试线上 Agent - 使用 SDK 在真实环境中调试 - 不修改客户端代码的情况下本地调试 ::: ## 常见问题 ### 1. 端口被占用 ```bash # 查看端口占用 lsof -i :9000 # macOS/Linux netstat -ano | findstr :9000 # Windows # 杀死进程 kill -9 # macOS/Linux taskkill /PID /F # Windows # 或更换端口 PORT=3001 node index.js ``` ### 2. 代理不生效 - 确保 Whistle 已启动:`w2 status` - 检查代理规则是否正确 - 确认系统代理已配置 - 小程序需要在微信开发者工具中单独配置代理 ## 下一步 完成本地开发后,可以将 Agent 部署到云端: - 📦 [HTTP 云函数部署](./deployment/cloud-function.md) - 🚀 [云托管部署](./deployment/cloud-run.md) 或了解如何在客户端调用 Agent: - 📱 [小程序调用](./integration/miniprogram.md) - 🌐 [Web 调用](./integration/web.md) - 🔧 [Node.js 调用](./integration/nodejs.md) --- # AI/Agent 开发/Agent UI 组件/Agent UI 小程序组件 > 当前文档链接: https://docs.cloudbase.net/ai/agent-ui/agent-ui-mp Agent UI 小程序源码组件可以帮助开发者快速在微信小程序中搭建聊天界面。 Agent UI 提供以下功能: - 对接云开发大模型 - 对接云开发 Agent(智能体) :::tip 由于微信小程序基础库安全策略升级,文件上传功能解析流程可能受到影响,请开发者进行 agent ui 组件升级 - 小程序源码组件,请更新替换至最新版本 - 微搭低码组件,请新建页面然后重新拖动更新 agent-ui 区块,完成后发布应用 ::: ## 快速体验 方式一:在微信开发者工具入口中通过模板创建项目,按照模板指引,快速体验 Agent-UI 小程序源码组件。 ![](https://qcloudimg.tencent-cloud.cn/raw/9e7ce66913a90131071d84b3b8117635.png) 方式二:[下载项目示例源码包](https://gitee.com/TencentCloudBase/cloudbase-agent-ui/tree/main/apps/miniprogram-agent-ui) ,导入到微信开发者工具使用。 > 注意:项目示例包中需手动修改 app.json 中 env 配置为您的环境 ID,pages/chatBot/chatBot.js 中 botId 配置 为您的 agent ID ## 如何引入组件到已有微信小程序 ### 1. 拷贝 miniprogram/components/agent-ui 组件到小程序 ![image](https://qcloudimg.tencent-cloud.cn/raw/2fb0ff731e5fbded35b970589c9ec89d.png) ### 2. 在页面 .json 配置文件中注册组件 ```js { "usingComponents": { "agent-ui":"/components/agent-ui/index" }, } ``` ### 3. 在页面 .wxml 文件中使用组件 ```js ``` ### 4. 在页面 .js 文件中编写配置 ```js data: { chatMode: "model", // bot 表示使用agent,model 表示使用大模型 showBotAvatar: true, // 是否在对话框左侧显示头像 agentConfig: { botId: "bot-e7d1e736", // agent id, allowWebSearch: true, // 允许客户端选择启用联网搜索(仅对配置型 agent 生效) allowUploadFile: true, // 允许上传文件(仅对配置型 agent 生效) allowPullRefresh: true, // 允许下拉刷新(仅对配置型 agent 生效) allowUploadImage: true // 允许上传图片(仅对配置型 agent 生效) showToolCallDetail: true, // 是否展示工具调用细节(仅对配置型 agent 生效) allowMultiConversation: true, // 是否展示会话列表,创建会话按钮(仅对配置型 agent 生效) allowVoice: true, // 是否允许客户端界面展示语音按钮(仅对配置型 agent 生效) // 前端工具示例列表(仅对云托管或者云函数 agent 生效) tools: [ { name: "get_weather", description: "获取指定城市的天气", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"], }, // 同步函数示例 handler: (params) => { const { city } = params; return `城市${city}的天气是晴朗的,温度是25摄氏度,无风`; } }, { name: "get_location", description: "获取指定城市的经纬度", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"], }, // 异步函数示例 handler: async (params) => { const { city } = params; // 模拟网络延迟 return new Promise((resolve) => { setTimeout(() => { resolve(`城市${city}的位置是东经114.305556度,北纬22.543056度`); }, 2000); }); }, }, ], }, modelConfig: { modelProvider: "cloudbase", quickResponseModel: "deepseek-v4-flash", logo: "", welcomeMsg: "欢迎语", }, } ``` ### 5. 在 app.js 中,onLauch 生命周期内,异步初始化 sdk ```js // app.js App({ onLaunch: function () { wx.cloud.init({ env: "<云开发环境ID>", }); }, }); ``` ## 组件参数和相关说明 ### 组件参数 | 参数名称 | 参数可选值 | 说明 | | :--------------------------------: | :---------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | | chatMode | 'bot' 或 'model' | 当 chatMode='bot'时,agentConfig.botId 必填;当 chatMode='model'时,modelConfig.modelProvider 和modelConfig.quickResponseModel必填 | | showBotAvatar | boolean | 界面是否展示左侧头像 | | agentConfig.botId | string | agent id,当 chatMode = 'bot' 时,必填 | | agentConfig.allowUploadFile | boolean | 界面是否展示文件上传 | | agentConfig.allowWebSearch | boolean | 允许界面呈现联网配置开关 | | agentConfig.allowPullRefresh | boolean | 允许下拉刷新 | | agentConfig.allowUploadImage | boolean | 允许上传图片 | | agentConfig.allowMultiConversation | boolean | 是否展示会话列表,创建会话按钮 | | agentConfig.showToolCallDetail | boolean | 是否展示工具调用细节 | | agentConfig.allowVoice | boolean | 是否允许客户端界面展示语音按钮 | | modelConfig.modelProvider | string | 大模型服务商 | | modelConfig.quickResponseModel | string | 具体的模型版本, 参考[大模型版本](#大模型版本) | | envShareConfig.resourceAppid | string | 环境共享的资源方 AppID | | envShareConfig.resourceEnv | string | 环境共享的资源方环境 ID | | agentConfig.tools | array | 前端工具示例列表[配置规则](/ai/agent/http-agent-protocol#前端工具-vs-服务端工具) | ### 相关说明 ** 1. 文件上传说明 ** - **大小限制**:单文件不超过 10M - **数量限制**:单次最多支持 5 个文件 - **文件类型**:pdf、txt、doc、docx、ppt、pptx、xls、xlsx、csv - **request 合法域名配置**:微信小程序上传文件需要添加“文件上传接口”到 request 合法域名列表,文件上传域名为:https://\{envid\}.api.tcloudbasegateway.com,替换 envid 为真实的环境 ID, 前往微信公众平台[https://mp.weixin.qq.com](https://mp.weixin.qq.com)配置 request 合法域名 ** 2. 图片上传说明 ** 每次仅支持上传单张图片,最大不超过 30MB ** 3. 语音功能说明 ** 若未授予小程序使用麦克风权限,组件会进行权限申请,请同意授予 ### 示例 - **使用 deepseek-v4-flash 模型** ```json { "chatMode": "model", "showBotAvatar": true, "modelConfig": { "modelProvider": "cloudbase", "quickResponseModel": "deepseek-v4-flash", "logo": "", "welcomeMsg": "欢迎语" } } ``` - **使用 hy3 模型** ```json data: { chatMode: "model", showBotAvatar: true, modelConfig: { modelProvider: "cloudbase", quickResponseModel: "hy3", welcomeMsg: "欢迎语", }, } ``` - **使用 Agent** ```js { chatMode: "bot", // bot 表示使用agent,model 表示使用大模型 showBotAvatar: true, // 是否在对话框左侧显示头像 agentConfig: { botId: "bot-e7d1e736", // agent id, allowWebSearch: true, // 允许客户端选择启用联网搜索(仅对配置型 agent 生效) allowUploadFile: true, // 允许上传文件(仅对配置型 agent 生效) allowPullRefresh: true, // 允许下拉刷新(仅对配置型 agent 生效) allowUploadImage: true // 允许上传图片 (仅对配置型 agent 生效) showToolCallDetail: true, // 是否展示工具调用细节(仅对配置型 agent 生效) allowMultiConversation: true, // 是否展示会话列表,创建会话按钮(仅对配置型 agent 生效) allowVoice: true // 是否允许客户端界面展示语音按钮(仅对配置型 agent 生效) // 前端工具示例列表(仅对云托管或者云函数 agent 生效) tools: [ { name: "get_weather", description: "获取指定城市的天气", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"], }, handler: (params) => { const { city } = params; return `城市${city}的天气是晴朗的,温度是25摄氏度,无风`; } }, ], } } ``` ## 获取 Agent ID ![](https://qcloudimg.tencent-cloud.cn/raw/cba2cfa73269dabe7c9067d5a27c67da.png) ## 大模型版本 ### hunyuan {#hunyuan} | 模型版本 | 说明 | | :----------: | :-----------: | | hy3 | 混元新一代模型 | ### cloudbase | 模型版本 | 说明 | | :--------------: | :------------------------------------------------: | | deepseek-v4-flash | 推理模型,专为数学、代码生成和复杂逻辑推理任务设计 | | deepseek-v3-0324 | 对话模型,增强推理和中文能力 | ## 通过环境共享调用其他环境的 AI 能力 假设 A 小程序创建了云开发环境,B 小程序想跨环境调用 A 小程序云开发环境下的 AI 能力,可参照以下指引。 ### 开通环境共享 登录 A 小程序,开通环境共享,将 A 小程序的云开发环境共享给 B 小程序使用 > A 小程序和 B 小程序必须是同一个主体才能环境共享 ![](https://qcloudimg.tencent-cloud.cn/raw/9d7970656735c76429613055667beb48.png) ### 配置环境共享参数 在 B 小程序中使用 Agent-UI 时,将 A 小程序的 appid 和 envId 传给 Agent-UI 组件 参数如下: ```js data: { chatMode: "model", // bot 表示使用agent,model 表示使用大模型 showBotAvatar: true, // 是否在对话框左侧显示头像 modelConfig: { modelProvider: "cloudbase", quickResponseModel: "deepseek-v4-flash", logo: "", // model 头像 welcomeMsg: "欢迎语", // model 欢迎语 }, envShareConfig: { // 不使用环境共享,请删除此配置或配置EnvShareConfig:null // 资源方 AppID resourceAppid: "wx7a******5f4f", // 资源方环境 ID resourceEnv: "chr**************46d2d", }, }, ``` ## 自定义 MCP 工具卡片 参考[工具卡片开发指引](../agent/toolCard) --- # AI/Agent 开发/Agent UI 组件/Agent UI 小程序 SDK > 当前文档链接: https://docs.cloudbase.net/ai/agent-ui/agent-ui-mp-sdk 微信小程序的 [AG-UI](https://docs.ag-ui.com) 客户端 SDK。 :::tip 选择合适的小程序集成方案 - **想要开箱即用的聊天界面?** → 使用 [Agent UI 小程序组件](./agent-ui-mp) - **想要完全自定义 UI?** → 使用本 SDK(`@cloudbase/agent-ui-miniprogram`) ::: ## 为什么用这个库? **[AG-UI](https://docs.ag-ui.com)** 是一个用于构建 AI 对话应用的协议。它定义了客户端和服务端如何通信——流式文本、工具调用、消息历史等。 如果从零开始构建 AG-UI 客户端,你需要: 1. 解析服务端的 Server-Sent Events 流 2. 处理 15+ 种事件类型(TEXT_MESSAGE_START、TOOL_CALL_ARGS、RUN_ERROR...) 3. 维护消息状态、合并流式片段、跟踪工具调用状态 4. 执行客户端工具并回传结果 5. 保持 UI 与所有状态变化同步 **@cloudbase/agent-ui-miniprogram 帮你处理了这些逻辑。** 你只需要: 调用 `this.agui.sendMessage` 发送消息: ```javascript // chat.js this.agui.sendMessage('你好') ``` 将注入的消息列表数据 `uiMessages` 渲染成 UI: ```xml {{item.role}}: {{item.parts[0].text}} ``` --- ## 安装 ```bash npm install @cloudbase/agent-ui-miniprogram@beta ``` --- ## 快速开始 ```javascript import { createAGUIBehavior, CloudbaseTransport } from '@cloudbase/agent-ui-miniprogram' Component({ behaviors: [ createAGUIBehavior({ transport: new CloudbaseTransport({ botId: 'your-bot-id' }), }) ], methods: { onSend(e) { this.agui.sendMessage(e.detail.value) } } }) ``` ```xml {{part.text}} 🔧 {{part.name}}: {{part.status}} {{agui.error.message}} ``` --- ## 核心概念 本 SDK 使用小程序的 [Behavior](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html) 机制——一种可以在多个组件间复用代码的 mixin 模式。 当你在组件中添加由 `createAGUIBehavior()` 创建出的 Behavior 后,它会: 1. 将 AG-UI 相关状态注入到 `this.data.agui` 中 2. 提供 `this.agui.xxx` 方法调用 AG-UI 相关能力 ### 状态 Behavior 会在 `this.data.agui` 中提供以下数据: | 属性 | 用途 | |------|------| | `uiMessages` | 渲染聊天界面 | | `isRunning` | 显示加载状态、禁用输入框 | | `error` | 显示错误信息 | 对于其他数据,请查看 [API 参考](#api-参考) 了解。 ### Transport ``` 小程序 SDK <--transport--> AG-UI 服务端 <--> LLM ``` @cloudbase/agent-ui-miniprogram 采用了通信层(Transport)无关的设计。它只负责处理 AG-UI 事件流、管理状态、提供交互方法,而不关心如何与后端通信。 Transport 层专门负责与后端通信,你可以在创建 Behavior 的时候传入任意符合定义的 Transport 实例。 ```javascript createAGUIBehavior({ transport: new AnyTransport() }) ``` @cloudbase/agent-ui-miniprogram 提供了 `CloudbaseTransport`,对接 Cloudbase 云开发 Agent。 ```javascript createAGUIBehavior({ transport: new CloudbaseTransport({ botId: '...' }) }) ``` 自定义 Transport 需要满足的接口: ```typescript interface Transport { run(input: RunAgentInput): AsyncIterable } ``` ### 客户端工具 客户端工具能够让 Agent 能够与小程序环境交互。 **使用场景:** - 访问设备功能(位置、相机、存储) - 读取客户端数据(用户偏好、购物车) - 触发 UI 操作(导航、弹窗、提示) **示例:** ```javascript createAGUIBehavior({ transport, tools: [{ name: 'get_location', description: '获取用户当前位置', parameters: { type: 'object', properties: {} }, handler: async ({ args }) => { const res = await wx.getLocation() return JSON.stringify(res) } }] }) ``` 当 Agent 决定调用工具时,SDK 会执行你的 handler 并自动将结果发回给 Agent。 --- ## CloudbaseTransport 配置 用于微信云开发 Cloudbase 的 Transport 实现,基于 `wx.cloud.extend.AI.bot`。 ### 前置条件 1. **开通微信云开发** - 在小程序中启用云开发 2. **创建 Agent** - 创建一个 AI Bot 并获取其 `agentId` ### 配置步骤 #### 1. 在 app.js 中初始化云开发 ```javascript // app.js App({ onLaunch() { wx.cloud.init({ env: 'your-env-id' }) } }) ``` #### 2. 使用 CloudbaseTransport ```javascript import { createAGUIBehavior, CloudbaseTransport } from '@cloudbase/agent-ui-miniprogram' const transport = new CloudbaseTransport({ botId: 'your-agent-id' // 从云开发控制台获取 }) Component({ behaviors: [createAGUIBehavior({ transport })] }) ``` ### CloudbaseTransport 构造函数 ```typescript new CloudbaseTransport(options: { botId: string }) ``` | 选项 | 类型 | 必需 | 说明 | |------|------|------|------| | `botId` | `string` | 是 | 云开发控制台中的 Agent ID | ### 工作原理 CloudbaseTransport 内部调用 `wx.cloud.extend.AI.bot.sendMessage()`: ```javascript const res = await wx.cloud.extend.AI.bot.sendMessage({ botId: 'your-bot-id', data: { threadId: '...', messages: [...], tools: [...], context: [...] } }) for await (const event of res.eventStream) { // AG-UI 事件在这里流式返回 } ``` --- ## API 参考 ### `createAGUIBehavior(options?)` 创建一个带有 AG-UI 状态管理的 Behavior mixin。 ```javascript import { createAGUIBehavior } from '@cloudbase/agent-ui-miniprogram' Component({ behaviors: [createAGUIBehavior(options)] }) ``` #### 配置项 | 选项 | 类型 | 说明 | |------|------|------| | `transport` | `Transport` | Transport 实现(`sendMessage` 必需) | | `threadId` | `string` | 会话线程 ID(不传则自动生成) | | `messages` | `Message[]` | 初始消息 | | `tools` | `ClientTool[]` | 带有 handler 的客户端工具 | | `contexts` | `Context[]` | Agent 运行时的上下文 | | `onRawEvent` | `(event: BaseEvent) => void` | 每个原始 AG-UI 事件的回调 | ### 实例方法 组件 attached 后通过 `this.agui.xxx` 访问。 | 方法 | 签名 | 说明 | |------|------|------| | `init` | `(config: AGUIConfig) => void` | 初始化或重新初始化 transport/threadId | | `sendMessage` | `(input: string \| Message[]) => Promise` | 发送消息并运行 agent | | `appendMessage` | `(message: Message) => void` | 添加消息但不触发 agent | | `setMessages` | `(messages: Message[]) => void` | 替换整个消息历史 | | `reset` | `() => void` | 重置为配置项中的初始状态 | | `setThreadId` | `(threadId: string) => void` | 更改线程 ID | | `addTool` | `(tool: ClientTool) => void` | 注册新工具 | | `removeTool` | `(name: string) => void` | 按名称移除工具 | | `updateTool` | `(name: string, updates: Partial) => void` | 更新工具属性 | | `clearTools` | `() => void` | 移除所有工具 | ### 状态属性 通过 `this.data.agui.xxx` 访问(WXML 中使用 `{{agui.xxx}}`)。 | 属性 | 类型 | 说明 | |------|------|------| | `messages` | `Message[]` | 原始消息历史(用于 AI) | | `uiMessages` | `UIMessage[]` | UI 优化的消息(用于渲染) | | `isRunning` | `boolean` | Agent 是否正在处理中 | | `error` | `AGUIClientError \| null` | 当前错误(如果有) | | `activeToolCalls` | `ToolCallState[]` | 进行中的工具调用 | | `threadId` | `string` | 当前会话线程 ID | | `tools` | `Tool[]` | 已注册的工具定义(不含 handler) | | `contexts` | `Context[]` | 已注册的上下文 | | `runId` | `string \| null` | 当前运行 ID(未运行时为 null) | ### `this.agui` 完整类型 可以通过 `this.agui` 访问到所有的数据和方法。 ```typescript interface AGUINamespace { // 方法 init(config: AGUIConfig): void sendMessage(input: string | Message[]): Promise appendMessage(message: Message): void setMessages(messages: Message[]): void reset(): void setThreadId(threadId: string): void addTool(tool: ClientTool): void removeTool(name: string): void updateTool(name: string, updates: Partial): void clearTools(): void // 状态(只读,与 this.data.agui 同步) readonly messages: Message[] readonly uiMessages: UIMessage[] readonly isRunning: boolean readonly error: AGUIClientError | null readonly activeToolCalls: ToolCallState[] readonly threadId: string readonly tools: Tool[] readonly contexts: Context[] readonly runId: string | null // 配置(只读,传入 createAGUIBehavior 的原始选项) readonly config: CreateAGUIBehaviorOptions } ``` ### 类型定义 #### AG-UI 协议类型 以下类型来自 [AG-UI 协议](https://docs.ag-ui.com),本 SDK 对标版本:**v0.0.42** 详细定义请参考 AG-UI 官方文档: - [`Message`](https://docs.ag-ui.com/sdk/js/core/types#message) - 消息结构 - [`Tool`](https://docs.ag-ui.com/sdk/js/core/types#tool) - 工具定义 - [`ToolCall`](https://docs.ag-ui.com/sdk/js/core/types#toolcall) - 工具调用结构 - [`Context`](https://docs.ag-ui.com/sdk/js/core/types#context) - Agent 上下文 - [`RunAgentInput`](https://docs.ag-ui.com/sdk/js/core/types#runagentinput) - Agent 运行输入 - [`BaseEvent`](https://docs.ag-ui.com/sdk/js/core/events) - 事件基础结构 - [`EventType`](https://docs.ag-ui.com/sdk/js/core/events) - 事件类型枚举 #### UIMessage ```typescript interface UIMessage { id: string role: 'user' | 'assistant' | 'system' | 'developer' parts: UIMessagePart[] } type UIMessagePart = TextPart | ToolPart interface TextPart { id: string type: 'text' text: string state?: 'streaming' | 'done' } interface ToolPart { id: string type: 'tool' toolCallId: string name: string argsString: string args: Record status: 'pending' | 'ready' | 'executing' | 'completed' | 'failed' result?: unknown error?: AGUIClientError } ``` #### ClientTool 扩展自 AG-UI 的 `Tool` 类型,增加了 `handler` 函数用于执行客户端工具。 ```typescript interface ClientTool extends Tool { handler: (params: ToolHandlerParams) => string | Promise } interface ToolHandlerParams { name: string description: string toolCallId: string args: Record } ``` #### AGUIClientError ```typescript interface AGUIClientError { code: AGUIClientErrorCode message: string recoverable: boolean details?: unknown originalError?: Error } type AGUIClientErrorCode = | 'INIT_ERROR' | 'TRANSPORT_ERROR' | 'RUNTIME_ERROR' | 'INVALID_EVENT' | 'TOOL_EXECUTION_ERROR' | 'TOOL_NOT_FOUND' | 'PARSE_ERROR' | 'TIMEOUT' | 'INVALID_CONFIG' | 'UNKNOWN' ``` #### ToolCallState ```typescript interface ToolCallState { toolCallId: string name: string argsString: string // 原始参数字符串(流式传输中) args: Record // 解析后的参数 status: ToolCallStatus result?: unknown error?: AGUIClientError } type ToolCallStatus = 'pending' | 'ready' | 'executing' | 'completed' | 'failed' ``` #### AGUIConfig ```typescript interface AGUIConfig { transport: Transport threadId?: string } ``` #### CreateAGUIBehaviorOptions ```typescript interface CreateAGUIBehaviorOptions { transport?: Transport threadId?: string messages?: Message[] tools?: ClientTool[] contexts?: Context[] onRawEvent?: (event: BaseEvent) => void } ``` #### Transport ```typescript interface Transport { run(input: RunAgentInput): AsyncIterable } ``` > `RunAgentInput` 和 `BaseEvent` 类型定义见 [AG-UI 协议类型](#ag-ui-协议类型)。 --- --- # AI/Agent 开发/Agent UI 组件/Agent UI React 组件 > 当前文档链接: https://docs.cloudbase.net/ai/agent-ui/agent-ui-react Agent UI React 组件基于腾讯云 Chat UI 及 Tea 组件设计规范打造,可以帮助开发者快速在 React 应用中搭建 AI 聊天界面。 Agent UI React 组件提供以下功能: - 对接云开发大模型 - 对接云开发 Agent(智能体) :::tip 由于 Agent UI React 组件依赖腾讯云 Tea 组件库,目前适配的 React 版本为 17,18 ::: :::tip 开发环境 Node 版本依赖 >= Node.js v18 ::: ## 演示效果 **PC 尺寸效果** ![](https://qcloudimg.tencent-cloud.cn/raw/3e95c76b8b6ec2d3e521e2ff80949ab9.gif) **移动端 H5 尺寸效果** ## 快速体验 方式一:访问云开发 Agent UI Web React [在线应用示例](https://lowcode-5g5llxbq5bc9299e-1300677802.tcloudbaseapp.com/agent-ui-example),直接体验 方式二:[下载 React 示例项目](https://weda.cloud.tencent.com/ai-resource/agent-ui/agent-ui-example.zip),本地调试体验。 ## 组件使用 ### 1. 安装依赖 ```bash // 安装云开发 @cloudbase/js-sdk,提供 Web 端访问云开发基础资源及 AI 相关能力 npm i @cloudbase/js-sdk // 安装本组件 npm i @cloudbase/agent-ui-react@1.0.0-beta.3 ``` ### 2. 引入组件 ```jsx import cloudbase from "@cloudbase/js-sdk"; import { useEffect, useState } from "react"; const tcb = cloudbase.init({ // 需替换为实际使用云开发环境 id env: "your envId", }); export function YourComponent() { const [ready, setReady] = useState(false); useEffect(() => { auth.signInAnonymously().then(() => { setReady(true); }); }); if (!ready) { return null; } return (
); } ``` **其中 agentConfig 参数结构如下:** | 参数名称 | 参数可选值 | 说明 | | :--------------------------------: | :---------------------: | :----------------------------------------------------------------------------------------------------------------------------------: | | chatMode | 'bot' 或 'model' | 当 chatMode='bot'时,agentConfig.botId 必填;当 chatMode='model'时,modelConfig.modelProvider 和 modelConfig.quickResponseModel 必填 | | showBotAvatar | boolean | 界面是否展示左侧头像 | | agentConfig.botId | string | agent id,当 chatMode = 'bot' 时,必填 | | agentConfig.allowUploadFile | boolean | 界面是否展示文件上传 | | agentConfig.allowWebSearch | boolean | 允许界面呈现联网配置开关 | | agentConfig.allowPullRefresh | boolean | 允许点击刷新 | | agentConfig.allowUploadImage | boolean | 允许上传图片 | | agentConfig.allowMultiConversation | boolean | 是否展示会话列表,创建会话按钮 | | agentConfig.showToolCallDetail | boolean | 是否展示工具调用细节 | | modelConfig.modelProvider | string | 大模型服务商 | | modelConfig.quickResponseModel | string | 具体的模型版本, 参考[大模型版本](#大模型版本) | ** 云开发平台开启对应能力 ** ![](https://qcloudimg.tencent-cloud.cn/raw/97d157613ce2c5c7dc41b79ce1d3d96c.png) ** 文档文件上传说明 ** - **大小限制**:单文件不超过 10M - **数量限制**:单次最多支持 5 个文件 - **文件类型**:pdf、txt、doc、docx、ppt、pptx、xls、xlsx、csv ** 图片文件上传说明 ** - **大小限制**:单张图片最大不超过 30MB - **数量限制**:单次最多支持 1 张图片 **直连大模型调用示例:** - **使用 deepseek 系列模型** ```json { "chatMode": "model", "showBotAvatar": true, "modelConfig": { "modelProvider": "cloudbase", "quickResponseModel": "deepseek-v4-flash", "logo": "", "welcomeMsg": "欢迎语" } } ``` - **使用 hunyuan 系列模型** ```json data: { chatMode: "model", showBotAvatar: true, modelConfig: { modelProvider: "cloudbase", quickResponseModel: "hy3", logo: "", welcomeMsg: "欢迎语", }, } ``` **使用 Agent 调用示例:** ```js { chatMode: "bot", // bot 表示使用agent,model 表示使用大模型 showBotAvatar: true, // 是否在对话框左侧显示头像 agentConfig: { botId: "your botid", // agent id, allowWebSearch: true, // 允许客户端选择启用联网搜索 allowUploadFile: true, // 允许上传文件 allowPullRefresh: true, // 允许下拉刷新 allowUploadImage: true // 允许上传图片 showToolCallDetail: true, // 是否展示工具调用细节 allowMultiConversation: true, // 是否展示会话列表,创建会话按钮 } } ``` ## 获取云开发环境 ID ![](https://qcloudimg.tencent-cloud.cn/raw/b8d5eb37077c3c1c63bfbc4d5e91f2d0.png) ## 获取 Agent ID ![](https://qcloudimg.tencent-cloud.cn/raw/cba2cfa73269dabe7c9067d5a27c67da.png) ## 大模型版本 ### hunyuan {#hunyuan} | 模型版本 | 说明 | | :----------: | :-----------: | | hy3 | 混元新一代模型 | ### cloudbase | 模型版本 | 说明 | | :--------------: | :-------------------------------------------------------------------------------------------------: | | deepseek-v4-flash | 推理模型,专为数学、代码生成和复杂逻辑推理任务设计 | | deepseek-v3-0324 | 相比 DeepSeek-V3 大幅提高了推理任务表现,增强了前端开发能力,升级了中文写作能力,优化了中文搜索能力 | --- # AI/Agent 开发/Agent UI 组件/Agent UI 微搭可视化组件 > 当前文档链接: https://docs.cloudbase.net/ai/agent-ui/agent-ui Agent UI 可以帮助开发者快速搭建聊天界面,并集成到微信小程序、H5、web 场景中。 Agent UI 提供以下功能: - **Agent 会话组件:** 提供对接 Agent 服务的组件,支持对接 Agent 服务的会话能力。 - **用户反馈:** 提供用户反馈弹窗,用于提交用户反馈,优化 Agent 服务质量。 :::tip 由于微信小程序基础库安全策略升级,文件上传功能解析流程可能受到影响,请开发者进行 agent ui 组件升级 - 小程序源码组件,请更新替换至最新版本 - 微搭低码组件,请新建页面然后重新拖动更新 agent-ui 区块,完成后发布应用 ::: ## 使用说明 ### 创建 Agent 在 Agent 管理页面,点击创建 Agent 按钮,创建 Agent,并 配置 Agent 的信息。 复制 Agent ID ,用于 Agent UI 区块配置 。 ### 添加 Agent UI 在公共区块中搜索 Agent UI ,并添加到当前页面。 ![](https://qcloudimg.tencent-cloud.cn/raw/cd4d3b88653566d6b8d137db31a3d254.png) ### 配置 Agent ID 点击 Agent UI 区块,配置 Agent ID , Agent ID 可以从 AI + -> Agent -> 具体的 Agent 详情页面复制 ![](https://qcloudimg.tencent-cloud.cn/raw/0d8fcd2f85273a0ee17a2d4c7ea7eab5.png) 完成以上配置,Agent UI 就已经配置好了,并且展示完整的聊天界面。 ### 发布到云开发平台 可视化构建 **Agent UI** 应用参考如下文档:[发布 Agent 应用](../release/) ### 集成到微信小程序 Agent UI 集成到微信小程序场景中,需要下载 Agent UI 的代码包,引入到微信小程序中,并配置 Agent ID。建议使用[分包方式集成](#通过小程序分包集成)。 1、进入任意微搭应用 2、下载代码包 在右侧区块中搜索 Agent UI ,并下载代码包。 ![](https://qcloudimg.tencent-cloud.cn/raw/5a832b4042b76939a18362e0d1b71af7.png) 3、解压并添加到小程序目录中 解压 Agent UI 代码包,会得到 components 文件夹,方便区分,这里将文件夹名改为 agent_ui,并将代码包添加到小程序目录 miniprogram 中。 小程序目录结构如下: ![](https://qcloudimg.tencent-cloud.cn/raw/bbbfe3f659bae570af7e175333101c4f.png) 4、安装依赖 在 miniprogram 文件夹上右键,在终端中打开,执行 npm 命令:npm install ./agent_ui > 注意:agent_ui 需要替换成第三步重命名的文件夹名。 ![](https://qcloudimg.tencent-cloud.cn/raw/832cbc065b07ad4303d3a4a4db8a5efd.png) ![](https://qcloudimg.tencent-cloud.cn/raw/a0f2019e6b4ef014f3dba1940d898acd.png) 构建 npm 点击小程序 工具 菜单栏,选择构建 npm ![](https://qcloudimg.tencent-cloud.cn/raw/e8e1c27f1fc8cf212e402ad1cf6e64b1.png) 5、初始化 SDK 在小程序 app.js 顶部初始化 weda-client。 envID 和 staticResourceDomain 可以在下载代码包的弹窗中获取。 ![](https://qcloudimg.tencent-cloud.cn/raw/8d9a76991c805fcbd6fdbcce0af04c8f.png) ```js import { init } from "@cloudbase/weda-client"; init({ envID: "xxx", // 云开发环境Id appConfig: { staticResourceDomain: "xxx.tcloudbaseapp.com", // 云开发环境下静态托管域名,用于使用素材资源 }, customConfig: { async login({ app, auth, loginState, defaultLogin }) { await auth.signInWithOpenId(); }, loginConfig: { needSignIn: true }, }, }); ``` 6、页面引用添加区块 注册 agent_ui 组件 在 pages/index 页面中注册 agent_ui 组件 ![](https://qcloudimg.tencent-cloud.cn/raw/6aa97352bcca054c0b1c4cb9fc37f0ab.png) 添加 agent_ui 组件到页面 在 pages/index 页面中添加 agent_ui 组件 ![](https://qcloudimg.tencent-cloud.cn/raw/40cbd533475131cdfee7333beb1177bc.png) > 注意:agent_ui 组件需要放在 id="wd-page-root"的 view 下。 配置 agent ID 在 pages/index 页面中配置 agent ID ![](https://qcloudimg.tencent-cloud.cn/raw/33a58945f13433719cbcc2484b24dc0d.png) ### 通过小程序分包集成 由于 Agent UI 依赖复杂,体积可能较大,我们推荐使用小程序子包的形式来引入。假设代码包解压至小程序项目中 `agent_ui` 目录,目录下包含 `dist`、`package.json` 等文件。 #### 1. 安装依赖包 可以直接下载最新的[预安装包](https://weda.cloud.tencent.com/ai-resource/agent-ui.zip),安装包中已完成 miniprogram 构建,可跳过该步骤。 1. 在 `agent_ui` 目录下执行命令,安装依赖: ```bash npm install ``` 1. 配置多目录 miniprogram_npm 编译。假设小程序 `project.config.json` 与 `app.json` 同级(即:miniprogramRoot 为根目录),配置 `project.config.json`, 中 setting 配置 ```json { "setting": { /* ... */ "packNpmManually": true, "packNpmRelationList": [ /* 如项目自身也需要 package.json,可添加配置 /* { "packageJsonPath": "package.json", "miniprogramNpmDistDir": "./" }, */ /* 路径和目录名以实际为准 */ { "packageJsonPath": "agent_ui/package.json", "miniprogramNpmDistDir": "agent_ui" } ] } } ``` 1. 构建 npm。在微信开发工具,操作栏上选择 `工具 - 构建 npm`,等待构建出 miniprogram_npm。 #### 2. 配置分包 修改 `app.json`,在 subpackages 中添加分包配置,和外部依赖库配置 ```json { "useExtendedLib": { "weui": true }, "subpackages": [ /* ... */ { "root": "agent_ui", "name": "agent-ui", "pages": [] } ] } ``` 同时在 `project.config.json` 中 `setting` 配置中添加过滤无依赖文件设置: ```json { "setting": { /* 添加下述配置 */ "ignoreDevUnusedFiles": true, "ignoreUploadUnusedFiles": true } } ``` #### 3. 初始化 SDK 使用组件前需要进行 sdk 初始化,由于整体存在于分包中,有两种初始化选择。 1. 在分包中初始化,可以保证组件在使用时,sdk 一定初始化完成,保证时序。 在`agent_ui`目录下创建 `index.js` 文件: ```js import * as ClientSDK from "@cloudbase/weda-client"; ClientSDK.init({ envID: "xxx", // 云开发环境Id }); ``` 在 agent_ui/dist/xxx/index.js 组件头部添加引用 `import '../../index'`,使组件在加载时立即初始化 sdk。 1. 在主包中初始化,需要保证组件使用在初始化完成之后。 在`agent_ui` 目录下创建 `index.js` 文件: ```js export * as ClientSDK from "@cloudbase/weda-client"; ``` 在 app.js 中,onLauch 生命周期内,异步初始化 sdk ```js require.async("./agent_ui/index").then((module) => { module.ClientSDK.init({ envID: "xxx", // 云开发环境Id }); }); ``` #### 4. 在小程序中使用组件 由于使用了分包,引用组件的方式有一些不同。 1. 在 page `index.json` 中引用组件,由于分包异步化,需要配置 componentPlaceholder: ```json { "usingComponents": { "chat": "/agent-ui/dist/Agent-UI/index" }, "componentPlaceholder": { "chat": "block" } } ``` 1. 在 `index.wxml` 中使用组件,注意组件外层需要有 id 为 `wd-page-root` 的组件 ```wxml ``` ### 小程序合法域名处理 如在微搭低代码和小程序中使用 agent ui,发布小程序后,提示`request 合法域名校验出错`,需要前往小程序管理后台配置合法域名。 ![](https://qcloudimg.tencent-cloud.cn/raw/99d63803eeb965a8acd553b533294094.png) 将报错的域名添加到合法域名列表中。 > 如果是授权给微搭的小程序,提示禁用域名配置,可以先在微搭解绑小程序,配置完合法域名后,在绑定到微搭。 ![](https://qcloudimg.tencent-cloud.cn/raw/2f5a59a0e1363d2e5df606613a6a834d.png) ## 属性 区块接收外部传入的属性。 | 属性名称 | 属性标识 | 类型 | 描述 | | --------- | -------- | ------ | -------------- | | 智能体 ID | `botId` | String | 必填,Agent ID | ## 实现细节 Agent UI 区块组件基于可视化技术搭建,为了便于开发者自定义,这里详细描述其中的实现细节,便于开发者自行调整逻辑。 使用 Agent UI 只需配置 Agent ID ,配置 Agent ID 后,Agent UI 会自动加载 Agent ID 对应的 Agent 配置,Agent 加载和会话流程如下。 ### 内部数据交互 初始化 Agent 阶段: - 查询 Agent ID 对应的 Agent 信息 - 加载 Agent 聊天记录 - 初始化 Agent 会话 会话阶段: - 发起 Agent 会话 - 流式加载 Agent 响应 - 查询建议问题 ### 区块 query 和事件说明 | 名称 | 描述 | | --------------------------------------- | ---------------------------------------- | | $w.queryBotById | 根据 Agent UI 配置的 ID 来查 Agent | | $w.queryChatRecords | 查询聊天记录,在 queryBotById 完成后调用 | | $w.sendMessage | 发送消息 | | $w.page.handler.queryRecommendQuestions | 查询推荐问题 | | $w.submitFeedback | 提交用户反馈 | --- # AI/Agent 开发/Agent UI 组件/Agent UI 自定义输出结构 > 当前文档链接: https://docs.cloudbase.net/ai/agent/custom-output ## 概述 希望 **Agent** 返回值中带有一些可视化组件进行操作,此时可以调整 **Agent UI** 组件支持HTML渲染,以及提示AI进行HTML格式返回 在此之前您需要先创建一个 **Agent**,若您还没开始创建,可以参照这里快速开始 [快速开始](/ai/agent-development) ## 示例 ## 操作步骤 ### 1. 修改 **Agent** 提示词 提示词示例: ``` 当我提问 "查询医保时", 通过html标签用一个卡片样式返回医保查询链接 "http://localhost:3000/ai/agent/custom-output" ``` 此时进行测试,返回结果中会带上一个卡片样式的HTML标签 ![返回结果](https://qcloudimg.tencent-cloud.cn/raw/2a69418393411f7c4b3594209fa5c2e5.png) ### 2. 修改 **Agent UI** 组件支持HTML渲染 进入 **可视化开发/应用** 模块,找到 **Agent UI** 组件,左侧大纲树中查询 **markdown**,找到 **markdown** 组件,点击 **属性**,在 **属性** 中找到 **html**,设置为 **true** ![](https://qcloudimg.tencent-cloud.cn/raw/6321efd4a124421d3c2d71fa8e6c2917.png) 此时在可视化中进行测试,返回结果的html标签会被渲染成真实效果 ![](https://qcloudimg.tencent-cloud.cn/raw/e68eb9d6c0ec56675b67c4dbbe6db0ac.png) ## 进阶配置 上述样式示例为 AI 自行产生,若想对样式做定制的话,建议直接提供样式模版给 AI 进行输出 提示词示例: ``` 提供以下样式模版 """
title
description
link
""" 提供以下问题列表,当客户命中问题时返回样式模版并替换模版中的 title、description、link 等字段 | 问题 | title | description | link | | --- | --- | --- | --- | | 查询医保 | 医保查询 | 医保查询链接 | http://localhost:3000/ai/agent/custom-output | ``` 上述 **HTML模版** 也可以用AI进行生成 示例: --- # AI/Agent 开发/Agent UI 组件/自定义 Artifact 解析 > 当前文档链接: https://docs.cloudbase.net/ai/agent/artifact 通过 Artifact,用户可以轻松创建和管理多种类型的内容,如代码架构图、流程图、网页设计、SVG 图形和交互式组件。Artifact 特别适合开发人员、设计师、产品经理和营销人员,用于将创意快速转化为实际产品。 :::tip 当前**[Agent UI React 组件](../agent-ui/agent-ui-react)** 已支持自定义 Artifact 能力 ::: ## 效果 ### Web 应用 Artifact **源代码** **应用预览** ### Mermaid 图表 Artifact **预览** ## 如何使用 [参考完整代码示例](https://gitee.com/TencentCloudBase/cloudbase-agent-ui/tree/main/apps/react-agent-ui) ### 1. 配置规范 Prompt > artifact 解析依赖大模型输出规范的 artifact 内容结构,可通过 prompt 约束生成 可参考如下 Prompt 示例,定义 `code` 与 `mermaid` 两种 Artifact,分别处理 `Web 前端应用` 与 `Mermaid 图表` 两类 Artifact。 当需要扩展其他类型 Artifact 时,参考实现即可(遵循 cloudbaseArtifact 标签格式)。 ``` 你是 CloudBase 的 AI 助手,负责为用户生成各种类型的内容。 当需要生成专业内容时,请使用如下格式: 支持的内容类型: 1. 用于 HTML/CSS/JS 应用 (type="code"): 2. 用于 Mermaid 流程图 (type="mermaid"): flowchart TD A[Start] --> B{Decision} B -->|Yes| C[Action] B -->|No| D[Another Action] 重要说明: - 必须提供完整且自包含的内容 - 对于 HTML 应用,所有 CSS 和 JavaScript 必须包含在同一个文件中 - 外部库请使用 CDN 链接,不要使用 npm 包 - Mermaid 流程图必须遵循正确语法 - 每个 artifact 必须有唯一的 ID、描述性标题和合适的 type - 不要使用 Fenced Code Blocks 包裹 artifact 内容 Create a simple counter application Here's a simple counter application: Simple Counter

Counter App

0
Create a flowchart showing a login process Here's a flowchart showing a typical login process: flowchart TD A[开始] --> B[步骤一] B --> C[步骤二] C --> D[结束]
``` ### 2. 实现 Artifact 组件 前端 Code Artifact 组件示例代码段 ```JSX export const CodeArtifactComponent: ArtifactComponent = ({ artifact }) => { const [isPreviewMode, setIsPreviewMode] = useState(false); const handlePreviewToggle = () => { setIsPreviewMode(!isPreviewMode); }; return (

{artifact.title}

{isPreviewMode ? (