# 腾讯云开发 CloudBase - 一站式后端云服务 > 腾讯云开发(Tencent CloudBase)是云端一体化的后端云服务 ,采用 serverless 架构,免去了移动应用构建中繁琐的服务器搭建和运维。同时云开发提供的静态托管、命令行工具(CLI)、Flutter SDK 等能力极大的降低了应用开发的门槛。使用云开发可以快速构建完整的小程序/小游戏、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 **CloudBase 文档型数据库** 支持灵活的 JSON 文档存储,无需预定义数据结构、支持任意结构的 JSON 数据、支持多文档事务操作,并提供 SDK 进行数据操作。 ## 如何创建 前往 [云开发平台/文档型数据库](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/webv2/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/webv2/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 CloudBase 提供了 **MySQL 数据库** 服务,支持完整的 SQL 功能,为开发者提供稳定可靠的数据存储解决方案。 ## 如何创建 ### 初始化数据库 访问 [云开发平台/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/webv2/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/webv2/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/webv2/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/webv2/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目前不支持事务操作。如需使用事务功能,建议通过云托管直接调用MySQL原生API来实现。 > 💡 注意:使用原生MySQL API时,需要自行管理数据库连接和事务状态。 #### 实现示例 ```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/webv2/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` - 数据删除 > ⚠️ 注意:不支持事务(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/webv2/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/webv2/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) 为您的应用提供完整的用户身份管理和访问控制解决方案。通过内置的多种登录方式和安全机制,帮助您快速构建可靠的用户体系。 云开发登录认证对用户端发起的每个请求都会进行身份验证和权限检查,有效防止资源被恶意访问或盗用。 ## 核心能力 云开发身份认证体系包含两个核心部分: ### 身份认证 解决「用户是谁」的问题,支持多种登录方式: - **匿名登录**:快速体验,无需注册 - **手机号登录**:短信验证码快速登录 - **邮箱登录**:邮箱 + 密码传统方式 - **用户名密码登录**:适用于传统应用 - **微信授权登录**:接入微信生态 - **自定义登录**:与现有账号体系集成 详细配置请参考 [管理登录方式](/authentication-v2/auth/manage-login)。 ### 权限控制 解决「用户能调用什么资源」的问题,通过角色和策略精细化管理资源访问权限: - **角色管理**:为不同类型的用户分配不同的角色(组织成员、注册用户、匿名用户、自定义角色等) - **策略配置**:为角色配置具体的资源访问权限(数据库、云函数、云存储等) - **成员管理**:管理用户的角色分配和权限变更 详细配置请参考 [权限控制](/authentication-v2/auth/auth-control)。 ## 支持的登录方式 云开发提供多种登录方式,您可以根据业务场景选择合适的方案: | 登录方式 | 适用场景 | 特点 | 文档链接 | | --- | --- | --- | --- | | 匿名登录 | 快速体验、临时用户 | 无需注册,自动生成临时身份 | [查看文档](/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)。 ::: ## 用户账号体系 ### 账号唯一标识 (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) | ### 用户类型说明 **组织用户**:适用于组织内部成员,如企业员工、团队成员等。组织用户可以分配自定义角色,拥有更细粒度的权限控制。在 [云开发平台/身份认证/组织架构](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/reference/store-extra-user-info) - 如何使用自定义用户表存储业务相关的扩展信息 - [为注册用户赋予管理员权限](/authentication-v2/auth/reference/grant-admin-permission) - 如何将注册用户转为组织成员并分配管理员角色 ## 相关文档 - [权限控制](/authentication-v2/auth/auth-control) - 角色体系和权限配置 - [用户注册](/authentication-v2/method/signup) - 用户注册 - [最佳实践](/authentication-v2/auth/best-practice) - 身份认证和权限管理最佳实践 --- # 身份认证/用户/权限/权限控制 > 当前文档链接: https://docs.cloudbase.net/authentication-v2/auth/auth-control 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) - 关系型数据库权限管理 **资源权限相关:** - [云函数权限控制](/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) 进行权限控制, - 平台默认策略可能会根据实际情况进行调整。 ::: 通过 [HTTP 访问服务](/service/introduce) 为云资源配置访问路径后,如路径启用了[身份认证](/service/authentication),携带云开发用户身份访问资源时会受到网关权限控制。 **启用身份认证后的默认权限:** | 资源类型 | 资源标识 | 管理员 | 组织成员 | 注册用户 | 匿名用户 | | ---------------------------------------------- | ----------- | :---: | :------: | :------: | :------: | | [云函数](/service/access-cloud-function) | `functions` | ✅ | ✅ | ✅ | ❌ | | [静态托管存储](/service/access-static-hosting) | `storages` | ✅ | ✅ | ✅ | ❌ | | [云托管](/service/access-cloudrun) | `cloudrun` | ✅ | ✅ | ✅ | ❌ | :::info 提示 - ✅ 表示该角色具备该功能的默认权限,❌ 表示该角色不具备该功能的默认权限,如需调整权限,请参考下文中的方法为对应角色关联策略。 - 平台默认策略可能会根据实际情况进行调整。 ::: ### 配置入口 进入 [云开发平台/权限控制](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. **域名**(可选):实际访问的域名,省略时表示 `*`(所有域名)
3. **HTTP方法**(可选):大写的 HTTP 请求方法(如 `GET`、`POST`、`PUT`、`DELETE`),省略时表示 `*`(所有方法)
4. **请求路径**(必填):HTTP 请求路径,使用 `*` 通配符匹配。`*` 表示所有路径,`/path/*` 表示 `/path/` 下的所有路径

