Serverless Components 是支持多个云资源编排和组织的场景化解决方案,主要基于客户的具体场景,如 Express 框架支持、网站部署等。Serverless Components 可以有效简化云资源的配置和管理,将网关、COS 和 CAM 等产品联动起来,让客户更多关注场景和业务。
本文将讲解如何使用 Serverless Components Koa 快速构建一个后台接口服务,并验收新建、查询用户等功能。
项目的框架结构:
代码语言:txt复制koa-backend-demo
├── config
│ ├── database
│ │ └── mysql.js
│ └── index.js
├── controller.js
├── controllers
│ └── User
│ ├── User.js
│ └── index.js
├── models
│ └── UserModel.js
├── package.json
├── serverless.yml
├── sls.js
└── utils
├── ApiResponse
│ └── ApiResponse.js
└── utils.js
配置 serverless.yml
代码语言:txt复制# serverless.yml
component: koa # (required) name of the component. In that case, it's koa.
name: koa-backend-demo # (required) name of your koa component instance.
inputs:
src:
src: ./ # (optional) path to the source folder.
exclude:
- .env
region: ap-guangzhou
runtime: Nodejs12.16
apigatewayConf:
protocols:
- http
- https
environment: release
新建 sls.js
代码语言:txt复制const Koa = require('koa');
const router = require('koa-router')();
const controller = require('./controller');
const app = new Koa();
// x-response-time 响应时间
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// 配置路由
app.use(controller());
module.exports = app;
新建 controller.js
controller.js 用于遍历 controllers 文件夹下的文件去配置接口路由。
代码语言:txt复制const fs = require('fs');
const config = require('./config/index.js');
function addMapping(router, mapping) {
for (let url in mapping) {
if (url.startsWith('GET ')) {
let path = config.prefix url.substring(4);
router.get(path, mapping[url]);
} else if (url.startsWith('POST ')) {
let path = config.prefix url.substring(5);
router.post(path, mapping[url]);
} else {
console.error(`invalid URL: ${url}`);
}
}
}
function addControllers(router, dir) {
let files = fs.readdirSync(__dirname '/' dir);
let js_files = [];
for (let i in files) {
js_files.push(`${files[i]}/index.js`);
}
for (let f of js_files) {
console.log(`process controller: ${f}...`);
let mapping = require(__dirname '/' dir '/' f);
addMapping(router, mapping);
}
}
module.exports = function (dir) {
let controllers_dir = dir || 'controllers';
let router = require('koa-router')();
addControllers(router, controllers_dir);
return router.routes();
};
config 配置文件夹
config/index.js
配置接口前缀
代码语言:txt复制'use strict'
module.exports = {
prefix: '/api',
}
config/database/mysql.js 数据库配置
代码语言:txt复制'use strict'
const mysql = require('mysql2');
const MYSQL_CONFIG = {
HOST: 'xxxxxxxxxxxx.gz.tencentcdb.com', // 腾讯云数据库
PORT: '3306',
DATABASE: 'mydata',
USERNAME: 'root',
PASSWORD: 'xxxxxxxx'
};
// init mysql connection
const initMysqlPool = () => {
const promisePool = mysql
.createPool({
host: MYSQL_CONFIG.HOST,
user: MYSQL_CONFIG.USERNAME,
port: MYSQL_CONFIG.PORT,
password: MYSQL_CONFIG.PASSWORD,
database: MYSQL_CONFIG.DATABASE,
connectionLimit: 1,
})
.promise();
return promisePool;
}
module.exports = {
MYSQL_CONFIG,
initMysqlPool,
}
备注:此处需要配置远程连接 MySQL 的配置信息(即 MYSQL_CONFIG)
新建 models 文件夹
通过 UserModel.js 操作数据库
代码语言:txt复制const { initMysqlPool } = require('./../config/database/mysql');
const pool = initMysqlPool();
class UserModel {
constructor({
id = 0,
name = '',
email = '',
site = '',
}) {
this.id = id;
this.name = name;
this.email = email;
this.site = site;
}
// 注册用户
insertData({
name = '',
email = '',
site = '',
}) {
return pool.query('INSERT into users SET ?', {
name,
email,
site,
})
}
// 查找所有用户
findAllUsers() {
return pool.query('SELECT * FROM users');
}
// 通过名字查找用户信息
findDataByName( name ) {
return pool.query(
'SELECT * FROM users WHERE name = ?',
[
name,
]
)
}
}
module.exports = UserModel;
controllers 控制器
controllers/User/index.js
用于定义接口路由:
代码语言:txt复制'use strict'
const User = require('./User');
const user = new User();
module.exports = {
'GET /user/get-user-by-name': user.getUserByName.bind(user),
'GET /user/get-user-list': user.getUserList.bind(user),
'GET /user/create-user': user.createUser.bind(user),
}
备注: /user/create-user 接口建议使用 POST 请求(即 'POST /user/create-user': user.createUser.bind(user) 定义),此处为了演示方便定义为 GET 请求。
controllers/User/User.js
业务逻辑
代码语言:txt复制'use strict';
const ApiResponse = require('./../../utils/ApiResponse/ApiResponse');
const { awaitWrap } = require('./../../utils/utils');
const UserModel = require('./../../models/UserModel');
const userModel = new UserModel({});
class User {
async getUserList(ctx, next) {
const res = new ApiResponse({});
const [data, err] = await awaitWrap(
userModel.findAllUsers()
)
if (err) {
res.errDetail = err;
ctx.body = res;
return false;
}
const [userData] = data;
res.code = 0;
res.msg = '请求成功';
res.data = userData;
ctx.body = res;
}
async createUser(ctx, next) {
const res = new ApiResponse({});
const name = ctx.request.query.name || "";
const email = ctx.request.query.email || "";
const site = ctx.request.query.site || "";
const userData = await this._getUserByName(name);
if (
userData.code === 0 &&userData.data &&
userData.data.length > 0
) {
res.code = 0;
res.msg = '已存在该用户名';
res.data = userData.data;
ctx.body = res;
return true;
}
const [data, err] = await awaitWrap(
userModel.insertData({
name,
email,
site,
})
);
if (err) {
res.errDetail = err;
ctx.body = res;
return false;
}
const [insertData] = data;
res.code = 0;
res.msg = '请求成功';
res.data = [
new UserModel({
id: insertData.insertId,
name: name,
email: email,
site: site,
})
]
ctx.body = res;
}
async _getUserByName(name) {
const res = new ApiResponse({});
if (!name) {
res.msg = '用户姓名不能为空';
return res;
}
const [data, err] = await awaitWrap(
userModel.findDataByName(name)
);
if (err) {
res.errDetail = err;
return res;
}
const [userData] = data;
if (userData.length > 0) {
res.code = 0;
res.msg = '请求成功';
res.data = userData;
} else {
res.code = 0;
res.msg = '用户信息不存在';
res.data = []
}
return res;
}
async getUserByName(ctx, next) {
const name = ctx.request.query.name || "";
const res = await this._getUserByName(name);
ctx.body = res;
}
}
module.exports = User;
utils 工具函数
utils/ApiResponse/ApiResponse.js
接口响应的数据结构
代码语言:txt复制'use strict'
class ApiResponse {
constructor({
code = 500,
data = {},
msg = '系统异常',
errDetail = '',
}) {
this.code = code;
this.data = data;
this.msg = msg;
this.errDetail = errDetail;
}
}
module.exports = ApiResponse;
utils/utils.js
提供 awaitWrap 函数捕获 promise 报错
代码语言:txt复制/**
* @param {*} promise
* @returns [data, err]
*/
const awaitWrap = (promise) => {
return promise
.then(data => [data, null])
.catch(err => [null, err]);
}
module.exports = {
awaitWrap,
}
package.json
通过 npm init 新建 package.json ,安装项目依赖包:
代码语言:txt复制npm i --save koa koa-router mysql2
使用 sls deploy 部署
代码语言:txt复制sls deploy
若未有 .env 配置文件,则通过微信扫码登录授权。
如上图所示,kao 接口服务的请求 url 是 https://service-gwlsee6c-1253513412.gz.apigw.tencentcs.com/release
我们刚刚写了三个接口,分别为:
新增用户接口:
https://service-gwlsee6c-1253513412.gz.apigw.tencentcs.com/release/api/user/create-user?name=xiaoming&email=1234566@qq.com&site=F
通过名字查找用户信息:
https://service-gwlsee6c-1253513412.gz.apigw.tencentcs.com/release/api/user/get-user-by-name?name=xiaoming
查找所有用户:
https://service-gwlsee6c-1253513412.gz.apigw.tencentcs.com/release/api/user/get-user-list
项目代码:
通过 Serverless Components Koa 构建后台服务