# 云开发迁移云托管指南

如果你想将自己的项目从「云开发」迁移到「云托管」,你可以仔细阅读此文档,理解两者的不同点以及相似特性,在迁移之前充分调研和判断;同时我们也提供了一些实践代码,可以供你参考。

# 一、基础信息介绍

# 1. 云托管和云开发的共性

云托管以容器服务为核心,立足业务需要提供各种周到的资源,比如 TDMysql 数据库,对象存储等。所有的资源和产品设计都是围绕如何更好、更方便的使用容器服务,打造一站式的后端服务平台。

云开发以云函数提供的计算能力为核心,围绕其打造业务开发需要的服务产品,比如 Mongo 协议的数据库;整合对象存储以及相关的存储处理解决方案;扩展能力。云开发产品设计的主旨是,打造一站式后端和强效的中间件平台。

# 2. 区别和联系

# (1) 联系

  • 执行环境都是容器实例
  • 都是根据配置信息进行单一实例资源分配,并启动和管理容器。
  • 都全部负责运行容器的创建、管理和删除清理操作,用户没有权限对其进行管理。

# (2) 区别

  • 云函数需要用户提供能在指定镜像中运行的代码,云托管容器服务需要用户提供镜像(或者镜像的原料)
  • 云函数的镜像是定制的,不可更改;云托管的镜像是用户自己提供的,可随意定制
  • 云函数的容器实例上层是 SCF 的服务层,控制每次调用对应的实例以及环境。云托管的容器实例上层直接是网络,没有严格限制更加自由。
  • 云函数的容器实例有重用,实例可以服务多次 SCF 调用;云托管的容器实例运行直达,有常驻运行形态。

# 二、迁移步骤指导

# 1. 盘点云开发环境资源

首先你需要对云开发环境使用到的资源进行盘点,盘点过后,我们需要一一对每个资源点进行迁移操作,一般有如下:

  • 云函数
  • 云数据库
  • 云存储
  • 静态资源存储
  • CMS管理系统

在盘点自己使用了哪些资源后,我们便可以逐一进行迁移了。

# 2. 云函数

「云函数」需要迁移到「云托管服务」中,你可以根据自己的需要,一个云函数对应建立一个服务,也可以将多个云函数集中起来建立同一个服务。划分依据一般按照自己的业务来决定。

我们盘点开发者在云函数使用配置这里有以下几种情况:

  1. 单一函数型计算,一个环境有几十个函数同时存在,每个云函数代表一个接口。
  2. 复合型函数计算,多个接口按照业务封装在同一个云函数中,一个环境只有几个云函数。
  3. 框架型项目,将 express 等类似框架直接塞到一个云函数中,一个环境只有一个云函数。

在这里我们的建议:

# (1) 单一函数型计算

根据业务盘点环境中的云函数,分门别类的进行整合,划归为多个大一些复合型函数计算

# (2) 复合型函数计算

根据使用的云函数语言,重新编写入口代码,以下以 nodejs 举一个例子,模拟云函数根据函数名路由到各个执行函数中。具体代码示例可以点此下载

const express = require('express')
const app = express()
app.use(express.json())

app.all('/:func', async (req, res) => {
  const { func } = req.params
  const event = req.method === 'POST' ? req.body : req.query
  const result = {
    errMsg: 'cloud.callFunction:ok',
    requestID: req.headers['x-wx-call-id']
  }
  try {
    // 在这里直接 require 云函数文件,需要将所有云函数代码放置在项目 functions 文件夹中,根据云函数名字来命名
    const handler = (require(`./functions/${func}/config.json`).handler || 'index.main').split('.')
    // 优先读取配置信息,如果没有配置信息则按照默认的 index.main 来执行
    result.result = await require(`./functions/${func}/${handler[0]}`)[handler[1]](event, req.headers)
  } catch (e) {
    const error = e.toString()
    result.errCode = -1
    if (error.indexOf('Cannot find module') !== -1) {
      result.errMsg = `Error: cloud.callFunction:fail Error: errCode: -501000  | errMsg: FunctionName parameter could not be found. (callId: ${req.headers['x-wx-call-id']}) (trace: 16:9:53 start->16:9:54 system error (Error: errCode: -501000  | errMsg: FunctionName parameter could not be found.), abort`
    } else {
      result.errCode = error
    }
  }
  res.send(result)
})

const port = process.env.PORT || 80

app.listen(port, () => {
  console.log('启动成功', port)
})

相应的在小程序端调用方式也需要变化,按照上述服务配置,小程序 app.js 增加一个方法,内容如下:

