# 云开发迁移云托管指南

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

## 一、基础信息介绍

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

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

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

### 2. 区别和联系

#### (1) 联系

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

#### (2) 区别

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

## 二、迁移步骤指导

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

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

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

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

### 2. 云函数

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

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

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

在这里我们的建议：

#### (1) 单一函数型计算

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

#### (2) 复合型函数计算

根据使用的云函数语言，重新编写入口代码，以下以 nodejs 举一个例子，模拟云函数根据函数名路由到各个执行函数中。具体代码示例可以[点此下载](https://7765-web-9gikcbug35bad3a8-1304825656.tcb.qcloud.la/real/tcb-run.zip)

``` js
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 增加一个方法，内容如下：

``` 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
  }
})
```

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

![](https://res8.wxqcloud.qq.com.cn/wxdoc/e54096a5-5d88-4999-a38a-af7b940726ba.png)

#### (3) 框架型项目

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

#### (4) wx-server-sdk

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

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

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

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

### 2. 静态资源存储

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

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

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

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

### 3. 云存储

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

在资源层面的迁移过程，和静态资源存储的操作一致。（如果云开发云存储的桶信息找不到，可以使用[云开发 CLI](https://docs.cloudbase.net/cli-v1/storage#%E4%B8%8B%E8%BD%BD%E5%85%A8%E9%83%A8%E6%96%87%E4%BB%B6)完成资源下载，然后用 COS 工具上传到云托管对象存储）

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

云托管中，可以使用[COS-SDK](/minigame/dev/wxcloudrun/src/development/storage/service/cos-sdk)来替换掉 wx-server-sdk 中的存储方法。

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

### 4. 云数据库

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

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

### 5. CMS 管理系统

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

### 6. 云调用

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

## 三、总结

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

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