上一篇文章我们在腾讯云 Serverless 上部署了一个最基本的 Nest 框架,现在让它能连接上 Mysql 数据库吧
因为没有购买 RDS 的原因,所以这篇文章就介绍云函数怎么使用内网连接在腾讯云 ECS 上的 Mysql 服务,前提是云服务器与云函数在同一个地区以及在同一个子网环境下
# 云服务与 Mysql 服务
# 查看云服务器的内网 IP
买云服务器都会有一个外网 IP 和一个内网 IP, 外网 IP 用来和外界交互,而内网 IP 就是用来和内网环境下的服务交互了,简单说就是同是在腾讯云购买的服务,如果在同一个地域和同一个子网下,那么可以使用内网交互,速度比外网交互快得多,延迟也更低。
查看内网可以使用 ifconfig 命令
| $ ifconfig | |
| eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 | |
| inet 172.17.16.13 netmask 255.255.240.0 broadcast 172.17.31.255 | |
| inet6 fe80::5054:ff:fecf:7581 prefixlen 64 scopeid 0x20<link> | |
| ether 52:54:00:cf:75:81 txqueuelen 1000 (Ethernet) | |
| RX packets 158609312 bytes 30728239812 (28.6 GiB) | |
| RX errors 0 dropped 0 overruns 0 frame 0 | |
| TX packets 154639972 bytes 57536005501 (53.5 GiB) | |
| TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 | |
| lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 | |
| inet 127.0.0.1 netmask 255.0.0.0 | |
| inet6 ::1 prefixlen 128 scopeid 0x10<host> | |
| loop txqueuelen 1000 (Local Loopback) | |
| RX packets 89181934 bytes 97925334102 (91.2 GiB) | |
| RX errors 0 dropped 0 overruns 0 frame 0 | |
| TX packets 89181934 bytes 97925334102 (91.2 GiB) | |
| TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 | 
这里查到内网 IP 是 172.17.16.13
# 创建 Mysql 用户名及授权
在云服务上已安装好 Mysql8, 安装方式见 Ubuntu 二进制方式安装 Mysql-8.0.21 (x86_64) 详细记录
使用 root 用户登录创建一个名为 manager 的库
| mysql> create database manager default charset utf8mb4 collate utf8mb4_general_ci; | |
| Query OK, 1 row affected, 2 warnings (0.01 sec) | 
创建一个新用户 managerserverless 并授权拥有该库的一切权限
| mysql> create user 'managerserverless'@'%' identified by '123456'; | |
| Query OK, 0 rows affected (0.05 sec) | |
| mysql> grant all privileges on manager.* to 'managerserverless'@'%' with grant option; | |
| Query OK, 0 rows affected (0.01 sec) | |
| mysql> flush privileges; | |
| Query OK, 0 rows affected (0.05 sec) | 
尝试使用 managerserverless 用户登录一下
| ➜ /root mysql -umanagerserverless -p123456 | |
| mysql: [Warning] Using a password on the command line interface can be insecure. | |
| Welcome to the MySQL monitor. Commands end with ; or \g. | |
| Your MySQL connection id is 10411 | |
| Server version: 8.0.21 MySQL Community Server - GPL | |
| Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. | |
| Oracle is a registered trademark of Oracle Corporation and/or its | |
| affiliates. Other names may be trademarks of their respective | |
| owners. | |
| Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. | |
| mysql> show databases; | |
| +--------------------+ | |
| | Database | | |
| +--------------------+ | |
| | information_schema | | |
| | manager | | |
| +--------------------+ | |
| 2 rows in set (0.01 sec) | 
至此云服务器和 Mysql 数据库就准备好了,接下来修改 Nest 项目,集成 Typeorm 到 Nest 中
# Nest9.0 集成 Typeorm
# 安装 Typeorm
| $ npm install --save @nestjs/typeorm typeorm mysql2 | 
# 使用腾讯云 serverless 需要注意的一点 - 层管理
腾讯云 serverless 的云函数只支持总代码量小于 250MB, 这个是要算 node_modules 文件夹的,随着项目开发的逐渐进展,所要安装的库文件越来越多越来越大,250MB 是远远不够的.
其实云函数所应用到的库文件只包括 package.json 的 dependencies 部分, devDependencies 完全无用,所以要另外创建一个文件夹用来放只安装 dependencies 的 node_modules 文件夹.
创建一个新的文件夹,将 package.json 复制进去,然后执行 npm install --production 即可
随后在腾讯云 Serverless 控制台 - 层中选择 manager-layer 新建一个版本,将该文件夹整个上传就好了.
# 修改.env 和 serverless.yml 文件
.env 添加 Mysql 配置项
| #Mysql配置 | |
| MYSQL_HOST=172.17.16.13 | |
| MYSQL_USER=managerserverless | |
| MYSQL_PASSWORD=123456 | |
| MYSQL_DATABASE=manager | 
serverless.yml 修改 layer 的版本号和 faas 的 environments 部分
| app: manager | |
| stage: dev | |
| component: http | |
| name: manager | |
| inputs: | |
| src: | |
| dist: ./ | |
| hook: npm run build | |
| exclude: | |
|       - .env | |
|       - node_modules | |
| src: ./ | |
| faas: | |
| runtime: Nodejs16.13 | |
| framework: nestjs | |
| name: '${name}-${stage}' | |
| eip: false | |
| timeout: 3 | |
| memorySize: 512 | |
| tags: [] | |
| environments: | |
| - key: MYSQL_HOST | |
| value: ${env:MYSQL_HOST} | |
| - key: MYSQL_USER | |
| value: ${env:MYSQL_USER} | |
| - key: MYSQL_DATABASE | |
| value: ${env:MYSQL_DATABASE} | |
| - key: MYSQL_PASSWORD | |
| value: ${env:MYSQL_PASSWORD} | |
| vpc: # 私有网络配置 | |
| vpcId: 'vpc-b5776770' # 私有网络的 Id | |
| subnetId: 'subnet-asy3z93z' # 子网 ID | |
| layers: | |
| - name: manager-layer | |
| version: 3 | |
| apigw: | |
| protocols: | |
|       - http | |
|       - https | |
| timeout: 60 | |
| customDomains: [] | |
| region: ${env:REGION} | 
# 集成 Typeorm
# 第一步。集成不同环境的配置变量
应用程序通常在不同的环境中运行。根据环境的不同,应该使用不同的配置设置。
例如,本地开发时访问本地数据库,部署到 serverless 后访问云服务器上的数据库服务.
那么就需要将线上环境和本地环境区分开,采取不同的环境变量配置
安装 nestjs-config 和 cross-env
| $ yarn add nestjs-config | |
| $ yarn add cross-env -D | 
修改 package.json 的 scripts 项
| "dev": "npm run start:dev", | |
| "start:dev": "cross-env NODE_ENV=local nest start --watch", | 
修改 app.module.ts , 集成 nestjs-config 和 Typeorm
| import { Module } from '@nestjs/common'; | |
| import { AppController } from './app.controller'; | |
| import { AppService } from './app.service'; | |
| import { HttpModule } from '@nestjs/axios'; | |
| import { TypeOrmModule } from '@nestjs/typeorm'; | |
| import {ConfigModule,ConfigService } from 'nestjs-config'; | |
| import * as path from 'path'; | |
| const ENV = process.env.NODE_ENV; | |
| @Module({ | |
| imports: [ | |
|     /* | |
| * 配置文件从 src/config 下的 *.ts 或 *.js 文件取,配置文件需要导出为一个模块 | |
| * 如果环境变量 NODE_ENV 为 local, 则会取.env.local 里的环境变量,如果 NODE_ENV 不存在,则取.env 文件的环境变量 | |
| */ | |
| ConfigModule.load(path.resolve(__dirname,'config','**/!(*.d).{ts,js}'),{ | |
| path:path.resolve(process.cwd(),!ENV ? '.env' : `.env.${ENV}`) | |
| }), | |
|     HttpModule, | |
|     /* | |
| * 采用异步的方式集成 Typeorm, 配置文件在 src/config/database.ts 中 | |
| */ | |
| TypeOrmModule.forRootAsync({ | |
| useFactory: async (config: ConfigService) => config.get('database'), | |
| inject: [ConfigService], | |
| }), | |
| ], | |
| controllers: [AppController], | |
| providers: [AppService], | |
| }) | |
| export class AppModule {} | 
我们在 src 目录下创建 config 目录,里面添加一个 database.ts 文件
| #database.ts | |
| export default { | |
| type: 'mysql', | |
| host: process.env.MYSQL_HOST, | |
| username: process.env.MYSQL_USER, | |
| password: process.env.MYSQL_PASSWORD, | |
| database: process.env.MYSQL_DATABASE, | |
| logging: process.env.MYSQL_LOGGING === 'true', | |
| synchronize: process.env.MYSQL_SYNC === 'true', | |
| autoLoadEntities: true, | |
| entities: ['dist/**/*.entity{.ts,.js}'], | |
| port: parseInt(process.env.MYSQL_PORT), | |
| keepConnectionAlive: true | |
| }; | 
Typeorm 关于 Mysql 的配置项说明
同时创建 .env.local 文件,添加相应配置项 ( .env 里同样要添加)
以下是我的配置
| //.env.local | |
| #Mysql配置 | |
| MYSQL_HOST=127.0.0.1 | |
| MYSQL_USER=managerserverless | |
| MYSQL_PASSWORD=123456 | |
| MYSQL_PORT=33060 | |
| MYSQL_DATABASE=manager | |
| MYSQL_LOGGING=true | |
| MYSQL_SYNC=true | 
这样就可以实现通过一个环境变量 NODE_ENV 切换不同环境的配置文件了,如果后期再加一个环境也很方便
# 第二步。编写第一个 entity
创建一个 options 表,可以使用 Typeorm 的 entity 自动创建相应的表.
首先创建 options 模块,控制器和 service
| $ nest g module options | |
| $ nest g co options | |
| $ nest g s options | 
在生成的 src/options 下创建 entity 文件夹,创建 options.entity.ts 文件
| import {Entity, Column, PrimaryGeneratedColumn,Index} from 'typeorm'; | |
| @Entity("options") | |
| export class Options { | |
| @PrimaryGeneratedColumn({ | |
| unsigned:true, | |
| }) | |
| id: number; | |
| @Index() | |
| @Column({ | |
| type: "varchar", | |
| length: 255, | |
| default: "", | |
| comment: "配置key" | |
| }) | |
| key: string; | |
| @Column({ | |
| type: "varchar", | |
| length: 2000, | |
| default: "", | |
| comment: "配置值" | |
| }) | |
| value: string; | |
| @Index() | |
| @Column({ | |
| type: "varchar", | |
| length: 255, | |
| default: "", | |
| comment: "配置前缀" | |
| }) | |
| prefix: string; | |
| } | 
接下来执行 npm run dev 提示如下信息,表自动就创建好了
| query: SELECT VERSION() AS `version` | |
| query: START TRANSACTION | |
| query: SELECT DATABASE() AS `db_name` | |
| query: SELECT `TABLE_SCHEMA`, `TABLE_NAME` FROM `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA` = 'manager' AND `TABLE_NAME` = 'options' | |
| query: SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = 'manager' AND `TABLE_NAME` = 'typeorm_metadata' | |
| query: CREATE TABLE `options` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT, `key` varchar(255) NOT NULL COMMENT '配置key' DEFAULT '', `value` varchar(2000) NOT NULL COMMENT '配置值' DEFAULT '', `prefix` varchar(255) NOT NULL COMMENT '配置前缀' DEFAULT '', INDEX `IDX_4d886cd47aa57e872fbf2ab3c1` (`key`), INDEX `IDX_5fc0b299d23181f6f2384bb735` (`prefix`), PRIMARY KEY (`id`)) ENGINE=InnoDB | |
| query: COMMIT | 
至此,配置环境变量和集成 Typeorm 已经完成,现在项目可以访问到服务器上的 Mysql 数据库了.
# 第三步。编写 options 接口操作数据库
接下来添加几个接口验证一下
修改 options.module.ts
| /* | |
| * options.module.ts | |
| */ | |
| import { Module } from '@nestjs/common'; | |
| import { OptionsController } from './options.controller'; | |
| import { OptionsService } from './options.service'; | |
| import { Options } from "./entity/options.entity"; | |
| import { TypeOrmModule } from "@nestjs/typeorm"; | |
| @Module({ | |
| imports:[ | |
| TypeOrmModule.forFeature([Options]) | |
| ], | |
| controllers: [OptionsController], | |
| providers: [OptionsService] | |
| }) | |
| export class OptionsModule {} | 
修改 options.service.ts
| /* | |
| * options.service.ts | |
| */ | |
| import { Injectable } from '@nestjs/common'; | |
| import { InjectRepository } from "@nestjs/typeorm"; | |
| import { Options } from "./entity/options.entity"; | |
| import { Repository } from "typeorm"; | |
| type optionType = { | |
| key:string, | |
| value:string, | |
| prefix:string|null | |
| }; | |
| @Injectable() | |
| export class OptionsService { | |
| constructor( | |
| @InjectRepository(Options) | |
| private optionsRepository: Repository<Options> | |
| ) {} | |
| async getAll():Promise<Options[]>{ | |
| return this.optionsRepository.find(); | |
|     } | |
| async getOne(key:string):Promise<Options>{ | |
| return this.optionsRepository.findOne({where:{key}}); | |
|     } | |
| async update(option:Options,value:string):Promise<boolean>{ | |
| option.value = value; | |
| await this.optionsRepository.save(option); | |
| return true; | |
|     } | |
| async add(data:optionType):Promise<Options>{ | |
| const option = new Options(); | |
| option.key = data.key; | |
| option.value = data.value; | |
| option.prefix = data.prefix?data.prefix:""; | |
| return await this.optionsRepository.save(option); | |
|     } | |
| } | 
修改 options.controller.ts
| import { Controller,Get,Post,Body } from '@nestjs/common'; | |
| import { Options } from "./entity/options.entity"; | |
| import { OptionsService } from "./options.service"; | |
| type optionType = { | |
| key:string, | |
| value:string, | |
| prefix:string|null | |
| }; | |
| @Controller('options') | |
| export class OptionsController { | |
| constructor( | |
| private readonly optionsService: OptionsService, | |
| ) {} | |
| @Get() | |
| async getAllOptions():Promise<Options[]>{ | |
| return await this.optionsService.getAll(); | |
|     } | |
| @Post("set") | |
| async setOption(@Body() data:optionType):Promise<Options>{ | |
| let option = await this.optionsService.getOne(data.key); | |
| if(option){ | |
| await this.optionsService.update(option,data.value); | |
| }else{ | |
| option = await this.optionsService.add(data); | |
|         } | |
| return option; | |
|     } | |
| } | 
添加了 2 个接口
GET /options 返回 options 列表
POST /options/set 设置 options, 根据 key 查询,有则修改,无则新增
先本地测试一遍,没问题就可以部署了
部署之前依旧更新一下 "层", 同时修改 serverless.yml 文件的 layer 版本号 (只要 package.json 的 dependencies 内容有变动就需要更新一下)
执行 npm run build && npm run deploy
| > npm run build && sls deploy | |
| > manager@0.0.1 prebuild | |
| > rimraf dist | |
| > manager@0.0.1 build | |
| > nest build | |
| serverless ⚡tencent | |
| Action: "deploy" - Stage: "dev" - App: "manager" - Name: "manager" | |
| apigw: | |
| apiList: | |
| - | |
| apiId: api-l764cd57 | |
| apiName: http_api | |
| authType: NONE | |
| businessType: NORMAL | |
|       created:         true | |
| internalDomain: | |
|       isBase64Encoded: false | |
| method: ANY | |
| path: / | |
| url: https://service-mlmh4ts7-1252902543.sh.apigw.tencentcs.com/release/ | |
| environment: release | |
| id: service-mlmh4ts7 | |
| subDomain: service-mlmh4ts7-1252902543.sh.apigw.tencentcs.com | |
| url: https://service-mlmh4ts7-1252902543.sh.apigw.tencentcs.com/release/ | |
| faas: | |
| name: manager-dev | |
| namespace: default | |
| runtime: Nodejs16.13 | |
| type: web | |
| region: ap-shanghai | |
| 应用控制台: https://serverless.cloud.tencent.com/apps/manager/manager/dev | |
| 91s »manager» 执行成功 | 
使用 curl 工具测试一下
| ➜ curl --location --request POST 'https://service-mlmh4ts7-1252902543.sh.apigw.tencentcs.com/options/set' \ | |
| --header 'Content-Type: application/json' \ | |
| --data '{"key":"first","value":"first value"}' | |
| {"id":1,"key":"first","value":"first value","prefix":""}# | |
| ➜ curl --location --request GET 'https://service-mlmh4ts7-1252902543.sh.apigw.tencentcs.com/options' | |
| [{"id":1,"key":"first","value":"first value","prefix":""}]# | 
测试成功!