**示例:**
- `functions:*` → 允许所有云函数访问(省略域名和方法)
- `functions:/hello` → 仅允许访问路径 `/hello`
- `functions:/api/*` → 允许访问 `/api/` 下的所有路径(如 `/api/users`、`/api/orders`)
- `cloudrun:env-xxxx.api.tcloudbasegateway.com: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": "*" } ] } ``` 允许访问所有云函数,但禁止访问特定敏感函数(`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** 双令牌机制实现用户会话管理。您可以灵活配置令牌过期策略和会话并发数,在安全性与用户体验之间取得最佳平衡。 ## 配置参数说明 | **参数** | **默认值** | **可配置范围** | **说明** | | :----------------------: | :--------: | :------------: | :-------------------------------------------------------------------------------: | | **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) --- # 身份认证/其他参考/账户关联登录 > 当前文档链接: 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/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/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 CloudBase 云存储提供稳定、安全、低成本、简单易用的云端存储服务,支持任意数量和形式的非结构化数据存储,如图片、文档、音频、视频、文件等。 您可以通过控制台、CloudBase CLI、HTTP API、SDK 等方式将文件上传到云端存储空间内,并对云端文件进行**文件管理**、**权限管理**和**缓存设置**等。 ## 管理方式 CloudBase 云存储提供多种管理方式,您可以根据自己的需求选择最适合的方式: | 管理方式 | 特点 | 适用场景 | 优势 | | --- | --- | --- | --- | | [**控制台管理**](/storage/manage) | 图形化界面,可配置权限、缓存设置 | 日常管理 | 上手快速,可视化操作 | | [**SDK 管理**](/storage/sdk) | 可集成到应用中 | 应用程序集成 | 灵活性高,可定制化程度高,适合集成到应用中 | | [**CLI 工具管理**](/cli-v1/storage) | 命令行操作,支持批量处理 | 自动化脚本、批量处理 | 高效批量操作,适合自动化部署 | | [**cosBrowser**](/storage/cosbrowser) | 图形化界面,支持批量处理 | 频繁文件管理 | 结合了图形界面的便捷性和批量操作的高效性 | 根据您的具体需求和使用场景,选择合适的管理方式可以显著提高工作效率。例如,开发阶段可以使用 SDK,运维阶段可以使用 CLI 工具,而日常管理则可以使用控制台或 cosBrowser。 ## 主要特性 ### 默认支持 CDN 加速 云存储无需进行繁杂的配置,默认支持 CDN 加速,并提供免费的 CDN 域名。CDN 会将云存储的内容分发至最接近用户的节点,直接由服务节点快速响应,可以有效降低用户访问延迟。 ### 身份验证集成 CloudBase 云存储与 CloudBase 用户身份验证无缝集成,您可以结合用户身份认证和安全规则对云存储里的文件设置访问权限,例如云存储的文件只对已登录用户公开或仅限使用微信登录的用户访问等。 ### 高可扩展性 CloudBase 云存储可以与 CloudBase 其它多种服务配合使用,例如: * 将文件的 fileID 存储到数据库; * 配合 CloudBase 扩展能力使用,如图像安全审核、图像处理、图像标签、图像盲水印等。 --- # 云存储/文件管理/控制台管理文件 > 当前文档链接: 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 云存储提供了完整的文件管理功能,包括上传、下载、删除、复制文件以及获取临时链接等操作。本文将详细介绍如何使用这些功能 | SDK 类型 | 适用平台 | | ------------------------------------------------------------------------------------------------------------ | ------------ | | [小程序 SDK](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/guide/storage/api.html) | 小程序 | | [JS SDK](/api-reference/webv2/storage) | Web 浏览器 | | [Node SDK](/api-reference/server/node-sdk/storage) | Node.js 环境 | | [HTTP API](http-api/storage/云存储) | 通用 | ## 上传文件 您可以上传任意数量、格式的文件至 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 为文件夹时,会自动上传文件内的所有文件。 ```sh tcb storage upload localPath cloudPath ``` ## 下载文件 您可以使用下面的命令下载文件/文件夹,需要下载文件夹时,需要指定 `--dir` 参数。 ```sh # 下载文件 tcb storage download cloudPath localPath # 下载文件夹 tcb storage download cloudPath localPath --dir ``` ### 下载全部文件 当指定 cloudPath 为 `/` 时,即代表下载云存储中的全部文件 ```sh # 下载文件夹 tcb storage download / localPath --dir ``` ## 删除文件 您可以使用下面的命令删除云端文件/文件夹,需要删除文件夹时,需要指定 `--dir` 参数。 ```sh # 删除文件 tcb storage delete cloudPath # 删除文件夹 tcb storage delete cloudPath --dir ``` ### 删除全部文件 云端路径为空时,即代表删除云存储中的全部文件 ```sh tcb storage delete ``` ## 列出文件列表 您可以使用下面的命令列出文件夹下的文件 ```sh tcb storage list cloudPath ``` ## 获取文件访问链接 您可以使用下面的命令获取文件的临时访问链接 ```sh tcb storage url cloudPath ``` ## 获取文件简单信息 您可以使用下面的命令获取文件的简单信息 ```sh tcb storage detail cloudPath ``` ## 获取文件访问权限 您可以使用下面的命令获取文件的访问权限设置信息 ```sh tcb storage get-acl ``` ## 设置文件访问权限 您可以使用下面的命令设置文件的访问权限 ```sh tcb storage set-acl ``` --- # 云存储/文件管理/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/data-permission CloudBase 提供了多层次的存储权限管理机制,确保文件安全的同时满足不同业务场景的权限控制需求。 云存储进行读写时会以 `_openid` 字段作为文件归属判定依据 ## 权限管理体系 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 云存储的 **文件级权限控制** 功能,通过自定义表达式精确控制每个文件的读写权限。 ## 配置入口 在 [云开发平台/云存储](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) --- # 云存储/高级功能/迁移数据到云存储 > 当前文档链接: 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) --- # 云存储/高级功能/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/faq ## 云存储文件突然不能访问了 当遇到云存储文件无法访问的情况时,可以按照以下步骤进行问题定位和解决: 1. **检查云存储权限配置**: 确保云存储的权限配置正确,特别是是否设置了公有读权限。错误的权限设置可能导致文件无法被正确访问。 2. **检查云开发套餐的 CDN 流量**: 前往云开发控制台,查看当前云开发套餐的 CDN 流量使用情况。如果 CDN 流量已用完,可能会导致文件访问受限。 3. **开启超限按量计费**: 如果 CDN 流量确实已用完,可以通过以下路径开启超限按量计费,以继续使用云存储服务: - 微信开发者工具 -> 云开发控制台 -> 设置 -> 环境设置 -> 按量计费 按照以上步骤操作后,云存储文件访问问题通常可以得到解决。如果问题依旧存在,建议查阅微信云开发官方文档或联系技术支持获取进一步帮助。 ## 云存储文件误删如何恢复 如果不小心删除了云存储中的文件,可以通过腾讯云 COS(对象存储)的回收站功能尝试恢复。 ### 前提条件 云存储的回收站功能需要**提前开启**才能使用。如果删除文件前未开启回收站功能,则无法恢复已删除的文件。 ### 恢复步骤 #### 1. 进入 COS 存储桶 1. 登录 [云开发控制台](https://console.cloud.tencent.com/tcb) 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: 概述 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 或更高版本) - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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.ur.split('?')[0] === "/") { res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" }); res.end("Hello World!"); } else if (req.ur.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.ur.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/) 或更高版本 - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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 或更高版本) - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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/) - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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 或更高版本) - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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://console.cloud.tencent.com/tcb) 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 会与同一命名空间下其他开启此功能的函数共享。 :::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|{host id}| |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` 命令可以快速部署云函数到云开发环境。支持部署普通云函数和 HTTP 函数(Web 函数)两种类型。 ## 基本用法 ### 方式一:使用配置文件 在包含 `cloudbaserc.json` 配置文件的项目目录下,执行以下命令: ```bash # 部署指定的云函数 tcb fn deploy # 部署配置文件中的所有云函数 tcb fn deploy # 部署为 HTTP 函数 tcb fn deploy --httpFn ``` ### 方式二:从当前目录部署 在函数代码目录下(包含 `package.json`),无需配置文件即可直接部署: ```bash cd my-function tcb fn deploy ``` CLI 会自动从 `package.json` 读取函数名称,并使用默认配置部署。如需部署为 HTTP 函数,添加 `--httpFn` 参数: ```bash tcb fn deploy --httpFn ``` ## 命令参数 ```bash tcb fn deploy [options] [name] ``` | 参数 | 说明 | 必填 | | ---------------------------- | ----------------------------------------------------------------------------- | ---- | | `-e, --envId ` | 环境 Id | 否 | | `--httpFn` | 部署为 HTTP 云函数 | 否 | | `--ws` | 部署 HTTP 云函数时启用 WebSocket 协议 | 否 | | `--code-secret ` | 传入此参数将保护代码,格式为 36 位大小写字母和数字 | 否 | | `--force` | 如果存在同名函数,上传后覆盖同名函数 | 否 | | `--path ` | 自动创建 HTTP 访问服务访问路径 | 否 | | `--dir ` | 指定云函数的文件夹路径 | 否 | | `--all` | 部署配置文件中包含的全部云函数 | 否 | | `--deployMode ` | 代码上传方式:`cos` 或 `zip`,默认 `cos`(zip 方式限制 1.5 MB) | 否 | | `-h, --help` | 查看命令帮助信息 | 否 | ## 使用示例 ```bash # 部署 app 函数 tcb fn deploy app # 部署所有函数 tcb fn deploy # 指定环境部署 tcb fn deploy app -e your-env-id ``` ### 代码加密 通过 `--code-secret` 参数对代码进行加密,密钥需要使用 36 位大小写字母和数字组成: ```bash tcb fn deploy app --code-secret 7sGLwMnhgEfKmkqg2dMjB6xWk2hCxsAgGR6w ``` :::warning 注意 启用代码加密后,将无法在小程序 IDE、腾讯云控制台中查看云函数的代码和信息。 ::: ### 覆盖同名函数 如果云端已存在同名函数,CLI 会提示是否覆盖。如需强制覆盖,可使用 `--force` 参数: ```bash tcb fn deploy app --force ``` 指定函数目录部署: ```bash tcb fn deploy app --dir ./functions/app ``` :::caution 重要提示 使用 `--force` 参数覆盖函数时,会同时覆盖函数的配置和触发器。 ::: ## 配置文件示例 ```json { "envId": "your-env-id", "functionRoot": "./functions", "functions": [ { "name": "app", "timeout": 5, "runtime": "Nodejs18.15", "installDependency": true, "handler": "index.main" }, { "name": "webFunction", "type": "HTTP", "timeout": 60, "runtime": "Nodejs18.15", "memorySize": 256, "envVariables": { "NODE_ENV": "production" } } ] } ``` :::tip 说明 HTTP 函数需将 `type` 设置为 `HTTP` ::: ## 默认配置 对于 Node.js 云函数,CLI 提供了默认配置,无需手动配置即可部署: ```json { "timeout": 5, "runtime": "Nodejs18.15", "installDependency": true, "handler": "index.main", "ignore": ["node_modules", "node_modules/**/*", ".git"] } ``` ## 部署流程 执行 `tcb fn deploy` 命令时,CLI 会自动完成以下操作: 1. **打包上传**:将函数代码打包为压缩文件并上传 2. **配置更新**:更新函数配置(超时时间、内存、环境变量、网络配置等) 3. **触发器部署**:根据配置文件部署或更新触发器 ## HTTP 函数 HTTP 函数是专门针对 Web 服务场景优化的云函数类型,支持标准 HTTP 请求响应模式。 ### 部署方式 #### 方式一:使用配置文件 在 `cloudbaserc.json` 中将 `type` 设置为 `HTTP`,然后执行: ```bash tcb fn deploy webFunction ``` #### 方式二:使用命令行参数 ```bash tcb fn deploy --httpFn ``` #### 方式三:从当前目录部署 在函数代码目录下(包含 `package.json`),无需配置文件即可直接部署: ```bash cd my-web-function tcb fn deploy --httpFn ``` CLI 会自动从 `package.json` 读取函数名称,并使用默认配置部署。 ### 启动脚本 HTTP 函数需要 `scf_bootstrap` 启动脚本来启动 Web Server。如果未创建,CLI 会提示自动生成。 #### 基本要求 | 要求项 | 说明 | | -------- | ------------------------------------------------------ | | 文件名称 | 必须命名为 `scf_bootstrap`,云函数仅识别该名称 | | 文件权限 | 必须具备可执行权限(`755` 或 `777`) | | 脚本格式 | 第一行必须包含 `#!/bin/bash`,文件结尾必须以 `LF` 结束 | | 路径要求 | 启动命令必须使用**绝对路径** | | 监听地址 | 使用 `0.0.0.0`,不可使用 `127.0.0.1` | | 监听端口 | Web Server 需监听 `9000` 端口 | #### 示例模板 ```bash #!/bin/bash export PORT=9000 /var/lang/node18/bin/node index.js ``` ```bash #!/bin/bash export PORT=9000 /var/lang/python39/bin/python3.9 app.py ``` ```bash #!/bin/bash /var/lang/php80/bin/php -c /var/runtime/php8 -S 0.0.0.0:9000 index.php ``` #### 支持的运行时 启动命令必须使用绝对路径,否则无法正常调用。 | 运行时 | 启动脚本路径 | | ------------- | ------------------------------------ | | Node.js 20.19 | `/var/lang/node20/bin/node` | | 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` | | Node.js 10.15 | `/var/lang/node10/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` | | PHP 8.0 | `/var/lang/php80/bin/php` | | PHP 7.4 | `/var/lang/php74/bin/php` | | Java 11 | `/var/lang/java11/bin/java` | | Java 8 | `/var/lang/java8/bin/java` | #### 设置权限 ```bash chmod 755 scf_bootstrap ``` :::tip 注意 在 Windows 系统中编辑的文件可能使用 `CRLF` 换行符,需转换为 `LF` 格式,否则会导致启动失败。 ::: :::warning 常见错误 如果调用函数时返回 **405 错误**,通常表示 `scf_bootstrap` 无法正常运行。请检查: - 启动命令路径是否正确 - 文件权限是否为可执行 - 换行符是否为 `LF` 格式 ::: ### 项目结构 ``` my-web-function/ ├── scf_bootstrap # 启动脚本(必需) ├── package.json # 项目配置及依赖声明 ├── cloudbaserc.json # CLI 配置文件(可选) └── index.js # 函数代码 ``` ``` my-python-function/ ├── scf_bootstrap # 启动脚本(必需) ├── requirements.txt # Python 依赖声明 ├── cloudbaserc.json # CLI 配置文件(可选) └── app.py # 函数代码 ``` ### 函数调用 CloudBase 云函数支持多种调用方式,满足不同场景和平台的需求。您可以根据实际情况选择最适合的调用方式。详情请参考 [云函数调用](https://docs.cloudbase.net/cloud-function/function-calls/)。 ## 注意事项 :::tip 上传方式 默认使用 COS 上传,可通过 `--deployMode zip` 指定 ZIP 上传(限制 1.5 MB)。 ::: :::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) - [Access Token 获取](/http-api/basic/access-token) - [云函数配置](/cli-v1/functions/configs) --- # 云函数/云函数管理/测试云函数 > 当前文档链接: 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 ``` ```bash #!/bin/bash export PORT=9000 /var/lang/python3/bin/python3 app.py ``` 启动脚本详情请参考:[启动文件说明](/cloud-function/develop/scf-bootstrap) ### 项目结构 完整的项目目录结构如下: ``` my-web-function/ ├── scf_bootstrap # 启动脚本(必需,无扩展名) ├── package.json # 项目配置 ├── index.js # 函数入口文件 └── node_modules/ # 依赖包(npm install 后生成) ``` **参考资源**: - 更多示例:[示例代码仓库](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)。 ## 函数结构与参数 ### 基本结构 ```js exports.main = function (event, context) { // 函数逻辑 }; ``` 或使用异步函数: ```js exports.main = async function (event, context) { // 异步函数逻辑 }; ``` > 💡 **类型定义支持**:云开发提供 `@cloudbase/functions-typings@v-1` 类型定义包,辅助 TypeScript 代码编写。详见 [使用 TypeScript 编写函数](#使用-typescript-编写函数代码) 章节。 ### event 参数 `event` 参数包含 HTTP 请求数据,内容根据请求类型不同而变化: - `POST` 请求:请求体(body)内容 - `multipart/form-data`:表单数据 - `PUT` 请求:上传的文件 - 无请求体时:空对象 `{}` ### context 参数 `context` 参数提供函数执行的上下文信息: | 属性/方法 | 类型 | 说明 | | ----------------- | ------------------------- | -------------------------------- | | `eventID` | `string` | 事件唯一标识,用于关联请求上下文 | | `eventType` | `string` | 事件类型,固定为 `http` | | `timestamp` | `number` | 请求时间戳 | | `httpContext` | `httpBasis` | HTTP 请求的相关信息 | | `extendedContext` | `Record` | 扩展上下文信息(环境信息等) | **httpBasis 接口**: | 属性 | 类型 | 说明 | | ------------ | --------------------- | ----------------------------- | | `url` | `string` | 本次请求的完整 URL | | `httpMethod` | `string` | HTTP 方法(如 `GET`、`POST`) | | `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 端用户是否为管理员 accessToken?: string; // 调用请求时的 AccessToken userId?: string; // 请求的用户 ID tmpSecret?: { // 临时凭证 secretId: string; secretKey: string; token: string; }; wechatContext?: { // 微信上下文信息 callId: string; // 微信调用 ID source: string; // 请求来源 appId: string; // 小程序 AppID openId: string; // 用户 OpenID unionId: string; // 用户 UnionID fromOpenId?: string; // 环境资源共享时的 fromOpenID fromUnionId?: string; // 环境资源共享时的 fromUnionID fromAppId?: string; // 环境资源共享时的 fromAppID }; } ``` ## 函数路由 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 云函数,包括 SDK 调用、HTTP API 调用、Web 客户端调用等 keywords: [云函数调用, SDK, HTTP API, Web 客户端, 小程序调用] --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import DocCardList from '@theme/DocCardList'; # 云函数调用方式 CloudBase 云函数支持多种调用方式,满足不同场景和平台的需求。您可以根据实际情况选择最适合的调用方式。 ## 调用方式概览 | 调用方式 | 适用场景 | 特点 | | ------------------------ | -------------------------- | --------------------------- | | **HTTP API** | 跨语言调用、第三方系统集成 | 标准 REST API,支持所有语言 | | **Web 客户端** | 浏览器环境、前端应用 | 支持 CORS,直接 HTTP 访问 | | **SDK 调用**(敬请期待) | 小程序、Web 应用、移动应用 | 简单易用,自动处理认证 | | **小程序调用**(敬请期待) | 微信小程序 | 原生支持,无需额外配置 | 通过 HTTP API 调用云函数支持跨语言访问,适合第三方系统集成。 ### 获取访问令牌 访问令牌的获取方式请参考 [AccessToken 文档](/http-api/basic/access-token)。 ### API 调用格式 **请求 URL:** ``` POST https://{env-id}.api.tcloudbasegateway.com/v1/functions/{function-name}?webfn=true ``` > ⚠️ 注意:调用 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 认证 | | `Content-Type` | string | 是 | 请求内容类型,通常为 `application/json` | | `X-Qualifier` | string | 否 | 指定调用函数的版本 | HTTP 云函数支持通过自定义域名进行标准的 HTTP 调用,适合浏览器环境和前端应用。 ### 前提条件 - 创建了 HTTP 云函数 - 配置了 HTTP 访问服务和自定义域名 - 获取了函数的访问 URL ### 配置 HTTP 访问服务 1. **创建 HTTP 云函数**:在云开发控制台创建一个新的 HTTP 云函数 2. **进入函数详情**:函数创建成功后,点击函数名称进入详情页面 3. **配置访问路径**:在函数配置页面找到「HTTP 访问服务路径」选项,点击「去设定」 4. **设置域名和路径**:在 HTTP 访问服务页面配置自定义域名和访问路径 > 💡 **提示**:详细的 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` 命令可以快速部署云函数到云开发环境。支持部署普通云函数和 HTTP 函数(Web 函数)两种类型。 ## 基本用法 ### 方式一:使用配置文件 在包含 `cloudbaserc.json` 配置文件的项目目录下,执行以下命令: ```bash # 部署指定的云函数 tcb fn deploy # 部署配置文件中的所有云函数 tcb fn deploy # 部署为 HTTP 函数 tcb fn deploy --httpFn ``` ### 方式二:从当前目录部署 在函数代码目录下(包含 `package.json`),无需配置文件即可直接部署: ```bash cd my-function tcb fn deploy ``` CLI 会自动从 `package.json` 读取函数名称,并使用默认配置部署。如需部署为 HTTP 函数,添加 `--httpFn` 参数: ```bash tcb fn deploy --httpFn ``` ## 命令参数 ```bash tcb fn deploy [options] [name] ``` | 参数 | 说明 | 必填 | | ---------------------------- | ----------------------------------------------------------------------------- | ---- | | `-e, --envId ` | 环境 Id | 否 | | `--httpFn` | 部署为 HTTP 云函数 | 否 | | `--ws` | 部署 HTTP 云函数时启用 WebSocket 协议 | 否 | | `--code-secret ` | 传入此参数将保护代码,格式为 36 位大小写字母和数字 | 否 | | `--force` | 如果存在同名函数,上传后覆盖同名函数 | 否 | | `--path ` | 自动创建 HTTP 访问服务访问路径 | 否 | | `--dir ` | 指定云函数的文件夹路径 | 否 | | `--all` | 部署配置文件中包含的全部云函数 | 否 | | `--deployMode ` | 代码上传方式:`cos` 或 `zip`,默认 `cos`(zip 方式限制 1.5 MB) | 否 | | `-h, --help` | 查看命令帮助信息 | 否 | ## 使用示例 ```bash # 部署 app 函数 tcb fn deploy app # 部署所有函数 tcb fn deploy # 指定环境部署 tcb fn deploy app -e your-env-id ``` ### 代码加密 通过 `--code-secret` 参数对代码进行加密,密钥需要使用 36 位大小写字母和数字组成: ```bash tcb fn deploy app --code-secret 7sGLwMnhgEfKmkqg2dMjB6xWk2hCxsAgGR6w ``` :::warning 注意 启用代码加密后,将无法在小程序 IDE、腾讯云控制台中查看云函数的代码和信息。 ::: ### 覆盖同名函数 如果云端已存在同名函数,CLI 会提示是否覆盖。如需强制覆盖,可使用 `--force` 参数: ```bash tcb fn deploy app --force ``` 指定函数目录部署: ```bash tcb fn deploy app --dir ./functions/app ``` :::caution 重要提示 使用 `--force` 参数覆盖函数时,会同时覆盖函数的配置和触发器。 ::: ## 配置文件示例 ```json { "envId": "your-env-id", "functionRoot": "./functions", "functions": [ { "name": "app", "timeout": 5, "runtime": "Nodejs18.15", "installDependency": true, "handler": "index.main" }, { "name": "webFunction", "type": "HTTP", "timeout": 60, "runtime": "Nodejs18.15", "memorySize": 256, "envVariables": { "NODE_ENV": "production" } } ] } ``` :::tip 说明 HTTP 函数需将 `type` 设置为 `HTTP` ::: ## 默认配置 对于 Node.js 云函数,CLI 提供了默认配置,无需手动配置即可部署: ```json { "timeout": 5, "runtime": "Nodejs18.15", "installDependency": true, "handler": "index.main", "ignore": ["node_modules", "node_modules/**/*", ".git"] } ``` ## 部署流程 执行 `tcb fn deploy` 命令时,CLI 会自动完成以下操作: 1. **打包上传**:将函数代码打包为压缩文件并上传 2. **配置更新**:更新函数配置(超时时间、内存、环境变量、网络配置等) 3. **触发器部署**:根据配置文件部署或更新触发器 ## HTTP 函数 HTTP 函数是专门针对 Web 服务场景优化的云函数类型,支持标准 HTTP 请求响应模式。 ### 部署方式 #### 方式一:使用配置文件 在 `cloudbaserc.json` 中将 `type` 设置为 `HTTP`,然后执行: ```bash tcb fn deploy webFunction ``` #### 方式二:使用命令行参数 ```bash tcb fn deploy --httpFn ``` #### 方式三:从当前目录部署 在函数代码目录下(包含 `package.json`),无需配置文件即可直接部署: ```bash cd my-web-function tcb fn deploy --httpFn ``` CLI 会自动从 `package.json` 读取函数名称,并使用默认配置部署。 ### 启动脚本 HTTP 函数需要 `scf_bootstrap` 启动脚本来启动 Web Server。如果未创建,CLI 会提示自动生成。 #### 基本要求 | 要求项 | 说明 | | -------- | ------------------------------------------------------ | | 文件名称 | 必须命名为 `scf_bootstrap`,云函数仅识别该名称 | | 文件权限 | 必须具备可执行权限(`755` 或 `777`) | | 脚本格式 | 第一行必须包含 `#!/bin/bash`,文件结尾必须以 `LF` 结束 | | 路径要求 | 启动命令必须使用**绝对路径** | | 监听地址 | 使用 `0.0.0.0`,不可使用 `127.0.0.1` | | 监听端口 | Web Server 需监听 `9000` 端口 | #### 示例模板 ```bash #!/bin/bash export PORT=9000 /var/lang/node18/bin/node index.js ``` ```bash #!/bin/bash export PORT=9000 /var/lang/python39/bin/python3.9 app.py ``` ```bash #!/bin/bash /var/lang/php80/bin/php -c /var/runtime/php8 -S 0.0.0.0:9000 index.php ``` #### 支持的运行时 启动命令必须使用绝对路径,否则无法正常调用。 | 运行时 | 启动脚本路径 | | ------------- | ------------------------------------ | | Node.js 20.19 | `/var/lang/node20/bin/node` | | 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` | | Node.js 10.15 | `/var/lang/node10/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` | | PHP 8.0 | `/var/lang/php80/bin/php` | | PHP 7.4 | `/var/lang/php74/bin/php` | | Java 11 | `/var/lang/java11/bin/java` | | Java 8 | `/var/lang/java8/bin/java` | #### 设置权限 ```bash chmod 755 scf_bootstrap ``` :::tip 注意 在 Windows 系统中编辑的文件可能使用 `CRLF` 换行符,需转换为 `LF` 格式,否则会导致启动失败。 ::: :::warning 常见错误 如果调用函数时返回 **405 错误**,通常表示 `scf_bootstrap` 无法正常运行。请检查: - 启动命令路径是否正确 - 文件权限是否为可执行 - 换行符是否为 `LF` 格式 ::: ### 项目结构 ``` my-web-function/ ├── scf_bootstrap # 启动脚本(必需) ├── package.json # 项目配置及依赖声明 ├── cloudbaserc.json # CLI 配置文件(可选) └── index.js # 函数代码 ``` ``` my-python-function/ ├── scf_bootstrap # 启动脚本(必需) ├── requirements.txt # Python 依赖声明 ├── cloudbaserc.json # CLI 配置文件(可选) └── app.py # 函数代码 ``` ### 函数调用 CloudBase 云函数支持多种调用方式,满足不同场景和平台的需求。您可以根据实际情况选择最适合的调用方式。详情请参考 [云函数调用](https://docs.cloudbase.net/cloud-function/function-calls/)。 ## 注意事项 :::tip 上传方式 默认使用 COS 上传,可通过 `--deployMode zip` 指定 ZIP 上传(限制 1.5 MB)。 ::: :::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) - [Access Token 获取](/http-api/basic/access-token) - [云函数配置](/cli-v1/functions/configs) --- # 云函数/日志/监控/日志 > 当前文档链接: 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 调用 - HTTP 触发器 - 定时触发器 - 数据库触发器 ::: ## 基本语法 云函数安全规则配置在环境级别,环境内所有函数共享一个配置文件。配置采用 JSON 格式: ```json { "函数名或通配符": { "invoke": "表达式或布尔值" } } ``` ### 配置结构说明 | 配置项 | 说明 | 示例 | | ------------ | ---------------------------------------- | ---------------------- | | **顶级 key** | 函数名(具体函数)或 `*`(通配所有函数) | `"*"`、`"getUserInfo"` | | **操作 key** | 固定为 `invoke`,表示调用权限 | `"invoke"` | | **规则值** | `true`、`false` 或 `"auth != null"` | 见下方支持的规则值说明 | :::tip 必读 1. 安全规则顶级配置**必须**包含 key 为 `*` 的通配配置 2. 每个函数配置中**必须**包含 `invoke` 操作配置 3. 规则值**仅支持** `true`、`false` 或 `"auth != null"`,不支持其他表达式 4. 默认情况下,建议配置为 `"auth != null"`(仅登录用户可调用) ::: ### 匹配优先级 1. 优先匹配具体的函数名配置 2. 如果没有匹配到具体函数名,则使用 `*` 通配配置 ### 支持的规则值 云函数安全规则**仅支持以下三种规则值**,不支持其他复杂表达式(如 `&&`、`||`、属性访问等): | 规则值 | 说明 | 适用场景 | | ---------------- | ---------------------------------- | ------------------------------ | | `true` | 允许所有用户调用(包括未登录用户) | 公开接口(获取公告、配置等) | | `false` | 禁止所有用户调用 | 已废弃的函数、仅内部调用的函数 | | `"auth != null"` | 仅登录用户可调用 | 需要用户身份验证的业务函数 | **不支持的表达式示例**: - ❌ `"auth.uid != null"` - 不支持属性访问 - ❌ `"auth != null && auth.loginType == 'WECHAT'"` - 不支持逻辑运算符 - ❌ `"auth.openid != null || auth.uid != null"` - 不支持复杂条件 如需更精细的权限控制,请在云函数内部实现业务逻辑验证。 ### 常用模板 ```json { "*": { "invoke": "auth != null" } } ``` 所有函数默认要求用户登录后才能调用,适用于需要身份验证的业务系统。 ```json { "*": { "invoke": "auth != null" }, "getAnnouncement": { "invoke": true }, "adminFunction": { "invoke": false } } ``` - 默认要求登录 - `getAnnouncement` 公开访问 - `adminFunction` 完全禁止客户端调用 ```json { "*": { "invoke": true } } ``` > ⚠️ 警告:所有函数允许匿名调用,存在安全风险,仅适用于完全公开的接口。 ```json { "*": { "invoke": false } } ``` 所有函数禁止客户端调用,仅允许通过管理端 API、触发器等方式调用。 ## 常见问题 ### 可以针对不同用户设置不同权限吗? 安全规则仅支持 `true`、`false` 和 `"auth != null"` 三种规则值,**不支持基于用户属性、登录方式、角色等的复杂权限控制**。 如需更精细的权限控制(如基于角色、用户组、登录方式等),可以在云函数内部进行验证 ### 如何排查权限错误? 当遇到 `PERMISSION_DENIED` 错误时,请按以下步骤排查: 1. **检查安全规则配置**: - 前往 [云开发平台/云函数](https://tcb.cloud.tencent.com/dev?#/scf) 查看权限控制配置 2. **确认用户登录状态**: - 使用客户端 SDK 检查用户是否已登录 - 对于需要 `"auth != null"` 的函数,确保调用前已完成登录 3. **查看错误详情**: - 详细错误信息请参考 [PERMISSION_DENIED 错误码](/error-code/PERMISSION_DENIED) - 检查是否是调用方式问题(如使用了 HTTP 触发器而非 SDK `callFunction`) --- # 云函数/高级功能/定时触发器 > 当前文档链接: 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 访问 ### 步骤 1:创建域名关联 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/) 或更高版本 - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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://console.cloud.tencent.com/tcb) 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 ( Hero Image ); } ``` ### 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/) 或更高版本 - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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://console.cloud.tencent.com/tcb) 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/) 或更高版本 - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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://console.cloud.tencent.com/tcb) 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/) 或更高版本 - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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://console.cloud.tencent.com/tcb) 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/) - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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://console.cloud.tencent.com/tcb) 2. 选择您的环境,进入「云函数」页面 3. 点击「新建云函数」 4. 选择「HTTP 云函数」 5. 填写函数名称(如:`springboot-app`) 7. 选择运行时:**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/) 或更高版本 - 拥有腾讯云账号并开通了 CloudBase 服务 - 了解基本的 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://console.cloud.tencent.com/tcb) 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://console.cloud.tencent.com/tcb) 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 云托管(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 函数 | 函数型开发框架 | 云托管 ⭐ | |---------|---------|----------|--------------|----------| | **概述** | [事件函数](https://docs.cloudbase.net/cloud-function/introduce) | [HTTP 函数](https://docs.cloudbase.net/cloud-function/introduce) | [函数型开发框架](https://docs.cloudbase.net/cbrf/intro) | [云托管](https://docs.cloudbase.net/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 种运行模式](https://docs.cloudbase.net/run/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数据库](https://docs.cloudbase.net/run/develop/resource-integration/mysql) ### 🔗 私有网络(VPC) 通过配置 [VPC 网络](https://docs.cloudbase.net/run/deploy/networking/internal-link),云托管实例可以访问 VPC 内的各种资源: - **Redis**:缓存、会话存储 - **MySQL/PostgreSQL**:关系型数据库 - **Elasticsearch**:全文搜索 - **Kafka**:消息队列 - **其它腾讯云资源产品**:其它腾讯云资源产品 **配置步骤**: 1. 在服务设置中开启「私有网络」 2. 选择目标 VPC 和子网 3. 配置安全组规则 4. 重新部署服务生效 ### 🗄️调用云开发资源 在云托管中可以便捷地调用云开发的各种资源,包括数据库、云存储等。目前可以使用 HTTP API来集成调用,node-sdk 建设中 (敬请期待) ## 🚀 持续交付(CI/CD) 支持从代码仓库自动部署,实现 DevOps 最佳实践。 **支持的代码仓库**: - GitHub - GitLab - Gitee(码云) **工作流程**: ``` 代码推送 → 自动触发构建 → 运行测试 → 构建镜像 → 自动部署 ``` **配置方式**: 1. 在服务设置中绑定代码仓库 2. 选择分支和构建配置 3. 推送代码自动触发部署 支持基于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/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://` 替换为 `ws://` 或者 `wss://` 如:,在 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) 构建,您可以根据自己的技术栈选择相应的数据库驱动和连接方式。 ## 快速开始 ### 步骤 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 等)内网网络无法互通。如果需要需要访问其它资源,需要为服务开启内网互联功能。 ### 开通内网互联功能 开通前提: * 您需要[腾讯云私有网络](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)。 --- # 云托管/服务指南/服务网络/固定公网出口 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`,函数的返回值为 `字符串`。 - 更多函数示例可参考: - - - 模板代码可参考: - JavaScript: - TypeScript: - 如何调试函数代码可参考: - 当前代码为简单的函数示例,`云托管函数框架` 在框架层面支持 `单实例多函数及函数路由`,即多个函数跑在同一个函数实例上,具体可参考:[单实例多函数及函数路由](#单实例多函数及函数路由) ### 第二步:运行函数 函数代码编写完毕后,既可以将函数运行起来,之后便可以通过 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` 进行调用,可参考:。 4. 通过 `SDK` 进行调用 1. 服务端 `@cloudbase/node-sdk` 调用,可参考: 1. `callFunction`: 2. `callContainer`: 2. 浏览器 `@cloudbase/js-sdk` 调用,可参考: 1. `callFunction`: 2. `callContainer`: 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 [可能有] ``` 该项目结构对应源代码见: 以上,是一个基于 `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` 调用云函数的参考文档: * `@cloudbase/js-sdk` 调用云函数的参考文档: 实现对本地运行的云函数的调用,首先需要启动一个 `本地代理层`,将请求转发到本地运行的云函数。 #### 搭建 `本地代理层` 服务 `whistle` 是一个常用的跨平台的网络代理工具,可以实现将请求转发到本地。可以实现上图描述的 `本地代理层` 的功能,将请求转发到本地运行的云函数。 `whistle` 参考: 安装 `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 代理配置为 即可,可通过 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) ``` 完整示例项目代码可查看: #### 使用 `@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` 下载示例代码: 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` 下载示例代码: 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. 跨场景复用 在云托管的场景之外,您已经使用了腾讯云的镜像仓库,并希望里面的镜像继续用于云托管的某个服务。此时,在创建服务时,可以选择 “绑定已有腾讯云镜像仓库” ,指定任意您账户下的私人镜像仓库。 请注意,若如此做,则您通过云托管产生的镜像(手动上传或自动构建推送),可能也会影响您在其他场景下的镜像使用。 2. 容量限制 个人版容器镜像服务存在相关仓库数限制,当触碰到此限制且不方便进行个人版清理时,可以切换为使用已有的企业版仓库;企业版仓库相关限制可以查询具体镜像仓库说明。 ## 服务域名 - 公网域名:公网任意来源可以用来对服务进行 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) - [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) --- # 云托管/常见问题/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 官方模板快速创建新项目 - **Git 仓库部署**:支持Git个人仓库及第三方公开仓库 **支持的构建配置:** - 自定义安装命令和构建命令 - 选择 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,您可以轻松部署您的文件到 CloudBase。 ## 适用场景 静态网站托管适用于以下应用场景: - **单页应用(SPA)**:React、Vue、Angular 等前端框架构建的应用 - **静态网站**:个人博客、企业官网、产品展示页 - **文档站点**:VitePress、Docusaurus、Hexo 等生成的文档 - **静态资源**:图片、视频、字体等资源文件托管 ## 快速开始 1. [快速开始](/hosting/quick-start) - 5 分钟快速上手静态托管 2. [在线部署指南](/hosting/web-hosting) - 了解详细的部署方式和配置 3. [自定义域名](/service/custom-domain) - 绑定您的专属域名 --- # 静态网站托管/快速开始 > 当前文档链接: https://docs.cloudbase.net/hosting/quick-start 本文档将指导您快速上手云开发静态网站托管服务,通过控制台管理和部署您的静态网站文件。 ## 前置条件 在开始之前,请确保您已经: :::tip 说明 - 拥有腾讯云账号并完成实名认证 - 创建了云开发环境 ::: ## 第 1 步:前往静态网站托管 前往 [云开发控制台 - 静态网站托管](https://tcb.cloud.tencent.com/dev#/static-hosting) 静态网站托管控制台 ## 第 2 步:准备网站文件 创建一个简单的 HTML 文件作为示例,命名为 `index.html`: ```html 欢迎使用云开发静态网站托管

🎉 恭喜!

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

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

查看文档
``` :::info 说明 您也可以准备其他类型的文件: - CSS 样式文件 - JavaScript 脚本文件 - 图片、字体等静态资源 - 单页应用(SPA)构建产物 ::: ## 第 3 步:上传文件 ### 方式一:云开发平台上传 1. 在静态网站托管页面,点击「上传文件」按钮 2. 选择准备好的 `index.html` 文件 3. 点击「确定」完成上传 上传文件 ### 方式二:批量上传 如果您有多个文件需要上传: 1. 点击「上传文件夹」按钮 2. 选择包含网站文件的整个文件夹 3. 系统会保持原有的目录结构进行上传 ### 方式三:CLI 管理文件 使用 CloudBase CLI 工具,通过命令行方式管理和部署静态网站文件。 **安装 CLI 工具:** ```bash npm install -g @cloudbase/cli ``` **登录授权:** ```bash cloudbase login ``` **部署文件:** ```bash # 部署当前目录下的所有文件 cloudbase hosting deploy . -e <环境ID> # 部署指定文件 cloudbase hosting deploy index.html -e <环境ID> # 部署指定文件夹 cloudbase hosting deploy dist/ -e <环境ID> ``` **优势:** - ✅ 支持批量上传,速度更快 - ✅ 可自动化部署,集成 CI/CD - ✅ 提供更多管理命令(删除、列表等) - ✅ 适合开发者和团队协作 详细使用方法请参考 [CLI 静态托管命令](/cli-v1/hosting)。 :::warning 注意事项 - 单个文件大小限制为 50MB - 文件名不能包含特殊字符,建议使用英文、数字、下划线和连字符 - 上传会覆盖同名文件,请谨慎操作 ::: ## 第 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. 如果看到您上传的页面内容,说明部署成功 验证部署成功 ## 下一步 恭喜您成功部署了第一个静态网站!接下来您可以: - [配置自定义域名](/service/custom-domain) - 使用您自己的域名访问网站 - [在线部署指南](/hosting/web-hosting) - 了解更多部署方式和构建配置 - [CDN 加速配置](/hosting/cdn) - 优化网站访问速度 - [HTTPS 配置](/hosting/https) - 为网站启用安全连接 --- # 静态网站托管/应用部署/部署方式 > 当前文档链接: https://docs.cloudbase.net/hosting/web-hosting 静态网站托管支持三种部署方式:上传代码包/文件夹、从官方模板创建、Git 仓库部署。本文档介绍如何使用这些部署方式,配置构建命令、设置部署路径,以及常见前端框架的部署方案。 ## 部署入口 前往 [云开发控制台 - 静态网站托管](https://tcb.cloud.tencent.com/dev#/static-hosting) ![](https://qcloudimg.tencent-cloud.cn/raw/dbc68441c2f2627092ec1f8a4d474d8f.png) ## 部署方式 ### 方式一:上传代码包/文件夹 适用场景:本地已有项目代码,希望快速部署。 #### 上传代码包 **准备代码包:** 支持上传以下格式的代码包: - `.zip` 格式(推荐) **代码包要求:** - 代码包应包含完整的前端项目源代码 **部署步骤:** 1. 在「静态网站托管」页面,点击「新建部署」 2. 选择「上传代码包」 3. 点击「选择文件」,上传本地 ZIP 文件 4. 配置构建参数 [详见下文](./web-hosting-guide) 5. 点击「部署」 #### 上传文件夹 **部署步骤:** 1. 在「静态网站托管」页面,点击「新建部署」 2. 选择「上传文件夹」 3. 选择本地项目文件夹 4. 配置构建参数(详见下文) 5. 点击「开始部署」 :::tip 提示 上传代码包之前,可以先删除 `node_modules`、`.git` 等常见的非必要目录,避免上传文件过大,发生上传失败的情况。 ::: ### 方式二:从官方模板创建 适用场景:快速创建新项目,基于最佳实践模板开发。 **支持的官方模板:** - **React 模板**:基于 Create React App 创建的标准 React 项目 - **Vue 模板**:基于 Vue CLI 创建的标准 Vue 3 项目 **部署步骤:** 1. 在「静态网站托管」页面,点击「新建部署」 2. 选择「从模板创建」 3. 选择模板类型(React 或 Vue) 4. 配置构建参数 [详见下文](./web-hosting-guide) 5. 点击「部署」 **模板特点:** - ✅ 开箱即用,无需配置 - ✅ 集成最佳实践 - ✅ 自动配置构建命令 - ✅ 更新部署 ### 方式三: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. 点击「部署」 --- # 静态网站托管/应用部署/部署指南 > 当前文档链接: https://docs.cloudbase.net/hosting/web-hosting-guide 本指南介绍如何配置和部署前端项目到云开发静态托管服务。 部署配置 ## 一、配置说明 ### 1.1 基础配置 | 配置项 | 说明 | 示例 | 必填 | |--------|------|------|------| | 项目名称 | 自定义项目标识 | `my-project` | ✅ | | 项目框架 | 选择前端框架 | React、Vue、Angular、其他 | ✅ | | Node.js 版本 | 构建环境版本(16/18/20/22/24) | Node.js 18 | ✅ | ### 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 ``` --- ## 四、框架配置参考 ### 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": "./" // 子路径部署使用相对路径 } } } } } } ``` --- ## 五、部署路径配置 ### 根路径部署(`/`) **适用场景:** - 单一应用,独占整个静态托管环境 - 官网、产品主页等需要域名直接访问 - 访问地址:`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 项目,无需编译构建 - 静态文档站点 - 落地页、活动页面 - 已经构建完成的前端项目产物 ## 快速开始 ### 前置条件 - 拥有腾讯云账号并完成实名认证 - 创建了云开发环境 - 准备好静态网站文件(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/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 网络,为您的网站提供高性能、高可用的访问体验。 ## 前置条件 在使用 CLI 操作静态网站服务前,请确保: 1. **开通静态网站服务**:前往 [云开发控制台](https://console.cloud.tencent.com/tcb) 开通静态网站服务 2. **手动开通**:静态网站托管服务需要在环境创建后单独开通 :::tip 提示 目前静态网站托管功能仅在腾讯云云开发控制台支持,小程序 IDE 控制台暂不支持。 ::: ## 部署网站 ### 全量部署 使用 `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 - **文件数量**:无限制 - **网络优化**:如遇到大量文件上传报错 `{ message: 'socket hang up', code: 'ECONNRESET' }`,请先执行: ```bash export COS_SDK_KEEPALIVE=false ``` ### SPA 应用配置 :::caution Vue History 模式 使用 Vue Router 的 history 模式时,需要在 [静态网站控制台](https://console.cloud.tencent.com/tcb/hosting) 的设置页面配置错误页面为应用的入口页面(通常是 `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 ``` **示例:** ```bash # 删除根目录下的 index.html tcb hosting delete index.html -e envId # 删除 static 文件夹及其所有内容 tcb hosting delete static -e envId # 清空整个静态网站 tcb hosting delete -e envId ``` ## 路径说明 ### 路径格式 - **localPath**:本地文件或文件夹路径 - 格式:`目录/文件名` - 示例:`./index.js`、`static/css/index.css` - **cloudPath**:云端文件或文件夹的相对路径 - 格式:`目录/文件名`(相对于根目录) - 示例:`index.js`、`static/css/index.js` ### 跨平台注意事项 :::caution Windows 系统 - **localPath**:使用系统路径格式,通常使用 `\` 分隔符 - **cloudPath**:统一使用 `/` 分隔符,与操作系统无关 ::: ## 常见问题 ### 上传失败处理 如果遇到网络连接问题导致上传失败,可以尝试: ```bash # 关闭 SDK 长连接 export COS_SDK_KEEPALIVE=false # 然后重新执行部署命令 tcb hosting deploy -e envId ``` ### 域名访问 部署完成后,您可以通过以下方式访问网站: - 在控制台查看分配的默认域名 - 配置自定义域名(需要 ICP 备案) --- # 静态网站托管/高级功能/静态托管安全配置 > 当前文档链接: https://docs.cloudbase.net/envconfig/staticpages/intro 静态托管的安全配置为云开发的静态托管服务提供全方位的安全防护机制,帮助开发者有效保护网站资源,防止恶意访问和资源盗用。通过合理配置以下安全选项,可以显著提升您的静态网站安全性: * 防盗链配置 - 控制资源访问来源 * 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 访问服务配置域名关联,实现对静态网站托管资源的访问。 ## 基础配置 ### 操作步骤 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 ## 访问问题 ### 应用更新服务后,访问页面还停留在原有页面? **原因** 检查一下是否设置了节点缓存,可以手动刷新一下,前往[cdn控制台](https://console.cloud.tencent.com/cdn/refresh)可查看cdn是否刷新完成,刷新完成后重新访问 ### 静态资源访问时出现 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)](https://www.npmjs.com/package/@cloudbase/js-sdk) **@cloudbase/js-sdk** 让您可以在 Web 端(如 PC Web 页面、微信公众平台 H5 等)使用 JavaScript 访问 Cloudbase 服务和资源。 :::tip 提示 当前@cloudbase/js-sdk@latest 版本已升级至 v2 版本,若需使用 v1 版本,请参考[v1 文档](/api-reference/webv1/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 -S # yarn yarn add @cloudbase/js-sdk ``` **初始化 SDK** ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id region: "ap-shanghai", // 不传默认为上海地域 }); ``` :::tip 提示 `latest` 表示使用最新版本,你可以设置为固定版本 ::: ```html ``` :::tip 提示 `latest` 表示使用最新版本,你可以设置为固定版本 ::: ```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 端** 用户权限,需要登录后才可调用云开发能力。 :::tip 提示 使用此功能请升级 @cloudbase/js-sdk 至 2.21.0 及以后版本 ::: Publishable Key 可以暴露在浏览器,用于请求公开访问的资源。Publishable Key 实际是一个匿名用户,可以有效降低 MAU。详细介绍请 [参考文档说明](https://docs.cloudbase.net/api-reference/webv2/api-key#publishable-key-%E4%B8%8E%E7%99%BB%E5%BD%95%E8%AE%A4%E8%AF%81%E7%9A%84%E5%85%B3%E7%B3%BB) 。 **获取 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 只能生成一次,并且是长期有效的、不能删除的,请妥善保管 详情请参考: [匿名登录](/api-reference/webv2/authentication#authsigninanonymously) ```js const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const auth = app.auth(); await auth.signInAnonymously(); ``` 详情请参考: [账号登录](/api-reference/webv2/authentication#authsignin) ```js const app = cloudbase.init({ env: "your-env-id", // 替换为您的环境id }); const auth = app.auth(); await auth.signIn({ 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) ``` ## 地理位置查询 > ⚠️ 注意:对地理位置字段进行查询时,请建立地理位置索引,否则查询将失败 ```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)/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 :::tip 提示 新登录体系下,确保一个环境对应一个 tcb 实例(避免多次初始化相同环境的 tcb 实例) ::: ## App.auth() #### 接口描述 返回 [`Auth`](#auth) 对象,身份认证相关的属性和方法都在 Auth 对象中 接口声明:`auth(): Auth` :::warning @cloudbase/js-sdk@2.x 版本将只支持 `local` 方式(Web 端在显式退出登录之前 30 天保留身份验证状态)存储身份状态。(原 1.x 版本的 `session` 及 `none` 模式不再支持) ::: #### 示例代码 ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "xxxx-yyy", clientId: "xxxx", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); ``` ## 注册/登录/登出相关 :::tip 提示 `手机号验证码注册` 仅支持 `上海` 地域 ::: ### Auth.signUp() #### 接口描述 接口功能:注册用户,目前支持手机号验证码注册,邮箱验证码注册。更多详细流程可参考[用户注册](/authentication-v2/method/signup)。 接口声明:`signUp(params: SignUpRequest): Promise` #### 接口入参: SignUpRequest | 字段 | 类型 | 必填 | 说明 | | ------------------ | ------ | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | phone_number | string | 否 | 注册所用手机号,phone_number 与 email 必须任选其一使用 | | email | string | 否 | 注册所用邮箱 ,phone_number 与 email 必须任选其一使用 | | verification_code | string | 否 | 验证码,可通过[Auth.getVerification](/api-reference/webv2/authentication#authgetverification)获取 | | verification_token | string | 否 | 验证码 token,可通过[Auth.verify](/api-reference/webv2/authentication#authverify)获取 | | provider_token | string | 否 | 第三方 provider token,用于绑定第三方用户信息,可通过[Auth.grantProviderToken](/api-reference/webv2/authentication#authgrantprovidertoken)获取 | | password | string | 否 | 密码 | | name | string | 否 | 用户名 | | gender | string | 否 | 性别 | | picture | string | 否 | 头像 | | locale | string | 否 | 地址 | | username | string | 否 | 用户名称,长度 5-24 位,支持字符中英文、数字、特殊字符(仅支持\_-),不支持中文 | #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1) #### 示例代码 ```js // 初始化SDK const app = cloudbase.init({ env: "xxxx-yyy", clientId: "xxxxx", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 例:手机号验证码注册 // 第一步:用户点击获取验证码,调用如下方法发送短信验证码,将 verificationInfo 存储到全局,方便第三步作为参数输入 const phoneNumber = "+86 13800000000"; const verification = await auth.getVerification({ phone_number: phoneNumber, }); // 若使用邮箱验证,第一步代码改为 { const email = "test@example.com"; const verification = await auth.getVerification({ email: email, }); } // 验证码验证 // 调用发送短信接口后,手机将会收到云开发的短信验证码。 // 用户填入短信验证码,可以调用下面的接口进行验证。 // 第二步:等待用户在页面中输入短信验证码 const verificationCode = userInputCode; // 6位验证码 // 第三步:待用户输入完验证码之后,验证短信验证码 const verificationTokenRes = await auth.verify({ verification_id: verification.verification_id, verification_code: verificationCode, }); // 第四步:注册 // 如果该用户已经存,则登录 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", }); } ``` ### Auth.signIn() :::tip 提示 `手机号登录` 仅支持 `上海` 地域 ::: #### 接口描述 接口功能:在已完成注册后,可通过该方法进行用户登录,目前支持手机号,邮箱,用户名密码登录。用户名密码登录的详细流程可参考[账号密码登录](/authentication-v2/method/username-login)。 接口声明:`signIn(params: SignInRequest): Promise` #### 接口入参: SignInRequest | 字段 | 类型 | 必填 | 说明 | | ------------------ | ------ | ---- | ----------------------------------------------------------------------------------------------------------------------------------- | | username | string | 是 | 用户手机号,邮箱或自定义用户名,如果是中国手机号要带上“+86 ”国家码,例如“+86 138xxxxxxxxx” | | password | string | 否 | 用户密码 ,password 与 verification_token 必须任选其一 | | verification_token | string | 否 | 验证码 token ,password 与 verification_token 必须任选其一,可通过[Auth.verify](/api-reference/webv2/authentication#authverify)获取 | #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1) #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", clientId: "xxxxx", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 已完成注册 // 例:手机号登录 const phoneNumber = "+86 13800000000"; await auth.signIn({ username: phoneNumber, password: "your password", }); // 例:邮箱登录 const email = "test@example.com"; await auth.signIn({ username: email, password: "your password", }); // 例:用户名登录 const username = "myname"; await auth.signIn({ username, password: "your password", }); ``` ### Auth.signOut() #### 接口描述 接口功能:退出登录 接口声明:`signOut(params?: SignoutRequest): Promise` #### 接口入参: SignoutRequest | 字段 | 类型 | 必填 | 说明 | | ------------ | ------ | ---- | ----------------------------------------------------------------------------------------- | | redirect_uri | string | 否 | 退出后的重定向页面地址 | | state | string | 否 | 在有 redirect_uri 时,会将该参数放在 redirect_uri 的链接参数 state 中,用于区分某个身份源 | #### 接口出参: SignoutReponse | 字段 | 类型 | 说明 | | ------------ | ------ | --------------------------------------------------------------------------------------- | | redirect_uri | string | 重定向页面地址,如果入参传了 redirect_uri 或第三方认证源配置了 redirect_uri,则会有返回 | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 监听登录态变化 auth.onLoginStateChanged((params) => { console.log(params); const { eventType } = params?.data || {}; switch (eventType) { case "sign_in": // 登录成功 break; case "sign_out": // 退出登录成功 break; case "credentials_error": // 权限失效 break; default: return; } }); await auth.signOut(); ``` ### Auth.signInAnonymously() #### 接口描述 接口功能:匿名登录,详细流程可参考[匿名登录](/authentication-v2/method/anonymous)。 接口声明 `signInAnonymously(): Promise` #### 接口入参: 无 #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1) #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); await auth.signInAnonymously(); ``` ### Auth.setCustomSignFunc() #### 接口描述 接口功能:设置获取自定义登录 ticket 函数 接口声明:`setCustomSignFunc(getTickFn: GetCustomSignTicketFn): void` #### 接口入参: GetCustomSignTicketFn | 字段 | 类型 | 说明 | | --------- | ------------------------------ | ---------------------------- | | getTickFn | () => Promise<string> | 获取自定义登录 ticket 的函数 | #### 接口出参: 无 #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const getTickFn = new Promise((resolve, reject)); await auth.setCustomSignFunc(getTickFn); ``` ### Auth.signInWithCustomTicket() #### 接口描述 接口功能:自定义登录,配合[`Auth.setCustomSignFunc`](/api-reference/webv2/authentication#authsetcustomsignfunc)使用。详细流程可参考[自定义登录](/authentication-v2/method/custom-login)。 接口声明:`signInWithCustomTicket(): Promise` #### 接口入参: 无 #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1) #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); await auth.signInWithCustomTicket(); ``` ### Auth.signInWithOpenId() :::tip 提示 仅支持在 `微信小程序` 中使用 ::: #### 接口描述 接口功能:微信小程序 openId 静默登录。如果用户不存在,会根据[云开发平台-登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中对应身份源的`登录模式`配置,判断是否自动注册 接口声明:`signInWithOpenId(params?: SignInWithOpenIdReq): Promise` #### 接口入参: SignInWithOpenIdReq | 字段 | 类型 | 必填 | 默认值 | 说明 | | ---------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------- | | useWxCloud | boolean | 否 | true | true: 使用微信云开发模式进行请求,需开通小程序微信云开发环境; false:使用普通 http 请求 | #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1) #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); await auth.signInWithOpenId(); ``` ### Auth.signInWithUnionId() :::tip 提示 仅支持在 `微信小程序` 中使用 ::: #### 接口描述 接口功能:微信小程序 unionId 静默登录。如果用户不存在,会根据[云开发平台-登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中对应身份源的`登录模式`配置,判断是否自动注册 接口声明:`signInWithUnionId(): Promise` #### 接口入参: 无 #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1) #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); await auth.signInWithUnionId(); ``` ### Auth.signInWithPhoneAuth() :::tip 提示 仅支持在 `微信小程序` 中使用 ::: #### 接口描述 接口功能:微信小程序手机号授权登录。如果用户不存在,会根据[云开发平台-登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中对应身份源的`登录模式`配置,判断是否自动注册 接口声明:`signInWithPhoneAuth(params: SignInWithPhoneAuthReq): Promise` #### 接口入参: SignInWithPhoneAuthReq | 字段 | 类型 | 必填 | 默认值 | 说明 | | --------- | ------ | ---- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | phoneCode | string | 是 | 空 | 微信小程序手机号授权码,通过[微信小程序手机号快速验证组件](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html#%E8%BF%94%E5%9B%9E%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E) | #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1) #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); await auth.signInWithPhoneAuth({ phoneCode: "xxx" }); ``` ### Auth.signInWithSms() :::tip 提示 仅支持 `上海` 地域 ::: #### 接口描述 接口功能:短信验证码登陆。详细流程可参考[短信验证码登录](/authentication-v2/method/sms-login)。 接口声明:`signInWithSms(params: SignInWithSmsReq): Promise` #### 接口入参: SignInWithSmsReq | 字段 | 类型 | 必填 | 默认值 | 说明 | | ---------------- | ------ | ----------------- | ------ | --------------------------------------------------------------------------------------------------------------------- | | verificationInfo | object | 验证码 token 信息 | {} | [Auth.getVerification](/api-reference/webv2/authentication#authgetverification)的返回值 | | verificationCode | string | 验证码 | 空 | 获取到的短信验证码,调用[Auth.getVerification](/api-reference/webv2/authentication#authgetverification)后会发送验证码 | | phoneNum | string | 手机号 | 空 | 获取验证码的手机号,中国手机号要带上“+86 ”国家码,例如“+86 138xxxxxxxxx” | #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1) #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", 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, // 用户手机号 }); ``` ### Auth.signInWithEmail() #### 接口描述 接口功能:邮箱验证码登陆。详细流程可参考[邮箱验证码登录](/authentication-v2/method/email-login)。 接口声明:`signInWithEmail(params: SignInWithEmailReq): Promise` #### 接口入参: SignInWithEmailReq | 字段 | 类型 | 必填 | 默认值 | 说明 | | ---------------- | ------ | ----------------- | ------ | --------------------------------------------------------------------------------------------------------------------- | | verificationInfo | object | 验证码 token 信息 | {} | [Auth.getVerification](/api-reference/webv2/authentication#authgetverification)的返回值 | | verificationCode | string | 验证码 | 空 | 获取到的邮箱验证码,调用[Auth.getVerification](/api-reference/webv2/authentication#authgetverification)后会发送验证码 | | email | string | 邮箱 | 空 | 获取验证码的邮箱地址 | #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1) #### 示例代码 ```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, // 用户邮箱 }); ``` ### Auth.toDefaultLoginPage() :::tip 提示 使用此功能请升级 @cloudbase/js-sdk 至 2.18.0 及以后版本 ::: #### 接口描述 接口功能:跳转系统默认登录页,兼容 web 和微信小程序端 接口声明:`toDefaultLoginPage(params: authModels.ToDefaultLoginPage): Promise` #### 接口入参: ToDefaultLoginPage | 字段 | 类型 | 含义 | 默认值 | 说明 | | -------------- | ------ | ---------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | config_version | string | 默认登录页面的登录配置版本 | env | 1、‘env’表示托管登录页配置,可在[“云开发平台-身份认证”](https://tcb.cloud.tencent.com/dev?#/identity/land-page)中设置,2、非‘env’表示使用独立的托管登录页配置,可在“云开发平台-可视化低代码-访问控制”中设置,相应的 config_version 可以在应用自动跳转的\_\_auth 登录页面的链接中参数获取到 | | redirect_uri | string | 登录后重定向地址 | 默认为当前页面地址 | | | app_id | string | 应用 id | 空 | config_version 不是‘env’的时候为必填项,如 app-xxx | | query | object | 跳转到默认登录页要携带的参数 | 空 | | #### 接口出参: 无 #### 示例代码 ```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", }); ``` #### 本地调试 如果开发 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` 即可访问应用 ## 第三方平台登录相关 ### Auth.genProviderRedirectUri() #### 接口描述 接口功能:生成第三方平台授权 Uri (如微信二维码扫码授权网页) 接口声明:`genProviderRedirectUri(params: GenProviderRedirectUriRequest): Promise` #### 接口入参: GenProviderRedirectUriRequest | 字段 | 类型 | 必填 | 说明 | | --------------------- | ---------------------- | ---- | ------------------------------------------------------------------- | | provider_id | string | 是 | 第三方平台 ID,参考系统内置三方平台[列表](#系统内置三方列表) | | provider_redirect_uri | string | 是 | 第三方平台重定向 Uri,授权完成后,重定向时会在 url 中携带 code 参数 | | state | string | 是 | 用户自定义状态标识字段,识别三方平台回调来源 | | other_params | {sign_out_uri?:string} | 否 | 其他参数 | #### 接口出参: GenProviderRedirectUriResponse | 字段 | 类型 | 必填 | 说明 | | ----------- | ------ | ---- | ---------- | | uri | string | 是 | 客户端请求 | | signout_uri | string | 是 | 登出 Uri | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const providerId = "test_provider_id"; // 第三方平台 ID const providerUri = "test_provider_redirect_uri"; // 第三方平台重定向 Uri const state = "wx_open"; // 用户自定义状态标识字段 const otherParams = { sign_out_uri: "test_sign_out_uri" }; // 其他参数 // 三方平台授权登录示例 // 1. 获取第三方平台授权页地址(如微信授权) const { uri } = await auth.genProviderRedirectUri({ provider_id: providerId, provider_redirect_uri: providerUri, state: state, other_params: otherParams, }); // 2. 访问uri (如 location.href = uri) // 3. 授权 (如微信扫码授权) // 4. 回调至 provider_redirect_uri 地址(url query中携带 授权code,state等参数),此时检查 state 是否符合预期(如 自己设置的 wx_open) const provider_code = "your provider code"; // 5. state符合预期(微信开放平台授权 wx_open),则获取该三方平台token const { provider_token } = await auth.grantProviderToken({ provider_id: "wx_open", provider_redirect_uri: "cur page", // 指定三方平台跳回的 url 地址 provider_code: provider_code, // 第三方平台跳转回页面时,url param 中携带的 code 参数 }); // 6. 通过 provider_token 仅登录或登录并注册(与云开发平台-登录方式-身份源登录模式配置有关) await auth.signInWithProvider({ provider_token, }); ``` ### Auth.grantProviderToken() #### 接口描述 接口功能:提供第三方平台登录 token 接口声明:`grantProviderToken(params: GrantProviderTokenRequest): Promise` #### 接口入参: GrantProviderTokenRequest | 字段 | 类型 | 必填 | 说明 | | --------------------- | ------ | ---- | -------------------------------------------------------- | | provider_id | string | 是 | 第三方平台 ID,参考系统内置[三方列表](#系统内置三方列表) | | provider_redirect_uri | string | 否 | 第三方平台重定向 uri | | provider_code | string | 否 | 第三方平台授权 code(重定向 uri 中携带) | | provider_access_token | string | 否 | 第三方平台访问 token(重定向 uri 中携带) | | provider_id_token | string | 否 | 第三方平台 ID token(重定向 uri 中携带) | #### 接口出参: GrantProviderTokenResponse | 字段 | 类型 | 必填 | 说明 | | ---------------- | ---------------------------------------------------------------------- | ---- | ---------------- | | provider_token | string | 是 | 第三方平台 token | | expires_in | number | 是 | 有效期 | | provider_profile | [ProviderProfile](/api-reference/webv2/authentication#providerprofile) | 否 | 第三方身份源信息 | ##### ProviderProfile | 字段 | 类型 | 必填 | 说明 | | ------------ | ------------------------------------------------------------------------------ | ---- | ------------------------------------------------- | | provider_id | string | 是 | 默认内置的三方 providerid,**wx_open**, **wx_mp** | | sub | string | 是 | 第三方用户 id (如 wxopenid) | | name | string | 否 | 名称 | | phone_number | string | 否 | 手机号 | | picture | string | 否 | 头像 | | meta | [ProviderProfileMeta](/api-reference/webv2/authentication#providerprofilemeta) | 否 | 第三方身份源扩展信息(小程序返回) | ##### ProviderProfileMeta | 字段 | 类型 | 必填 | 说明 | | ------ | ------ | ---- | --------------- | | appid | string | 否 | 小程序的 appid | | openid | string | 否 | 小程序的 openid | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const providerId = "wx_open"; // 微信开放平台 auth.grantProviderToken({ provider_id: providerId, provider_redirect_uri: "cur page", // 指定三方平台跳回的 url 地址 provider_code: "provider code", // 第三方平台跳转回页面时,url param 中携带的 code 参数 }); ``` ### Auth.signInWithProvider() #### 接口描述 接口功能:第三方平台登录。如果用户不存在,会根据[云开发平台-登录方式](https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage)中对应身份源的`登录模式`配置,判断是否自动注册 接口声明:`signInWithProvider(params: SignInWithProviderRequest): Promise` #### 接口入参: SignInWithProviderRequest | 字段 | 类型 | 必填 | 说明 | | -------------- | ------ | ---- | ----------------------------------------------------------------------------------------------------------------- | | provider_token | string | 是 | 第三方平台 token,可通过[Auth.grantProviderToken](/api-reference/webv2/authentication#authgrantprovidertoken)获取 | #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1) #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const providerToken = "test_provider_token"; auth.signInWithProvider({ provider_token: providerToken, }); ``` ### Auth.unbindProvider() #### 接口描述 接口功能:解除第三方绑定 接口声明:`unbindProvider(params: UnbindProviderRequest): Promise` #### 接口入参: UnbindProviderRequest | 字段 | 类型 | 必填 | 说明 | | ----------- | ------ | ---- | -------------------------------------------------------------------------------------------------------------------------- | | provider_id | string | 是 | 第三方平台 ID,参考第三方绑定时的回包,可通过[Auth.getProviders](/api-reference/webv2/authentication#authgetproviders)获取 | #### 接口出参: 无 #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 完成登录后 // 获取绑定第三方平台ID const { id } = await auth.getProviders(); await auth.unbindProvider({ provider_id: id, }); ``` ### Auth.getProviders() #### 接口描述 接口功能:获取第三方绑定列表 接口声明:`getProviders(): Promise` #### 接口入参: 无 #### 接口出参: UserProfileProvider | 字段 | 类型 | 必填 | 说明 | | ---------------- | ------ | ---- | ----------------- | | id | string | 是 | 第三方平台 ID | | provider_user_id | string | 是 | 第三方平台用户 ID | | name | string | 是 | 第三方平台昵称 | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 完成登录后 await auth.getProviders(); ``` ### Auth.bindWithProvider() #### 接口描述 接口功能:绑定第三方登录 接口声明:`bindWithProvider(params: BindWithProviderRequest): Promise` #### 接口入参: BindWithProviderRequest | 字段 | 类型 | 必填 | 说明 | | -------------- | ------ | ---- | ---------------------------------------------------------------------------------------------------------------------- | | provider_token | string | 是 | 第三方平台授权 token,可通过[Auth.grantProviderToken](/api-reference/webv2/authentication#authgrantprovidertoken) 获取 | #### 接口出参: 无 #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const provider_token = "test_provider_token"; // 参考Auth.grantProviderToken 获取 const auth = app.auth(); await auth.bindWithProvider({ provider_token, }); ``` ## 验证/授权相关 ### Auth.verifyCaptchaData() #### 接口描述 接口功能:验证图形验证码数据,用于自定义适配器或使用 adapter 时处理图形验证码验证流程 接口声明:`verifyCaptchaData(params: VerifyCaptchaDataRequest): Promise` #### 接口入参: VerifyCaptchaDataRequest | 字段 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | -------------------------------------------------------------------- | | token | string | 是 | 验证码 token,从 adapter 的 `captchaOptions.openURIWithCallback` 回调 URL 参数中获取 | | key | string | 是 | 用户输入的图形验证码答案 | #### 接口出参: CaptchaToken | 字段 | 类型 | 说明 | | ------------- | ------ | ------------------------ | | captcha_token | string | 验证通过后返回的验证令牌 | | expires_in | number | 令牌有效期(秒) | #### 示例代码 ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 在自定义适配器中使用 const adapter = { // 其他适配器配置... captchaOptions: { openURIWithCallback: async (url) => { // 解析 URL 参数 const urlObj = new URL(url); const captchaData = urlObj.searchParams.get("captcha_data") || ""; // Base64 编码的验证码图片 const state = urlObj.searchParams.get("state") || ""; const token = urlObj.searchParams.get("token") || ""; // 1. 展示验证码图片给用户(captchaData 为 Base64 编码的图片数据) // 2. 等待用户输入验证码 const userInputKey = await showCaptchaDialogAndGetUserInput(captchaData); // 3. 调用 verifyCaptchaData 验证用户输入 const captchaToken = await auth.verifyCaptchaData({ token: token, key: userInputKey, // 用户输入的验证码 }); return captchaToken; }, }, }; ``` :::tip 使用场景 当云开发检测到需要进行图形验证码验证时(如频繁登录等安全敏感操作),会触发适配器的 `captchaOptions.openURIWithCallback` 回调。开发者需要: 1. 从回调 URL 中解析 `captcha_data`(验证码图片)、`token` 等参数 2. 将验证码图片展示给用户 3. 获取用户输入的验证码答案 4. 调用 `verifyCaptchaData` 接口验证并获取 `captchaToken` 5. 将 `captchaToken` 作为 Promise 的返回值 ::: ### Auth.verify() :::tip 提示 `短信验证码` 仅支持 `上海` 地域 ::: #### 接口描述 接口功能:验证码验证,包括短信验证码、邮箱验证码 接口声明:`verify(params: VerifyRequest): Promise` #### 接口入参: VerifyRequest | 字段 | 类型 | 必填 | 说明 | | ----------------- | ------ | ---- | ---------------------------------------------------------------------------------------------------- | | verification_code | string | 是 | 验证码,可通过[Auth.getVerification](/api-reference/webv2/authentication#authgetverification)获取 | | verification_id | string | 是 | 验证码 ID,可通过[Auth.getVerification](/api-reference/webv2/authentication#authgetverification)获取 | #### 接口出参: VerifyResponse | 字段 | 类型 | 必填 | 说明 | | ------------------ | ------ | ---- | ------------ | | verification_token | string | 否 | 验证码 token | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 例:手机号验证码/ 邮箱验证码注册 // 第一步:用户点击获取验证码,调用如下方法发送短信验证码,将 verificationInfo 存储到全局,方便第三步作为参数输入 const phoneNumber = "+86 13800000000"; const verificationInfo = await auth.getVerification({ phone_number: phoneNumber, }); /* // 若使用邮箱验证,第一步代码改为 const email = "test@example.com" const verificationInfo = await auth.getVerification({ email: email }); */ // 第二步:等待用户在页面中输入验证码 const verificationCode = userInputCode; // 6位验证码 // 第三步:待用户输入完验证码之后,验证验证码 await auth.verify({ verification_code: verificationCode, verification_id: verificationInfo.verification_id, }); ``` ### Auth.getVerification() :::tip 提示 `获取短信验证码` 仅支持 `上海` 地域 ::: #### 接口描述 接口功能:获取短信/邮件验证码 接口声明:`getVerification(params: GetVerificationRequest): Promise` #### 接口入参: GetVerificationRequest | 字段 | 类型 | 必填 | 说明 | | ------------ | ---------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | | phone_number | string | 否 | 手机号,中国手机号要带上“+86 ”国家码,例如“+86 138xxxxxxxxx”。与 email 二选一 | | email | string | 否 | 邮箱,与 phone_number 二选一 | | target | [Target](/api-reference/webv2/authentication#target) | 否 | 手机号或邮箱的应用场景 | ##### Target | 值 | 含义 | | ---- | -------------------------------------------------------------------- | | ANY | 不校验手机号或邮箱是否存在,使用场景:注册或者登录这种,没有登录态的 | | USER | 需要手机号或邮箱必须在系统中存在,且已经绑定用户 | #### 接口出参: GetVerificationResponse | 字段 | 类型 | 说明 | | --------------- | ------- | -------------- | | verification_id | string | 验证码 id | | is_user | boolean | 是否是注册用户 | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const verificationCode = "000000"; const phoneNumber = "+86 13800000000"; // const email = "test@example.com"; const verification = await auth.getVerification({ phone_number: phoneNumber, // email: email, }); ``` ### Auth.sudo() #### 接口描述 接口功能:通过 sudo 接口获取高级操作权限,如修改密码([Auth.setPassword](/api-reference/webv2/authentication#authsetpassword))、修改手机号([Auth.bindPhoneNumber](/api-reference/webv2/authentication#authbindphonenumber))、修改邮箱([Auth.bindEmail](/api-reference/webv2/authentication#authbindemail))、删除用户([Auth.deleteMe](/api-reference/webv2/authentication#authdeleteMe))等操作 接口声明:`Auth.sudo(params: SudoRequest): Promise` #### 接口入参: SudoRequest | 字段 | 类型 | 必填 | 说明 | | ------------------ | ------ | ---- | ----------------------------------------------------------------------------------------------------------------------- | | password | string | 否 | 密码,password 和 verification_token 二选一,如果绑定了手机号,则必须使用 verification_token 进行 sudo | | verification_token | string | 否 | token 令牌,通过账号绑定的手机号或邮箱验证获取,可通过[Auth.verify](/api-reference/webv2/authentication#authverify)获取 | #### 接口出参: SudoResponse | 字段 | 类型 | 说明 | | ---------- | ------ | --------------------------------------------------------------------------------------------- | | sudo_token | string | 高级权限 token 令牌, 如果用户只开启三方登录, 没有设置密码的情况下,sudo token 为 "" 空字符串 | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 方式一:使用密码 const pass = "test_password"; const sudoRes = await auth.sudo({ password: pass, }); console.log(sudoRes.sudo_token); // 方式二:通过邮箱验证码,手机号验证码获取。 // 当前账号绑定的邮箱地址或手机号 const email = "test@example.com"; // const phoneNumber = "+86 13800000000" // 第一步:用户点击获取验证码,调用如下方法发送验证码,将 verificationInfo 存储到全局,方便第三步作为参数输入 const verificationInfo = await auth.getVerification({ email: email, // phone_number: phoneNumber }); // 第二步:等待用户在页面中输入验证码 const verificationCode = userInputCode; // 6位验证码 // 第三步:待用户输入完验证码之后,验证验证码 const verificationTokenRes = await auth.verify({ verification_id: verificationInfo.verification_id, verification_code: verificationCode, }); // 第四步:获取 sudo_token const sudoRes = await auth.sudo({ verification_token: verificationTokenRes.verification_token, }); console.log(sudoRes.sudo_token); // 可调用auth.setPassword、auth.bindEmail、auth.bindPhoneNumber、auth.deleteMe等方法 ``` ### Auth.getAccessToken() #### 接口描述 接口功能:获取访问凭证 accessToken 接口声明:`Auth.getAccessToken(): Promise` #### 接口入参: 无 #### 接口出参: GetAccessTokenResponse | 字段 | 类型 | 说明 | | ----------- | ------ | -------- | | accessToken | string | 访问令牌 | | env | string | 环境 id | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 某种方式登录后... // 获取access_token const { accessToken } = await auth.getAccessToken(); console.log(accessToken); ``` ## 用户信息相关 ### Auth.getCurrentUser() #### 接口描述 接口功能:`Auth.currentUser`的异步操作,返回表示当前用户的 [`User`](#user) 实例 接口声明:`getCurrentUser(): Promise` #### 接口入参: 无 #### 接口出参: User 详见 [`User`](/api-reference/webv2/authentication#user-1) #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", clientId: "xxxx", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 执行某种登录之后... const user = await auth.getCurrentUser(); ``` ### Auth.bindPhoneNumber() :::tip 提示 仅支持 `上海` 地域 ::: #### 接口描述 接口功能:绑定手机号 接口声明:`bindPhoneNumber(params: BindPhoneRequest): Promise` #### 接口入参: BindPhoneRequest | 字段 | 类型 | 必填 | 说明 | | ------------------ | ------ | ---- | ------------------------------------------------------------------------------------------ | | phone_number | string | 是 | 新手机号,中国手机号要带上“+86 ”国家码,例如“+86 138xxxxxxxxx” | | sudo_token | string | 是 | 高级权限 token 令牌,可通过[Auth.sudo](/api-reference/webv2/authentication#authsudo)获取 | | verification_token | string | 是 | 验证码 token 令牌,可通过[Auth.verify](/api-reference/webv2/authentication#authverify)获取 | #### 接口出参: 无 #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); // 前置条件,先登录 const auth = app.auth(); // 第一步:获取 sudo token, 参考 Auth.sudo 接口获取 const sudo_token = "test_sudo_token"; const phone_number = "+86 13800000000"; // 第二步:用户点击获取验证码,调用如下方法发送短信验证码,将 verificationInfo 存储到全局,方便第四步作为参数输入 const verificationInfo = await auth.getVerification({ phone_number: phone_number, }); // 第三步:等待用户在页面中输入短信验证码 const verificationCode = userInputCode; // 6位验证码 // 第四步:待用户输入完验证码之后,验证短信验证码 const verification_token_res = await auth.verify({ verification_id: verificationInfo.verification_id, verification_code: verification_code, }); const verification_token = verification_token_res.verification_token; // 第五步:绑定手机号 await auth.bindPhoneNumber({ phone_number: phone_number, sudo_token: sudo_token, verification_token: verification_token, }); ``` ### Auth.bindEmail() #### 接口描述 接口功能:更新邮箱地址 接口声明:`bindEmail(params: BindEmailRequest): Promise` #### 接口入参: BindEmailRequest | 字段 | 类型 | 必填 | 说明 | | ------------------ | ------ | ---- | ------------------------------------------------------------------------------------------ | | email | string | 是 | 邮箱地址 | | sudo_token | string | 是 | 高级权限 token 令牌,可通过[Auth.sudo](/api-reference/webv2/authentication#authsudo)获取 | | verification_token | string | 是 | 验证码 token 令牌,可通过[Auth.verify](/api-reference/webv2/authentication#authverify)获取 | #### 接口出参: 无 #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); // 前置条件,先登录 const auth = app.auth(); // 第一步:获取 sudo token, 参考 Auth.sudo 接口获取 const sudoToken = "test_sudo_token" const newEmail = "new@example.com"; // 第二步:用户点击获取验证码,调用如下方法发送邮箱验证码,将 verificationInfo 存储到全局,方便第四步作为参数输入 const verificationInfo = await auth.getVerification({ email: newEmail }); // 第三步:等待用户在页面中输入短信验证码 const verificationCode = userInputCode; // 6位验证码 // 第四步:待用户输入完验证码之后,验证短信验证码 const newEmailTokenRes = await auth.verify({ verification_id: verificationInfo.verification_id, verification_code: verificationCode, }); const verificationToken = newEmailTokenRes.verification_token // 第五步:绑定新邮箱 await auth.bindEmail({ email: newEmail, sudo_token: sudoToken verification_token: verificationToken }); ``` ### Auth.setPassword() #### 接口描述 接口功能:设置密码(已登录状态下,更新用户密码) 接口声明 `setPassword(params: SetPasswordRequest): Promise` #### 接口入参: SetPasswordRequest | 字段 | 类型 | 必填 | 说明 | | ------------ | ------ | ---- | ---------------------------------------------------------------------------------------- | | new_password | string | 是 | 新密码 | | sudo_token | string | 是 | 高级权限 token 令牌,可通过[Auth.sudo](/api-reference/webv2/authentication#authsudo)获取 | #### 接口出参: 无 #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 第一步:获取 sudo token,参考 Auth.sudo 接口获取 const sudoToken = "test_sudo_token"; // 第二步:更新密码 const newPassWord = "test_new_password"; await auth.setPassword({ new_password: newPassWord, sudo_token: sudoToken, }); ``` ### Auth.getUserInfo() #### 接口描述 接口功能:获取用户信息 接口声明:`getUserInfo(): Promise` #### 接口入参: 无 #### 接口出参: UserInfo | 字段 | 类型 | 说明 | | ------------------- | ------------------------------------------------------------------------------ | -------------------------------------- | | name | string | 用户昵称(区分与 登录用户名 username) | | picture | string | 用户上传头像 | | phone_number | string | 用户绑定手机号 | | email_verified | boolean | 用户是否经过邮箱验证 | | birthdate | string | 用户生日 | | locale | string | 用户设置语言 | | zoneinfo | string | 时区 | | UserProfileProvider | [UserProfileProvider](/api-reference/webv2/authentication#userprofileprovider) | 第三方平台配置 | ##### UserProfileProvider | 字段 | 类型 | 必填 | 说明 | | ---------------- | ------ | ---- | -------------------------------------------------------------------------------------------------------------- | | id | string | 否 | 默认内置的三方 [provider_id](/api-reference/webv2/authentication#系统内置三方列表),例如**wx_open**, **wx_mp** | | provider_user_id | string | 否 | 第三方 provider 用户 id (如 wxopenid) | | name | string | 否 | 名称 | ##### 系统内置三方列表 | provider_id | 含义 | | ----------- | ------------ | | wx_open | 微信开放平台 | | wx_mp | 微信公众号 | | google | google平台 | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const userInfo = await auth.getUserInfo(); ``` ### Auth.queryUser() :::warning 自定义登录场景和匿名登录场景,不支持使用该接口查询用户信息(自定义登录场景请在业务服务中自行查询用户信息,匿名登录场景不支持) ::: #### 接口描述 接口功能:查询用户,可通过用户名、邮箱、手机号任意参数查询 接口声明:`queryUser(queryObj: QueryUserProfileRequest): Promise;` #### 接口入参: QueryUserProfileRequest | 字段 | 类型 | 必填 | 说明 | | ------------ | ------------------- | ---- | ------------------------------------------------------------ | | id | Array<string> | 否 | 用户 uid 数组,最多支持查询 50 个 id 对应的用户 | | username | string | 否 | 用户名称 | | email | string | 否 | 邮箱 | | phone_number | string | 否 | 手机号,中国手机号要带上“+86 ”国家码,例如“+86 138xxxxxxxxx” | #### 接口出参: QueryUserProfileResponse | 字段 | 类型 | 必填 | 说明 | | ----- | ---------------------------------------------------------------------------- | ---- | -------- | | total | string | 否 | 数量 | | data | [SimpleUserProfile](/api-reference/webv2/authentication#simpleuserprofile)[] | 否 | 用户列表 | ##### SimpleUserProfile | 字段 | 类型 | 必填 | 说明 | | ------------ | ------ | ---- | ------ | | sub | string | 是 | 下标 | | name | string | 是 | 名称 | | picture | string | 否 | 图片 | | gender | string | 否 | 性别 | | locale | string | 否 | 地点 | | email | string | 否 | 邮箱 | | phone_number | string | 否 | 手机号 | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // const email = "test@example.com"; // const phoneNumber = "+86 13800000000"; const username = "test_username"; const userInfo = await auth.queryUser({ username: username, // email, // phone_number: phoneNumber, }); ``` ### Auth.resetPassword() :::tip 提示 `手机号验证码方式` 仅支持 `上海` 地域 ::: #### 接口描述 接口功能:重置密码(用户忘记密码无法登录时,可使用该接口强制设置密码) 接口声明:`resetPassword(params: ResetPasswordRequest): Promise` #### 接口入参: ResetPasswordRequest | 字段 | 类型 | 必填 | 说明 | | ------------------ | ------ | ---- | ------------------------------------------------------------------------------------------ | | email | string | 否 | 邮箱, 与 phone_number 二选一 | | phone_number | string | 否 | 手机号,与 email 二选一,中国手机号要带上“+86 ”国家码,例如“+86 138xxxxxxxxx” | | new_password | string | 是 | 新密码 | | verification_token | string | 是 | 验证码 token 令牌,可通过[Auth.verify](/api-reference/webv2/authentication#authverify)获取 | #### 接口出参: 无 #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const email = "testexample.com"; const newPassword = "test_new_password"; const phoneNumber = "+86 13800000000"; // 第一步:用户点击获取验证码,调用如下方法发送短信验证码,将 verificationInfo 存储到全局,方便第三步作为参数输入 const verificationInfo = await auth.getVerification({ phone_number: phoneNumber, }); // 第二步:等待用户在页面中输入短信验证码 const verificationCode = userInputCode; // 6位验证码 // 第三步:待用户输入完验证码之后,验证短信验证码 const verificationToken = await auth.verify({ verification_id: verificationInfo.verification_id, verification_code: verificationCode, }); await auth.resetPassword({ // email: email, phone_number: phoneNumber, new_password: newPassword, verification_token: verificationToken.verificationToken, }); ``` ### Auth.isUsernameRegistered() #### 接口描述 接口功能:检查用户名是否存在。 接口声明:`isUsernameRegistered(username: string): Promise` #### 接口入参 | 字段 | 类型 | 必填 | 说明 | | -------- | ------ | ---- | ------------------------------------------------------------------------------- | | username | string | 是 | 用户名称,长度 5-24 位,支持字符中英文、数字、特殊字符(仅支持\_-),不支持中文 | #### 接口出参: boolean | 值 | 含义 | | ----- | ------ | | true | 存在 | | false | 不存在 | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const username = "your_awesome_username"; const exist = await auth.isUsernameRegistered(username); ``` ### Auth.deleteMe() #### 接口描述 接口功能:删除用户。 接口声明:`deleteMe(params: WithSudoRequest): Promise` #### 接口入参: WithSudoRequest | 字段 | 类型 | 必填 | 说明 | | ---------- | ------ | ---- | ---------------------------------------------------------------------------------------- | | sudo_token | string | 是 | 高级权限 token 令牌,可通过[Auth.sudo](/api-reference/webv2/authentication#authsudo)获取 | #### 接口出参: UserProfile | 字段 | 类型 | 说明 | | -------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | sub | string | 用户唯一标识) | | name | string | 用户昵称(区分与 登录用户名 username) | | username | string | 用户名称,长度 5-24 位,支持字符中英文、数字、特殊字符(仅支持\_-),不支持中文 | | picture | string | 用户上传头像 | | phone_number | string | 用户绑定手机号 | | email | string | 用户绑定邮箱 | | email_verified | boolean | 用户是否经过邮箱验证 | | groups | Array<{id: string; expires_at?: string}> | 用户所属组/角色列表(字符串数组) | | gender | string | 性别 | | birthdate | string | 用户生日 | | locale | string | 用户设置语言 | | zoneinfo | string | 时区 | | providers | Array<[UserProfileProvider](/api-reference/webv2/authentication#userprofileprovider)> | 第三方平台配置 | #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); // 1. 通过 sudo 操作获取 sudo_token,参考 Auth.sudo 方法 // 2. deleteMe const user = await auth.deleteMe({ sudo_token: "your sudo_token", }); ``` ### Auth.loginScope() #### 接口描述 接口功能:查询用户的权限范围,可以用来判断是否为匿名登录状态 接口声明:`loginScope(): Promise` #### 接口入参: 无 #### 接口出参: string #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", region: "ap-shanghai", // 不传默认为上海地域 }); const auth = app.auth(); const username = "your_awesome_username"; // 经过某种方式登录后... const loginScope = await auth.loginScope(); if (loginScope === "anonymous") { console.log("当前为匿名登录方式"); } ``` ## LoginState `LoginState` 对象是对用户当前的登录态的抽象 ### Auth.hasLoginState() #### 接口描述 接口功能:返回当前登录状态 [`LoginState`](/api-reference/webv2/authentication#loginstate-1),如果未登录,则返回 null 接口声明:`hasLoginState(): LoginState | null` #### 接口入参: 无 #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1),返回 null 则表示当前未登录 #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", clientId: "xxxx", region: "ap-shanghai", // 不传默认为上海地域 }); const loginState = app.auth().hasLoginState(); if (loginState) { // 登录态有效 } else { // 没有登录态,或者登录态已经失效 } ``` ### Auth.getLoginState() #### 接口描述 接口功能:`Auth.hasLoginState()`的异步操作,返回当前登录状态 [`LoginState`](#loginstate),返回 null 则表示当前未登录 接口声明:`getLoginState(): Promise` :::tip 提示 此 API 是 `hasLoginState` 的异步模式,适用于本地存储为异步的平台,比如 React Native ::: #### 接口入参: 无 #### 接口出参: LoginState 详见 [`LoginState`](/api-reference/webv2/authentication#loginstate-1),返回 null 则表示当前未登录 #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", clientId: "xxxx", region: "ap-shanghai", // 不传默认为上海地域 }); const loginState = await app.auth().getLoginState(); if (loginState) { // 登录态有效 } else { // 没有登录态,或者登录态已经失效 } ``` ### Auth.onLoginStateChanged() #### 接口描述 接口功能:监听登录状态变化,当登录状态发生变化时会触发该函数。比如调用[注册/登录/登出相关接口](#注册登录登出相关)、或者 Credentials 出现异常(如'credentials not found'、'no refresh token found in credentials'错误)等。 接口声明:`onLoginStateChanged(callback: Function): Promise` #### 接口入参: OnLoginStateChangedParams | 字段 | 类型 | 必填 | 说明 | | -------- | -------- | ---- | -------- | | callback | Function | 是 | 回调函数 | ##### callback 回调函数入参定义 | 字段 | 类型 | 说明 | | ---- | ------ | -------------------------------------------------------------------------------- | | name | string | 默认值为 loginStateChanged | | data | object | 包括 { msg?: string; eventType: 'sign_in' \| 'sign_out' \| 'credentials_error' } | :::tip 提示 如果 eventType 是 sign_in 或 sign_out,还会返回当前登录状态 LoginState ::: #### 接口出参: 无 #### 示例代码 ```js const app = cloudbase.init({ env: "xxxx-yyy", clientId: "xxxx", region: "ap-shanghai", // 不传默认为上海地域 }); app.auth().onLoginStateChanged((params) => { console.log(params); const { eventType } = params?.data || {}; switch (eventType) { case "sign_in": // 登录成功 break; case "sign_out": // 退出登录成功 break; case "credentials_error": // 权限失效 break; default: return; } }); ``` ### LoginState.user 类型:`User | null` 表示当前用户,具体请参考 [User](/api-reference/webv2/authentication#user-1) 如果没有登录,则为 `null` ## User ### User.update() #### 接口描述 接口功能:更新用户信息 接口声明:`update(userInfo): Promise` #### 接口入参: userInfo | 字段 | 类型 | 必填 | 说明 | | ------------ | ------ | ---- | ------------------------------------------------------------------------------- | | name | string | 否 | 用户昵称(区分与 登录用户名 username) | | username | string | 否 | 用户名称,长度 5-24 位,支持字符中英文、数字、特殊字符(仅支持\_-),不支持中文 | | picture | string | 否 | 用户上传头像 | | phone_number | string | 否 | 用户绑定手机号 | | email | string | 否 | 用户绑定邮箱 | | gender | string | 否 | 性别,取值仅限于 MALE、FEMALE、UNKNOWN | | birthdate | string | 否 | 用户生日 | #### 接口出参: 无 #### 示例代码 ```js const user = auth.currentUser; user .update({ gender: "MALE", // 性别,取值仅限于 MALE、FEMALE、UNKNOWN }) .then(() => { // 更新用户信息成功 }); ``` ### User.refresh() #### 接口描述 接口功能:刷新本地用户信息。当用户在其他客户端更新用户信息之后,可以调用此接口同步更新之后的信息。 接口声明:`refresh(): Promise` #### 接口入参: 无 #### 接口出参: UserInfo | 字段 | 类型 | 说明 | | -------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | sub | string | 用户唯一标识) | | name | string | 用户昵称(区分与 登录用户名 username) | | username | string | 用户名称,长度 5-24 位,支持字符中英文、数字、特殊字符(仅支持\_-),不支持中文 | | picture | string | 用户上传头像 | | phone_number | string | 用户绑定手机号 | | email | string | 用户绑定邮箱 | | email_verified | boolean | 用户是否经过邮箱验证 | | groups | Array<{id: string; expires_at?: string}> | 用户所属组/角色列表(字符串数组) | | gender | string | 性别 | | birthdate | string | 用户生日 | | locale | string | 用户设置语言 | | zoneinfo | string | 时区 | | providers | Array<[UserProfileProvider](<(/api-reference/webv2/authentication#userprofileprovider)>)> | 第三方平台配置 | #### 示例代码 ```js const user = auth.currentUser; user.refresh().then((userInfo) => { // 刷新用户信息成功 }); ``` ## 错误码 ### 登录错误 | 错误码 | 说明 | | ------------------ | ---------------------------------------------------- | | 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 | 请在“云开发平台-环境配置-安全来源-安全域名”中检查对应{env}环境下是否已经配置了安全域名{url},配置后 10 分钟生效 | ### 验证码相关处理 error==captcha_required 或 error==captcha_invalid 表示请求触发了验证码相关逻辑。需要进行机器验证。 > 验证码流程完成后,若业务接口返回 error 等于 captcha_required,表示请求需要 captcha_token 参数,尽可能使用本地的未过期的验证码。当 error 等于 captcha_invalid 时,表示验证码无效,需要需要重新获取验证码。在同一个验证流程内,captcha_invalid 最多尝试一次即可。 > 如需使用 adapter 进行处理,请参考[adapter 的验证码处理指南](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) #### 初始化验证码 ```bash curl -X POST "https://${env}.ap-shanghai.tcb-api.tencentcloudapi.com/auth/v1/captcha/init" \ -H "Content-Type:application/json" \ -u ${clientID}:${clientSecrect} -d \ '{ "redirect_uri": "https://example.com/callback", "state": "your_state string" }' ``` ##### 请求参数 redirect_uri: (必传) 验证码验证完成后的地址。 state: (必传) 系统状态字符串,该字符串在验证码验证完成后后将 state 携带到。 ##### 响应 1:状态码 200 且返回 url 字段非空,需要展示验证码并完成验证 如果用户请求比较频繁或存在其他风险,验证码服务会返回下面的形式。 ```json { "url": "https://exmaple.com/captcha.html", "expires_in": 600 } ``` 此时表示,用户行为需要经过验证码验证才可以通过,请求成功后,客户端通过浏览器或 webview/iframe 等 打开 url 地址,比如上面的https://exmaple.com/captcha.html 用户在 web 中处理完成后,会自动重定向到下面的地址:(其中 captcha_token 为验证码 token,expires_in 为过期时间,单位为秒), https://example.com/callback?state=xxxxx&captcha_token=hbGciOiJeyJhbGciOiJSUAeyJhbGciOiJ&expires_in=600 业务方需要监听 redirect_uri 的地址变化,当地址为 appanme://com.package.name/callback 时,比对 state 是否和传入的相同,并获取到 captcha_token 和 expires_in。 若验证过程发生错误,验证页面会展示错误信息,用户点击返回后,验证页面会将错误信息 error 和 error_description 拼接到 redirect_uri 后重定向,例如: https://example.com/callback?state=xxxxx&error=xxx&error_description=xxx 此时业务方可以根据需要恢复初始页或做其它处理。 ##### 响应 2:状态码非 200,需要进行错误处理 如果用户请求比较频繁或存在其他风险,验证码服务会返回下面的形式。 ```json { "error": "resource_exhausted", "error_description": "Your operation is too frequent, please try again later" } ``` 此时客户端需要展示 error_descriprion, 可以结合 i18n 展示进行多语言展示。 ##### 拿到 captcha_token 再次请求 拿到 captcha_token 后,将 captcha_token 放到 url 参数中进行请求; 比如,请求 /v1/example 返回 captcha_invalid 错误,此时,则需要再次请求 /v1/example?captcha_token=hbGciOiJeyJhbGciOiJSUAeyJhbGciOiJ 即可完成操作。 ## 类型定义 ### User User 对象是当前用户信息的抽象 | 属性 | 类型 | 说明 | | ------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | uid | string \| undefined | 用户唯一标识 | | gender | string \| undefined | 性别 | | picture | string \| undefined | 头像 URL | | email | string \| undefined | ​ 邮箱 | | emailVerified | boolean \| undefined | ​ 邮箱是否已验证 | | phoneNumber | string \| undefined | 手机号 | | username | string \| undefined | 用户名称,长度 5-24 位,支持字符中英文、数字、特殊字符(仅支持\_-),不支持中文 | | name | string \| undefined | 用户昵称 | | providers | Array<{ id?: string; providerUserId?: string; name?: string }> \| undefined | 第三方登录绑定列表 | | birthdate | string \| undefined | 出生日期 | | sub | string \| undefined | 身份主体标识(JWT sub) | ### Credentials Credentials 对象定义了用于身份认证与授权的凭据信息结构 | 属性 | 类型 | 说明 | | ------------- | ---------------- | ------------------------------------------------------------- | | token_type | string \| null | 令牌类型,常见值为 Bearer​ 等,用于指示令牌的认证方案 | | access_token | string \| null | 访问令牌,用于调用受保护资源的身份凭证 | | refresh_token | string \| null | 刷新令牌,用于在访问令牌过期后获取新的访问令牌 | | scope | string \| null | 权限范围,通常为空格分隔的字符串(如 "openid profile email") | | expires_in | number \| null | 访问令牌的有效期,单位为秒 | | expires_at | Date \| null | 访问令牌的过期时间点 | | sub | string \| null | 用户唯一标识 | | groups | string[] \| null | 用户所属组/角色列表(字符串数组) | | version | string | 凭据的版本号或来源标识 | ### LoginState LoginState 对象是对用户当前的登录态的抽象 | 属性 | 类型 | 说明 | | --------------- | ------------------- | --------------------------------------------------------------------- | | user | User | [`User 对象`](/api-reference/webv2/authentication#user-1) | | oauthLoginState | Credentials \| null | [`Credentials 对象`](/api-reference/webv2/authentication#credentials) | --- # JS SDK(V2)/云函数 > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/functions ## callFunction ### 1. 接口描述 接口功能:执行云函数 接口声明:`callFunction(object: Object): Promise` ### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | -------- | -------- | ---- | ---------------------------------------------------------------------------------------------------------------------- | | name | string | 是 | 云函数名称 | | data | object | 否 | 云函数参数 | | callback | function | 否 | 回调函数 | | parse | boolean | 否 | 设置为 true 时,当函数返回值为对象时,API 请求会返回解析对象,而不是 JSON 字符串,适用于在浏览器调试时直接查看返回结果 | 函数型云托管 额外可以传参数,传入 `type:'cloudrun'` 参数后,将调用函数型云托管 服务 | 字段 | 类型 | 必填 | 说明 | | -------- | ------------------------ | ---- | -------------------------------- | | `type` | `cloudrun` | 否 | 是否调用 基于 云托管的函数型云托管 | | `method` | `string` | 否 | HTTP 请求方法 | | `path` | `string` | 否 | HTTP 请求路径 | | `header` | `Record` | 否 | HTTP 请求头 | | `data` | `object` | 否 | HTTP 请求体 | ### 3. 输出参数 | 字段 | 类型 | 必填 | 说明 | | --------- | ------ | ---- | ------------------------ | | code | string | 否 | 状态码,操作成功则不返回 | | message | string | 否 | 错误描述 | | result | object | 否 | 云函数执行结果 | | requestId | string | 否 | 请求序列号,用于错误排查 | ### 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 ### 1. 接口描述 接口功能:调用云托管服务 接口声明:`callContainer(object: Object): Promise` ### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | ------ | ----------------------- | ---- | ------------- | | name | string | 是 | 云托管服务名 | | method | string | 否 | HTTP 请求方法 | | path | string | 否 | HTTP 请求路径 | | header | Record | 否 | HTTP 请求头 | | data | object | 否 | HTTP 请求体 | ### 3. 输出参数 | 字段 | 类型 | 必填 | 说明 | | --------- | ------ | ---- | ------------------------ | | code | string | 否 | 状态码,操作成功则不返回 | | message | string | 否 | 错误描述 | | result | object | 否 | HTTP响应体数据 | | requestId | string | 否 | 请求序列号,用于错误排查 | > 服务执行报错,将通过异常抛出 ### 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/storage ## uploadFile #### 1. 接口描述 接口功能:上传文件到文件管理服务 接口声明:`uploadFile(object: Object): Promise` :::tip 提示 1.0.1 版本后,为了提高文件上传性能,文件上传方式修改为直接上传到对象存储,为了防止在使用过程中出现 CORS 报错,需要登录 **[云开发平台](https://tcb.cloud.tencent.com/dev)** ,在 **[安全来源](https://tcb.cloud.tencent.com/dev#/env/safety-source)** 页面的 **安全域名** 添加域名。如果已有域名出现 CORS 报错,请删除安全域名,重新添加。 ::: #### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | ---------------- | ---------------- | ---- | -------------------------- | | cloudPath | string | 是 | 文件的绝对路径,包含文件名 | | filePath | HTML upload file | 是 | 要上传的文件对象 | | method | 'post' \| 'put' | 否 | 上传方法,默认为 put | | onUploadProgress | function | 否 | 上传进度回调 | :::tip 提示 `cloudPath` 为文件的绝对路径,包含文件名 foo/bar.jpg、foo/bar/baz.jpg 等,不能包含除[0-9 , a-z , A-Z]、/、!、-、\_、.、、\*和中文以外的字符,使用 / 字符来实现类似传统文件系统的层级结构。[查看详情](https://cloud.tencent.com/document/product/436/13324) ::: #### 3. 输出参数 | 字段 | 类型 | 必填 | 说明 | | --------- | ------ | ---- | --------------------------------------- | | code | string | 否 | 状态码,操作成功则不返回 | | message | string | 否 | 错误描述 | | fileID | fileID | 是 | 文件唯一 ID,用来访问文件,建议存储起来 | | requestId | string | 否 | 请求序列号,用于错误排查 | #### 4. 示例代码 ```javascript import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "xxxx-yyy", }); app .uploadFile({ cloudPath: "test-admin.jpeg", filePath: document.getElementById("file").files[0], onUploadProgress: function (progressEvent) { console.log(progressEvent); var percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total ); }, }) .then((result) => { // 上传结果 }); ``` ## getTempFileURL #### 1. 接口描述 接口功能:获取文件 CDN 下载链接 接口声明:`getTempFileURL(object: Object): Promise` #### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | -------- | -------------------- | ---- | -------------------------- | | fileList | <Array>.string | 是 | 要下载的文件 ID 组成的数组 | ##### fileList | 字段 | 类型 | 必填 | 说明 | | ------ | ------- | ---- | -------------- | | fileID | string | 是 | 文件 ID | | maxAge | Integer | 是 | 文件链接有效期 | #### 3. 输出参数 | 字段 | 类型 | 必填 | 说明 | | --------- | -------------------- | ---- | ---------------------------- | | code | string | 否 | 状态码,操作成功则为 SUCCESS | | message | string | 否 | 错误描述 | | fileList | <Array>.object | 否 | 存储下载链接的数组 | | requestId | string | 否 | 请求序列号,用于错误排查 | ##### fileList | 字段 | 类型 | 必填 | 说明 | | ----------- | ------ | ---- | ------------------------ | | code | string | 否 | 删除结果,成功为 SUCCESS | | message | string | 否 | 错误描述 | | fileID | string | 是 | 文件 ID | | tempFileURL | string | 是 | 文件访问链接 | #### 4. 示例代码 ```javascript //初始化SDK实例部分代码如上 app .getTempFileURL({ fileList: [ "cloud://jimmytest-088bef.jimmytest-088bef-1251059088/a|b测试.jpeg", ], }) .then((res) => { res.fileList.forEach((el) => { if (el.code === "SUCCESS") { console.log(el.tempFileURL); } else { //获取下载链接失败 } }); }); ``` ## deleteFile #### 1. 接口描述 接口功能:删除文件 接口声明:`deleteFile(object: Object): Promise` #### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | -------- | -------------------- | ---- | -------------------------- | | fileList | <Array>.string | 是 | 要删除的文件 ID 组成的数组 | #### 3. 输出参数 | 字段 | 类型 | 必填 | 说明 | | --------- | -------------------- | ---- | ------------------------ | | code | string | 否 | 状态码,操作成功则不返回 | | message | string | 否 | 错误描述 | | fileList | <Array>.object | 否 | 删除结果组成的数组 | | requestId | string | 否 | 请求序列号,用于错误排查 | #### fileList | 字段 | 类型 | 必填 | 说明 | | ------ | ------ | ---- | ------------------------ | | code | string | 否 | 删除结果,成功为 SUCCESS | | fileID | string | 是 | 文件 ID | #### 4. 示例代码 ```javascript app .deleteFile({ fileList: ["cloud://jimmytest-088bef/1534576354877.jpg"], }) .then((res) => { res.fileList.forEach((el) => { if (el.code === "SUCCESS") { //删除成功 } else { } }); }); ``` ## downloadFile #### 1. 接口描述 接口功能:下载文件到本地 接口声明:`downloadFile(object: Object): Promise` #### 2. 输入参数 | 字段 | 类型 | 必填 | 说明 | | ------ | ------ | ---- | ----------------- | | fileID | string | 是 | 要下载的文件的 id | #### 3. 输出参数 | 字段 | 类型 | 必填 | 说明 | | --------- | ------ | ---- | ------------------------ | | code | string | 否 | 状态码,操作成功则不返回 | | message | string | 否 | 错误描述 | | requestId | string | 否 | 请求序列号,用于错误排查 | #### 4. 示例代码 ```javascript cloudbase .downloadFile({ fileID: "cloud://aa-99j9f/my-photo.png", }) .then((res) => {}); ``` --- # JS SDK(V2)/AI > 当前文档链接: https://docs.cloudbase.net/api-reference/webv2/ai 提供云开发 AI 接入能力,快速接入大模型和 Agent。 ## 初始化 使用 js-sdk 进行初始化并登录后,通过 `.ai()` 获取 [AI](#ai-1) 实例。 ```js import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id" }); const auth = app.auth(); await auth.signInAnonymously(); const ai = app.ai(); ``` ## app.ai 初始化后,可以使用挂载至 cloudbase 实例上的 ai 方法创建 [AI](#ai-1) 实例,用于后续模型创建。 ### 使用示例 ```ts app = cloudbase.init({ env: "your-env" }); const ai = app.ai(); ``` ### 类型声明 ```ts function ai(): AI; ``` #### 返回值 > `AI` 返回新创建的 [AI](#ai-1) 实例。 ## AI 用于创建 AI 模型的类。 ### createModel() 创建指定的 AI 模型。 #### 使用示例 ```ts const model = ai.createModel("hunyuan-exp"); ``` #### 类型声明 ```ts function createModel(model: string): ChatModel; ``` 返回一个实现了 ChatModel 抽象类的模型实例,该实例提供 AI 生成文本相关能力。 ### createImageModel() 创建指定的图片生成模型。 #### 使用示例 ```ts const imageModel = ai.createImageModel("hunyuan-image"); ``` #### 类型声明 ```ts function createImageModel(provider: string): ImageModel; ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | -------- | ---- | ------ | -------------------------------------------- | | provider | 是 | string | 模型提供方名称,如 `"hunyuan-image"` | #### 返回值 返回一个 [ImageModel](#imagemodel) 实例,该实例提供 AI 图片生成相关能力。 ### bot 挂载了 Bot 类的实例,上面集合了一系列与 Agent 交互的方法。具体可参考 [Bot 类](#bot-1) 的详细文档。 #### 使用示例 ```ts const agentList = await ai.bot.list({ pageNumber: 1, pageSize: 10 }); ``` ### 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("hunyuan-exp"); const result = await model.generateText({ model: "hunyuan-turbos-latest", 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("hunyuan-exp"); // 创建模型 const res = await hy.generateText({ model: "hunyuan-turbos-latest", 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: "hunyuan-turbos-latest", 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("hunyuan-exp"); // 创建模型 const res = await hy.streamText({ model: "hunyuan-turbos-latest", 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: "hunyuan-turbos-latest", 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("hunyuan-exp"); const res = await hy.streamText({ model: "hunyuan-turbos-latest", 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: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} ``` ## ImageModel 这个类描述了 AI 图片生成模型类提供的接口。 ### generateImage() 调用大模型生成图片。 #### 使用示例 ```ts const imageModel = ai.createImageModel("hunyuan-image"); const res = await imageModel.generateImage({ model: "hunyuan-image-v3.0-v1.0.4", prompt: "一只可爱的猫咪在草地上玩耍", }); console.log(res.data[0].url); // 打印生成的图片 URL ``` #### 类型声明 ```ts function generateImage(input: HunyuanARGenerateImageInput): Promise; function generateImage(input: HunyuanGenerateImageInput): Promise; ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ------ | ---- | ------------------------ | -------------------------------------------------------------- | | input | 是 | HunyuanARGenerateImageInput \| HunyuanGenerateImageInput | 图片生成参数,详见 [HunyuanARGenerateImageInput](#hunyuanargenerateimageinput) 或 [HunyuanGenerateImageInput](#hunyuangenerateimageinput) | #### 返回值 `Promise` 或 `Promise` | 属性名 | 类型 | 说明 | | --------------- | -------------- | -------------------------------------- | | id | string | 此次请求的 id | | created | number | unix 时间戳 | | data | Array\ | 返回的图片生成内容 | | data[n].url | string | 生成的图片 url,有效期为 24 小时 | | data[n].revised_prompt | string | 原 prompt 改写后的文本(若 revise 为 false,则为原 prompt) | ## Bot 用于与 Agent 交互的类。 ### get() 获取某个 Agent 的信息。 #### 使用示例 ```ts const res = await ai.bot.get({ botId: "botId-xxx" }); console.log(res); ``` #### 类型声明 ```ts function get(props: { botId: string }); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ----------- | ---- | ------ | ------------------------ | | props.botId | 是 | string | 要获取信息的 Agent 的 id | #### 返回值 | 属性名 | 类型 | 示例 | 说明 | | --------------- | ------- | ---------------- | ------------------------ | | botId | string | `"bot-27973647"` | Agent ID | | name | string | `"信达雅翻译"` | Agent 名称 | | introduction | string | | Agent 简介 | | welcomeMessage | string | | Agent 欢迎语 | | avatar | string | | Agent 头像链接 | | background | string | | Agent 聊天背景图链接 | | isNeedRecommend | boolean | | Agent 回答后是否推荐问题 | | type | string | | Agent 类型 | ### list() 批量获取多个 Agent 的信息。 #### 使用示例 ```ts await ai.bot.list({ pageNumber: 1, pageSize: 10, name: "", enable: true, information: "", introduction: "", }); ``` #### 类型声明 ```ts function list(props: { name: string; introduction: string; information: string; enable: boolean; pageSize: number; pageNumber: number; }); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ------------------ | ---- | ------- | ------------------------ | | props.pageNumber | 是 | number | 分页下标 | | props.pageSize | 是 | number | 分页大小 | | props.enable | 是 | boolean | Agent 是否启用 | | props.name | 是 | string | Agent 名字,用于模糊查询 | | props.information | 是 | string | Agent 信息,用于模糊查询 | | props.introduction | 是 | string | Agent 简介,用于模糊查询 | #### 返回值 | 属性名 | 类型 | 示例 | 说明 | | ---------------------------- | --------------- | ---------------- | ------------------------ | | total | number | --- | Agent 总数 | | botList | `Array` | | Agent 列表 | | `botList[n].botId` | string | `"bot-27973647"` | Agent ID | | `botList[n].name` | string | `"信达雅翻译"` | Agent 名称 | | `botList[n].introduction` | string | | Agent 简介 | | `botList[n].welcomeMessage` | string | | Agent 欢迎语 | | `botList[n].avatar` | string | | Agent 头像链接 | | `botList[n].background` | string | | Agent 聊天背景图链接 | | `botList[n].isNeedRecommend` | boolean | | Agent 回答后是否推荐问题 | | `botList[n].type` | string | | Agent 类型 | ### sendMessage() 与 Agent 进行对话。响应会通过 SSE 返回,该接口的返回值对 SSE 做了不同程度的封装,开发者能根据实际需求获取到文本流和完整数据流。 #### 使用示例 ```ts const res = await ai.bot.sendMessage({ botId: "botId-xxx", history: [{ content: "你是李白。", role: "user" }], msg: "你好", }); for await (let str of res.textStream) { console.log(str); } for await (let data of res.dataStream) { console.log(data); } ``` #### 类型声明 ```ts function sendMessage(props: { botId: string; msg: string; history: Array<{ role: string; content: string; }>; }): Promise; ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | -------------------------- | ---- | ------ | -------------------------- | | props.botId | 是 | string | Agent id | | props.msg | 是 | string | 此次对话要发送的消息 | | props.history | 是 | [] | 在此次对话前发生的聊天记录 | | `props.history[n].role` | 是 | string | 本聊天信息的发送角色 | | `props.history[n].content` | 是 | string | 本聊天信息的内容 | #### 返回值 `Promise` | StreamResult 属性名 | 类型 | 说明 | | ------------------- | ---- | ---- | | textStream | `AsyncIterable` | 以流式返回的 Agent 生成文本,可参考使用示例获取到生成的增量文本。 | | dataStream | `AsyncIterable` | 以流式返回的 Agent 生成文本,可参考使用示例获取到生成的增量文本。 | | AgentStreamChunk 属性名 | 类型 | 说明 | | ------------------- | ---- | ---- | | created | number | 对话时间戳 | | record_id | string | 对话记录ID | | model | string | 大模型类型 | | version | string | 大模型版本 | | type | string | 回复类型: text: 主要回答内容,thinking: 思考过程,search: 查询结果,knowledge: 知识库 | | role | string | 对话角色,响应中固定为 assistant | | content | string | 对话内容 | | finish_reasion | string | 对话结束标志,continue 表示对话未结束,stop 表示对话结束 | | reasoning_content | string | 深度思考内容(仅deepseek-r1是不为空字符串) | | usage | object | token使用量 | | usage.prompt_tokens | number | 表示prompt的tokens数,多次返回中保持不变 | | usage.completion_tokens | number | 回答的token总数,在流式返回中,表示到目前为止所有completion的tokens总数,多次返回中持续累加 | | usage.total_tokens | number | 表示prompt_tokens和completion_tokens之和 | | knowledge_base | string[] | 对话中使用到的知识库 | | search_info | object | 搜索结果信息,需要开启联网查询 | | search_info.search_results | object[] | 搜索引文信息 | | search_info.search_results[n].index | string | 搜索引文序号 | | search_info.search_results[n].title | string | 搜索引文标题 | | search_info.search_results[n].url | string | 搜索引文链接 | ### getChatRecords() 获取聊天记录。 #### 使用示例 ```ts await ai.bot.getChatRecords({ botId: "botId-xxx", pageNumber: 1, pageSize: 10, sort: "asc", }); ``` #### 类型声明 ```ts function getChatRecords(props: { botId: string; sort: string; pageSize: number; pageNumber: number; }); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ---------------- | ---- | ------ | -------- | | props.botId | 是 | string | Agent id | | props.sort | 是 | string | 排序方式 | | props.pageSize | 是 | number | 分页大小 | | props.pageNumber | 是 | number | 分页下标 | #### 返回值 | 属性名 | 类型 | 说明 | | ---------------------------- | --------------- | ------------------- | | total | number | 对话总数 | | recordList | `Array` | 对话总数 | | `recordList[n].botId` | string | Agent ID | | `recordList[n].recordId` | string | 对话 ID,由系统生成 | | `recordList[n].role` | string | 对话中的角色 | | `recordList[n].content` | string | 对话内容 | | `recordList[n].conversation` | string | 用户标识 | | `recordList[n].type` | string | 对话数据类型 | | `recordList[n].image` | string | 对话生成的图片链接 | | `recordList[n].triggerSrc` | string | 对话发起来源 | | `recordList[n].replyTo` | string | 对话回复的记录 ID | | `recordList[n].createTime` | string | 对话时间 | ### sendFeedback() 发送对某条聊天记录的反馈信息。 #### 使用示例 ```ts const res = await ai.bot.sendFeedback({ userFeedback: { botId: "botId-xxx", recordId: "recordId-xxx", comment: "非常棒", rating: 5, tags: ["优美"], aiAnswer: "落英缤纷", input: "来个成语", type: "upvote", }, botId: "botId-xxx", }); ``` #### 类型声明 ```ts function sendFeedback(props: { userFeedback: IUserFeedback; botId: string }); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ------------------ | ---- | ------------- | ------------------------------------------------------- | | props.userFeedback | 是 | IUserFeedback | 用户反馈,详见 [IUserFeedback 类型定义](#iuserfeedback) | | props.botId | 是 | string | 将要反馈的 Agent id | ### getFeedback() 获取已存在的反馈信息。 #### 使用示例 ```ts 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", }); ``` #### 类型声明 ```ts function sendFeedback(props: { botId: string; type: string; sender: string; senderFilter: string; minRating: number; maxRating: number; from: number; to: number; pageSize: number; pageNumber: number; }); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ------------------ | ---- | ------ | -------------------------------------------------------------------------------------- | | props.botId | 是 | string | Agent id | | props.type | 是 | string | 用户反馈类型,点赞 upvote 点踩 downvote | | props.sender | 是 | string | 评论创建用户 | | props.senderFilter | 是 | string | 评论创建用户过滤关系 include:包含 exclude:不包含 equal:等于 unequal:不等于 prefix:前缀 | | props.minRating | 是 | number | 最低评分 | | props.maxRating | 是 | number | 最高评分 | | props.from | 是 | number | 开始时间戳 | | props.to | 是 | number | 结束时间戳 | | props.pageSize | 是 | number | 分页大小 | | props.pageNumber | 是 | number | 分页下标 | #### 返回值 | 属性名 | 类型 | 说明 | | ------------------------ | -------- | --------------------------------------- | | feedbackList | object[] | 反馈查询结果 | | feedbackList[n].recordId | string | 对话记录 ID | | feedbackList[n].type | string | 用户反馈类型,点赞 upvote 点踩 downvote | | feedbackList[n].botId | string | Agent ID | | feedbackList[n].comment | string | 用户评论 | | feedbackList[n].rating | number | 用户评分 | | feedbackList[n].tags | string[] | 用户反馈的标签数组 | | feedbackList[n].input | string | 用户输入的问题 | | feedbackList[n].aiAnswer | string | Agent 的回答 | | total | number | 反馈总数 | ### uploadFiles() 将云存储中的文件上传至 Agent,用于进行文档聊天。 #### 使用示例 ```ts // 上传文件 await ai.bot.uploadFiles({ botId: "botId-xxx", fileList: [ { fileId: "cloud://xxx.docx", fileName: "xxx.docx", type: "file", }, ], }); // 进行文档聊天 const res = await ai.bot.sendMessage({ botId: "your-bot-id", msg: "这个文件的内容是什么", files: ["xxx.docx"], // 文件 fileId 数组 }); for await (let text of res.textStream) { console.let(text); } ``` #### 类型声明 ```ts function uploadFiles(props: { botId: string; fileList: Array<{ fileId: "string"; fileName: "string"; type: "file"; }>; }); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | -------------------------- | ---- | ------ | ------------------- | | props.botId | 是 | string | Agent id | | props.fileList | 是 | string | 文件列表 | | props.fileList[n].fileId | 是 | string | 云存储文件 id | | props.fileList[n].fileName | 是 | string | 文件名 | | props.fileList[n].type | 是 | string | 暂时只支持 `"file"` | ### getRecommendQuestions() 获取推荐的问题。 #### 使用示例 ```ts const res = ai.bot.getRecommendQuestions({ botId: "botId-xxx", history: [{ content: "你是谁啊", role: "user" }], msg: "你好", agentSetting: "", introduction: "", name: "", }); for await (let str of res.textStream) { console.log(str); } ``` #### 类型声明 ```ts function getRecommendQuestions(props: { botId: string; name: string; introduction: string; agentSetting: string; msg: string; history: Array<{ role: string; content: string; }>; }): Promise; ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | -------------------------- | ---- | ------ | ------------ | | props.botId | 是 | string | Agent id | | props.name | 是 | string | Agent 名称 | | props.introduction | 是 | string | Agent 简介 | | props.agentSetting | 是 | string | Agent 设定 | | props.msg | 是 | string | 用户发送信息 | | props.history | 是 | Array | 历史对话信息 | | `props.history[n].role` | 是 | string | 历史信息角色 | | `props.history[n].content` | 是 | string | 历史信息内容 | #### 返回值 `Promise` | StreamResult 属性名 | 类型 | 说明 | | ------------------- | ---- | ---- | | textStream | `AsyncIterable` | 以流式返回的 Agent 生成文本,可参考使用示例获取到生成的增量文本。 | | dataStream | `AsyncIterable` | 以流式返回的 Agent 生成文本,可参考使用示例获取到生成的增量文本。 | | AgentStreamChunk 属性名 | 类型 | 说明 | | ------------------- | ---- | ---- | | created | number | 对话时间戳 | | record_id | string | 对话记录ID | | model | string | 大模型类型 | | version | string | 大模型版本 | | type | string | 回复类型: text: 主要回答内容,thinking: 思考过程,search: 查询结果,knowledge: 知识库 | | role | string | 对话角色,响应中固定为 assistant | | content | string | 对话内容 | | finish_reasion | string | 对话结束标志,continue 表示对话未结束,stop 表示对话结束 | | reasoning_content | string | 深度思考内容(仅deepseek-r1是不为空字符串) | | usage | object | token使用量 | | usage.prompt_tokens | number | 表示prompt的tokens数,多次返回中保持不变 | | usage.completion_tokens | number | 回答的token总数,在流式返回中,表示到目前为止所有completion的tokens总数,多次返回中持续累加 | | usage.total_tokens | number | 表示prompt_tokens和completion_tokens之和 | | knowledge_base | string[] | 对话中使用到的知识库 | | search_info | object | 搜索结果信息,需要开启联网查询 | | search_info.search_results | object[] | 搜索引文信息 | | search_info.search_results[n].index | string | 搜索引文序号 | | search_info.search_results[n].title | string | 搜索引文标题 | | search_info.search_results[n].url | string | 搜索引文链接 | ### createConversation() 创建与 Agent 的新对话。 #### 使用示例 ```ts const res = await ai.bot.createConversation({ botId: "botId-xxx", title: "我的对话", }): Promise; ``` #### 类型声明 ```ts function createConversation(props: IBotCreateConversation); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ----------- | ---- | ------ | -------- | | props.botId | 是 | string | Agent ID | | props.title | 否 | string | 对话标题 | #### 返回值 `Promise` 相关类型:[IConversation](#iconversation) ### getConversation() 获取对话列表。 #### 使用示例 ```ts const res = await ai.bot.getConversation({ botId: "botId-xxx", pageSize: 10, pageNumber: 1, isDefault: false, }); ``` #### 类型声明 ```ts function getConversation(props: IBotGetConversation); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ---------------- | ---- | ------- | ------------------ | | props.botId | 是 | string | Agent ID | | props.pageSize | 否 | number | 分页大小,默认 10 | | props.pageNumber | 否 | number | 分页下标,默认 1 | | props.isDefault | 否 | boolean | 是否只获取默认对话 | ### deleteConversation() 删除指定对话。 #### 使用示例 ```ts await ai.bot.deleteConversation({ botId: "botId-xxx", conversationId: "conv-123", }); ``` #### 类型声明 ```ts function deleteConversation(props: IBotDeleteConversation); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | -------------------- | ---- | ------ | --------------- | | props.botId | 是 | string | Agent ID | | props.conversationId | 是 | string | 要删除的对话 ID | ### speechToText() 语音转文字。 #### 使用示例 ```ts const res = await ai.bot.speechToText({ botId: "botId-xxx", engSerViceType: "16k_zh", voiceFormat: "mp3", url: "https://example.com/audio.mp3", }); ``` #### 类型声明 ```ts function speechToText(props: IBotSpeechToText); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | -------------------- | ---- | ------- | -------------------- | | props.botId | 是 | string | Agent ID | | props.engSerViceType | 是 | string | 引擎类型,如"16k_zh" | | props.voiceFormat | 是 | string | 音频格式,如"mp3" | | props.url | 是 | string | 音频文件 URL | | props.isPreview | 否 | boolean | 是否为预览模式 | ### textToSpeech() 文字转语音。 #### 使用示例 ```ts const res = await ai.bot.textToSpeech({ botId: "botId-xxx", voiceType: 1, text: "你好,我是AI助手", }); ``` #### 类型声明 ```ts function textToSpeech(props: IBotTextToSpeech); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | --------------- | ---- | ------- | -------------- | | props.botId | 是 | string | Agent ID | | props.voiceType | 是 | number | 语音类型 | | props.text | 是 | string | 要转换的文本 | | props.isPreview | 否 | boolean | 是否为预览模式 | ### getTextToSpeechResult() 获取文字转语音的结果。 #### 使用示例 ```ts const res = await ai.bot.getTextToSpeechResult({ botId: "botId-xxx", taskId: "task-123", }); ``` #### 类型声明 ```ts function getTextToSpeechResult(props: IBotGetTextToSpeechResult); ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | --------------- | ---- | ------- | -------------- | | props.botId | 是 | string | Agent ID | | props.taskId | 是 | string | 任务 ID | | props.isPreview | 否 | boolean | 是否为预览模式 | ## 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; } ``` ## HunyuanGenerateImageInput 混元图片生成输入参数。 ```ts interface HunyuanGenerateImageInput { model: 'hunyuan-image'; /** 用来生成图像的文本描述 */ prompt: string; /** 模型版本,支持 v1.8.1 和 v1.9,默认版本 v1.8.1 */ version?: 'v1.8.1' | 'v1.9' | (string & {}); /** 图片尺寸,默认 "1024x1024" */ size?: string; /** 仅 v1.9 支持,负向词 */ negative_prompt?: string; /** 仅 v1.9 支持,可指定风格 */ style?: '古风二次元风格' | '都市二次元风格' | '悬疑风格' | '校园风格' | '都市异能风格' | (string & {}); /** 为 true 时对 prompt 进行改写,默认为 true */ revise?: boolean; /** 生成图片个数,默认为 1 */ n?: number; /** 业务自定义水印内容,限制 16 个字符长度 */ footnote?: string; /** 生成种子,范围 [1, 4294967295] */ seed?: number; } ``` | HunyuanGenerateImageInput 属性名 | 类型 | 说明 | | -------------------------------- | --------- | ---------------------------------------------------------------------------------------------- | | model | `string` | 模型名称,固定为 `hunyuan-image` | | prompt | `string` | 用来生成图像的文本描述 | | version | `string` | 模型版本,支持 `v1.8.1` 和 `v1.9`,默认版本 `v1.8.1` | | size | `string` | 图片尺寸,默认 `"1024x1024"` | | negative_prompt | `string` | 仅 v1.9 支持,负向词 | | style | `string` | 仅 v1.9 支持,可指定风格:古风二次元风格、都市二次元风格、悬疑风格、校园风格、都市异能风格 | | revise | `boolean` | 为 true 时对 prompt 进行改写,默认为 true | | n | `number` | 生成图片个数,默认为 1 | | footnote | `string` | 业务自定义水印内容,限制 16 个字符长度 | | seed | `number` | 生成种子,范围 [1, 4294967295] | ## HunyuanGenerateImageOutput 混元图片生成输出。 ```ts interface HunyuanGenerateImageOutput { /** 此次请求的 id */ id: string; /** unix 时间戳 */ created: number; /** 返回的图片生成内容 */ data: Array<{ /** 生成的图片 url,有效期为 24 小时 */ url: string; /** 原 prompt 改写后的文本。若 revise 为 false,则为原 prompt */ revised_prompt?: string; }>; } ``` | HunyuanGenerateImageOutput 属性名 | 类型 | 说明 | | --------------------------------- | --------------- | ----------------------------------------------------------------- | | id | `string` | 此次请求的 id | | created | `number` | unix 时间戳 | | data | `Array` | 返回的图片生成内容 | | data[n].url | `string` | 生成的图片 url,有效期为 24 小时 | | data[n].revised_prompt | `string` | 原 prompt 改写后的文本。若 revise 为 false,则为原 prompt | ## HunyuanARGenerateImageInput 混元图片生成 v3.0 输入参数,支持自定义宽高比。 ```ts interface HunyuanARGenerateImageInput { /** 模型名称:hunyuan-image-v3.0-v1.0.4(推荐)或 hunyuan-image-v3.0-v1.0.1 */ model: 'hunyuan-image-v3.0-v1.0.4' | 'hunyuan-image-v3.0-v1.0.1'; /** 生成图片使用的文本,不超过 8192 字符 */ prompt: string; /** * 图片尺寸,格式 "${宽}x${高}",默认 "1024x1024" * hunyuan-image-v3.0-v1.0.4:宽高范围 [512, 2048],面积不超过 1024x1024 * hunyuan-image-v3.0-v1.0.1:支持固定尺寸列表 */ size?: string; /** 生成种子,仅当生成图片数为 1 时生效,范围 [1, 4294967295] */ seed?: number; /** 业务自定义水印内容,限制 16 字符,生成在图片右下角 */ footnote?: string; /** 是否对 prompt 改写,默认开启。改写会增加约 30s 耗时 */ revise?: { value: boolean }; /** 改写是否开启 thinking 模式,默认开启。开启后效果提升但耗时增加(最大 60s) */ enable_thinking?: { value: boolean }; } ``` | HunyuanARGenerateImageInput 属性名 | 类型 | 说明 | | ---------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------- | | model | `string` | 模型名称,`hunyuan-image-v3.0-v1.0.4`(推荐)或 `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) | ## 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 | --- # 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` 中接收和使用。详细说明请参考 [跨端开发指南](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)。 - `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 function genAdapter(options) { const adapter: SDKAdapterInterface = { // 其他适配器配置... 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) --- # 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) **@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 | | `secretId` | `string` | 否 | 腾讯云 API 固定密钥对 `secretId`,前往 [腾讯云控制台/API密钥管理](https://console.cloud.tencent.com/cam/capi) 生成 | | `secretKey` | `string` | 否 | 腾讯云 API 固定密钥对 `secretKey`,前往 [腾讯云控制台/API密钥管理](https://console.cloud.tencent.com/cam/capi) 生成 | | `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** 使用管理员权限,不同环境下的鉴权方式如下: ### 云函数环境 在云函数环境中,**无需手动配置鉴权参数**。 ```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.js 环境 在 Node.js 环境中,需要配置以下任意一种鉴权方式: 前往 [腾讯云控制台/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", }) ``` --- # 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/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 | <Array>.{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("hunyuan-exp"); ``` #### 类型声明 ```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("hunyuan-exp"); const result = await model.generateText({ model: "hunyuan-turbos-latest", 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("hunyuan-exp"); // 创建模型 const res = await hy.generateText({ model: "hunyuan-turbos-latest", 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: "hunyuan-turbos-latest", 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("hunyuan-exp"); // 创建模型 const res = await hy.streamText({ model: "hunyuan-turbos-latest", 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: "hunyuan-turbos-latest", 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("hunyuan-exp"); const res = await hy.streamText({ model: "hunyuan-turbos-latest", 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: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} // {created: 1723013866, id: "a95a54b5c5d2144eb700e60d0dfa5c98", model: "hunyuan-turbos-latest", version: "202404011000", choices: Array(1), …} ``` ## ImageModel 这个类描述了 AI 图片生成模型类提供的接口。 ### generateImage() 调用大模型生成图片。 #### 使用示例 ```ts const imageModel = ai.createImageModel("hunyuan-image"); const res = await imageModel.generateImage({ model: "hunyuan-image-v3.0-v1.0.4", prompt: "一只可爱的猫咪在草地上玩耍", }); console.log(res.data[0].url); // 打印生成的图片 URL ``` #### 类型声明 ```ts function generateImage(input: HunyuanARGenerateImageInput): Promise; function generateImage(input: HunyuanGenerateImageInput): Promise; ``` #### 参数 | 参数名 | 必填 | 类型 | 说明 | | ------ | ---- | ------------------------ | -------------------------------------------------------------- | | input | 是 | HunyuanARGenerateImageInput \| HunyuanGenerateImageInput | 图片生成参数,详见 [HunyuanARGenerateImageInput](#hunyuanargenerateimageinput) 或 [HunyuanGenerateImageInput](#hunyuangenerateimageinput) | #### 返回值 `Promise` 或 `Promise` | 属性名 | 类型 | 说明 | | --------------- | -------------- | -------------------------------------- | | id | string | 此次请求的 id | | created | number | unix 时间戳 | | data | Array\ | 返回的图片生成内容 | | data[n].url | string | 生成的图片 url,有效期为 24 小时 | | data[n].revised_prompt | string | 原 prompt 改写后的文本(若 revise 为 false,则为原 prompt) | ## 类型定义 ### 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; }; ``` ### HunyuanGenerateImageInput 混元图片生成输入参数。 ```ts interface HunyuanGenerateImageInput { model: 'hunyuan-image'; /** 用来生成图像的文本描述 */ prompt: string; /** 模型版本,支持 v1.8.1 和 v1.9,默认版本 v1.8.1 */ version?: 'v1.8.1' | 'v1.9' | (string & {}); /** 图片尺寸,默认 "1024x1024" */ size?: string; /** 仅 v1.9 支持,负向词 */ negative_prompt?: string; /** 仅 v1.9 支持,可指定风格 */ style?: '古风二次元风格' | '都市二次元风格' | '悬疑风格' | '校园风格' | '都市异能风格' | (string & {}); /** 为 true 时对 prompt 进行改写,默认为 true */ revise?: boolean; /** 生成图片个数,默认为 1 */ n?: number; /** 业务自定义水印内容,限制 16 个字符长度 */ footnote?: string; /** 生成种子,范围 [1, 4294967295] */ seed?: number; } ``` | HunyuanGenerateImageInput 属性名 | 类型 | 说明 | | -------------------------------- | --------- | ---------------------------------------------------------------------------------------------- | | model | `string` | 模型名称,固定为 `hunyuan-image` | | prompt | `string` | 用来生成图像的文本描述 | | version | `string` | 模型版本,支持 `v1.8.1` 和 `v1.9`,默认版本 `v1.8.1` | | size | `string` | 图片尺寸,默认 `"1024x1024"` | | negative_prompt | `string` | 仅 v1.9 支持,负向词 | | style | `string` | 仅 v1.9 支持,可指定风格:古风二次元风格、都市二次元风格、悬疑风格、校园风格、都市异能风格 | | revise | `boolean` | 为 true 时对 prompt 进行改写,默认为 true | | n | `number` | 生成图片个数,默认为 1 | | footnote | `string` | 业务自定义水印内容,限制 16 个字符长度 | | seed | `number` | 生成种子,范围 [1, 4294967295] | ### HunyuanGenerateImageOutput 混元图片生成输出。 ```ts interface HunyuanGenerateImageOutput { /** 此次请求的 id */ id: string; /** unix 时间戳 */ created: number; /** 返回的图片生成内容 */ data: Array<{ /** 生成的图片 url,有效期为 24 小时 */ url: string; /** 原 prompt 改写后的文本。若 revise 为 false,则为原 prompt */ revised_prompt?: string; }>; } ``` | HunyuanGenerateImageOutput 属性名 | 类型 | 说明 | | --------------------------------- | --------------- | ----------------------------------------------------------------- | | id | `string` | 此次请求的 id | | created | `number` | unix 时间戳 | | data | `Array` | 返回的图片生成内容 | | data[n].url | `string` | 生成的图片 url,有效期为 24 小时 | | data[n].revised_prompt | `string` | 原 prompt 改写后的文本。若 revise 为 false,则为原 prompt | ### HunyuanARGenerateImageInput 混元图片生成 v3.0 输入参数,支持自定义宽高比。 ```ts interface HunyuanARGenerateImageInput { /** 模型名称:hunyuan-image-v3.0-v1.0.4(推荐)或 hunyuan-image-v3.0-v1.0.1 */ model: 'hunyuan-image-v3.0-v1.0.4' | 'hunyuan-image-v3.0-v1.0.1'; /** 生成图片使用的文本,不超过 8192 字符 */ prompt: string; /** * 图片尺寸,格式 "${宽}x${高}",默认 "1024x1024" * hunyuan-image-v3.0-v1.0.4:宽高范围 [512, 2048],面积不超过 1024x1024 * hunyuan-image-v3.0-v1.0.1:支持固定尺寸列表 */ size?: string; /** 生成种子,仅当生成图片数为 1 时生效,范围 [1, 4294967295] */ seed?: number; /** 业务自定义水印内容,限制 16 字符,生成在图片右下角 */ footnote?: string; /** 是否对 prompt 改写,默认开启。改写会增加约 30s 耗时 */ revise?: { value: boolean }; /** 改写是否开启 thinking 模式,默认开启。开启后效果提升但耗时增加(最大 60s) */ enable_thinking?: { value: boolean }; } ``` | HunyuanARGenerateImageInput 属性名 | 类型 | 说明 | | ---------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------- | | model | `string` | 模型名称,`hunyuan-image-v3.0-v1.0.4`(推荐)或 `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) | ### 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/其他参考/更新日志 > 当前文档链接: https://docs.cloudbase.net/api-reference/server/node-sdk/changelog ## v3.15.0 - [add] `callContainer` 支持 `GET` 等更多 `Method` 方法 ## v3.14.0 - [add] 新增 `MySQL数据库` 操作方法 ## v3.4.0 - [add] 新增 `tcb.SYMBOL_DEFAULT_ENV` 用于指定默认环境 ## v3.1.0 - [add] 新增 [sendTemplateNotification](/api-reference/server/node-sdk/templateNotify) 用于发送消息通知 ## v3.0.0 功能特性变更: 1. [change] Node.js >=12,即不再保证更低版本的兼容性,产品特性上控制台可以考虑不再支持新增 Node.js12 以下的版本 2. [change] 云函数中,初始化未指定环境 ID,使用当前云函数环境 ID,原来使用的是云开发默认环境,即不再需要指定 `init({env: SYMBOL_CURRENT_ENV})`,指定了也没关系 3. [change] 非云函数环境下默认开启 `keepalive` 其他变更: 1. [refactor] 整体重构优化代码 2. [refactor] TS 导出类型重构,完善类型导出,DB 类型直接导出 `@cloudbase/database` 的类型定义 3. [refactor] 更新 `axios、jsonwebtoken、xml2js、@cloudbase/signature-nodejs` 等依赖库版本 4. [refactor] 移除 `request` 包,改为原生实现,解决 `request` 不维护问题,优化请求处理逻辑 5. [test] 补充优化单元测试用例,提升单元测试覆盖率 ## v2.3.0 - [add] 新增 [updateAndReturn](/api-reference/server/node-sdk/database#updateandreturn) 接口 - [refactor] 重构 aggregate 接口 ## v2.2.5 - [add] 新增 [getEndUserInfo](/api-reference/server/node-sdk/auth#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](/api-reference/server/node-sdk/env#getCloudbaseContext) - [add] 新增 env 参数校验逻辑,若 init 时未指定 env,则 warning 提示使用默认环境, 若指定 env 但请求时发现为空,则抛错处理 - [add][createticket](/api-reference/server/node-sdk/auth#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 // 环境配置管理 app.commonService // 通用服务管理 ``` 每个模块提供完整的资源管理能力,详见各功能模块的 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 | 来源 | | Alias | 是 | String | 环境别名 | | Status | 是 | String | 环境状态 | | CreateTime | 是 | String | 创建时间 | | UpdateTime | 是 | String | 更新时间 | | PackageId | 是 | String | 环境套餐 ID | | PackageName | 是 | String | 套餐名 | | Databases | 是 | Array | 数据库资源详情 | | Storages | 是 | Array | 存储资源详情 | | Functions | 是 | Array | 函数资源详情 | | LogServices | 是 | Array | 日志资源详情 | ### 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 env in EnvList) { // 遍历envList console.log(env) } } 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() ``` ## 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() ``` --- # Manager Node SDK/模块管理/用户管理 > 当前文档链接: https://docs.cloudbase.net/api-reference/manager/node/user ## 获取用户列表 ### 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/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