App({
  async callFunction (obj) {
    const result = await wx.cloud.callContainer({
      config: {
        env: 'prod-xxxxx' // 云托管环境ID
      },
      path: `/${obj.name}`,
      data: obj.data,
      header: {
        'X-WX-SERVICE': 'service' // 服务名
      }
    })
    return result.data
  }
})

业务调用方式就变成如下效果了:

# (3) 框架型项目

这种类型,有部分开发者之前在使用云开发云函数的时候,也是将其看成普通服务器形式来用的,这种方式效率很低,本身也不适合云函数。可以直接平移到云托管成为一个服务。调用方式也基本平移,甚至可以更简化,因为 callcontainer 比callfunction更适合这种类型。

# (4) wx-server-sdk

云函数最重要的函数模块wx-server-sdk,在迁移到云托管后基本就失效了,包括对数据库、存储以及云调用的方法,在云托管中都有更灵活的使用方式,所以涉及模块资源方法的云函数,不是直接复制粘贴了事,需要对代码的方法调用也要做一些更改,在这里针对各种资源的更改将在各资源下面专门讲解。

另一方面就是云函数自带用户信息鉴权,一般我们会 cloud.getWXContext() 形式获取用户 openid 等信息,在云托管中直接从请求的 req.headers 中读取,所以涉及到的也要改一下。

最后,在小程序端 wx.cloud.CloudID 处理的开放信息 CloudID,云函数会自动解析为开放数据,但是云托管基于语言框架灵活性考虑不做这种封装,所以也需要你自己通过 API 完成信息置换,可以参考此文档来操作。

注意:云函数的执行是一次执行对应一次请求,所以在业务中无需考虑数据库连接数问题,但放在云托管中就需要考虑这一点

# 2. 静态资源存储

「云开发」静态资源存储和「云托管」提供的是同一种形态产品,只不过隶属的环境不同,所以这里的迁移比较好进行。

首先迁移存储资源,可以参考此文档,使用工具登录「云开发」和「云托管」的存储资源(云开发环境也可以使用,存储桶的名称可以从 CloudID 中获取)。

将存储资源迁移完成后,保证两边资源同步,此时就可以对客户端替换资源链接了,将云开发静态默认域名,替换成云托管静态默认域名。

如果你对外是自定义域名,则可以在云托管默认域名先配置同样的域名信息,然后在直接修改域名的 DNS 解析,达到无缝迁移的效果。

# 3. 云存储

「云开发」的云存储和「云托管」的对象存储提供的也是同一种形态产品,只不过隶属的环境不同。

在资源层面的迁移过程,和静态资源存储的操作一致。(如果云开发云存储的桶信息找不到,可以使用云开发CLI完成资源下载,然后用 COS 工具上传到云托管对象存储)

由于云存储属于业务生产型资源,所以在业务代码层面也要做一些改进。

云托管中,可以使用COS-SDK来替换掉 wx-server-sdk 中的存储方法。

小程序端的方法仍然可以正常使用,但是需要注意的是,要确保执行的环境是云托管环境,特别是一个小程序使用多个环境 ID 的情况。

# 4. 云数据库

「云开发」的云数据库属于 mongo 协议的 flexdb 数据库,云托管没有与其对应的数据库类型。目前有两个解决方法:

  1. 保留云开发数据库形态,但由于 wx-server-sdk 没有提供外部环境初始化能力,因此可以使用 cloudbase-node-sdk 对原有云开发数据库进行操作。
  2. 不保留云开发数据库形态,业务上可以重新构建代码,迁移到云托管 mysql 数据库,或者可以在腾讯云购买 mongo 数据库,实现大体相等的非关系型数据库资源替换,购买配置可以参考此文档

# 5. CMS管理系统

「CMS管理系统」是云开发环境定制的项目,完全迁移难度巨大。建议放弃迁移,使用其他数据库基础在云托管支持范围中的内容管理系统。

# 6. 云调用

云函数云调用主要靠 wx-server-sdk 封装方法实现,云托管可以直接开启开放接口服务,在容器服务内直接无 token 调用 API 接口,就能完成云调用,所以云函数相关代码可以改造一下,使用 request 封装即可。具体可以参考此文档

# 三、总结

一种服务计算形态到另一种的迁移,本身就不是一个简单复制粘贴的过程,整个服务底层以及提供逻辑都变化了,代码改写是不可避免的。

在迁移之前做好充分规划,并充分考虑到线上生产环境的影响。盲目修正迁移,采取补救的形式来硬割裂的转换行为是很不妥当的,凡事都需要计划周密,接驳时间点,执行流程,备用方案都要考虑仔细,充分验证。