共计 5200 个字符,预计需要花费 13 分钟才能阅读完成。
分层标准
从本章起,正式进入企业级 Web 服务器核心内容。通常,一块残缺的业务逻辑是由视图层、管制层、服务层、模型层独特定义与实现的,如下图:
从上至下,抽象层次逐步加深。从下至上,业务细节逐步清晰。视图层属于 Web 前端内容,本文采纳 JavaScript Modules 进行演示。
本章着重说说管制层与服务层,对业务逻辑外围局部进行开展。
写一个简易版的商铺治理
间接从上一章已实现的工程 host1-tech/nodejs-server-examples – 00-static 开始着手,先编写服务层内容:
$ mkdir src/services # 新建 src/services 目录寄存服务层逻辑
$ tree -L 2 -I node_modules # 展现除了 node_modules 之外的目录内容构造
.
├── Dockerfile
├── package.json
├── public
│ └── index.html
├── src
│ ├── server.js
│ └── services
└── yarn.lock
// src/services/shop.js
// 店铺数据
const memoryStorage = {'1001': { name: '良品铺子'},
'1002': {name: '来伊份'},
'1003': {name: '三只松鼠'},
'1004': {name: '百草味'},
};
// 模仿延时
async function delay(ms = 200) {await new Promise((r) => setTimeout(r, ms));
}
class ShopService {async init() {await delay();
}
async find({id, pageIndex = 0, pageSize = 10}) {await delay();
if (id) {return [memoryStorage[id]].filter(Boolean);
}
return Object.keys(memoryStorage)
.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize)
.map((id) => ({id, ...memoryStorage[id] }));
}
async modify({id, values}) {await delay();
const target = memoryStorage[id];
if (!target) {return null;}
return Object.assign(target, values);
}
async remove({id}) {await delay();
const target = memoryStorage[id];
if (!target) {return false;}
return delete memoryStorage[id];
}
}
// 单例模式
let service;
module.exports = async function () {if (!service) {service = new ShopService();
await service.init();}
return service;
};
以上服务层提供了店铺管理所需的根底业务逻辑,存储临时以内存和延时模仿,当初通过管制层向外裸露 RESTful 接口:
$ mkdir src/controllers # 新建 src/controllers 目录寄存管制层逻辑
$ tree -L 2 -I node_modules # 展现除了 node_modules 之外的目录内容构造
.
├── Dockerfile
├── package.json
├── public
│ └── index.html
├── src
│ ├── controllers
│ ├── server.js
│ └── services
└── yarn.lock
// src/controllers/shop.js
const {Router} = require('express');
const shopService = require('../services/shop');
class ShopController {
shopService;
async init() {this.shopService = await shopService();
const router = Router();
router.get('/', this.getAll);
router.get('/:shopId', this.getOne);
router.put('/:shopId', this.put);
router.delete('/:shopId', this.delete);
return router;
}
getAll = async (req, res) => {const { pageIndex, pageSize} = req.query;
const shopList = await this.shopService.find({pageIndex, pageSize});
res.send({success: true, data: shopList});
};
getOne = async (req, res) => {const { shopId} = req.params;
const shopList = await this.shopService.find({id: shopId});
if (shopList.length) {res.send({ success: true, data: shopList[0] });
} else {res.status(404).send({success: false, data: null});
}
};
put = async (req, res) => {const { shopId} = req.params;
const {name} = req.query;
const shopInfo = await this.shopService.modify({
id: shopId,
values: {name},
});
if (shopInfo) {res.send({ success: true, data: shopInfo});
} else {res.status(404).send({success: false, data: null});
}
};
delete = async (req, res) => {const { shopId} = req.params;
const success = await this.shopService.remove({id: shopId});
if (!success) {res.status(404);
}
res.send({success});
};
}
module.exports = async () => {const c = new ShopController();
return await c.init();};
// src/controllers/index.js
const {Router} = require('express');
const shopController = require('./shop');
module.exports = async function initControllers() {const router = Router();
router.use('/api/shop', await shopController());
return router;
};
// src/server.js
const express = require('express');
const {resolve} = require('path');
const {promisify} = require('util');
+const initControllers = require('./controllers');
const server = express();
const port = parseInt(process.env.PORT || '9000');
const publicDir = resolve('public');
async function bootstrap() {server.use(express.static(publicDir));
+ server.use(await initControllers());
await promisify(server.listen.bind(server, port))();
console.log(`> Started on port ${port}`);
}
bootstrap();
当初应用 yarn start
启动利用,通过浏览器即可间接拜访接口 http://localhost:9000/api/shop 与 http://localhost:9000/api/shop/1001。
补充一个店铺治理界面
以 JavaScript Modules 写一个店铺治理界面仅作演示(理论生产中倡议应用 React 或 Vue),调用 GET
、PUT
、DELETE
接口对店铺信息进行查问、批改、删除:
<!-- public/index.html -->
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
- <h1>It works!</h1>
+ <div id="root"></div>
+
+ <script type="module">
+ import {refreshShopList, bindShopInfoEvents} from './index.js';
+
+ async function bootstrap() {+ await refreshShopList();
+ await bindShopInfoEvents();
+ }
+
+ bootstrap();
+ </script>
</body>
</html>
// public/index.js
export async function refreshShopList() {const res = await fetch('/api/shop');
const {data: shopList} = await res.json();
const htmlItems = shopList.map(({ id, name}) => `
<li data-shop-id="${id}">
<div data-type="text">${name}</div>
<input type="text" placeholder="输出新的店铺名称" />
<a href="#" data-type="modify"> 确认批改 </a>
<a href="#" data-type="remove"> 删除店铺 </a>
</li>`
);
document.querySelector('#root').innerHTML = `
<h1> 店铺列表:</h1>
<ul class="shop-list">${htmlItems.join('')}</ul>`;
}
export async function bindShopInfoEvents() {document.querySelector('#root').addEventListener('click', async (e) => {e.preventDefault();
switch (e.target.dataset.type) {
case 'modify':
await modifyShopInfo(e);
break;
case 'remove':
await removeShopInfo(e);
break;
}
});
}
export async function modifyShopInfo(e) {
const shopId = e.target.parentElement.dataset.shopId;
const name = e.target.parentElement.querySelector('input').value;
await fetch(`/api/shop/${shopId}?name=${encodeURIComponent(name)}`, {method: 'PUT',});
await refreshShopList();}
export async function removeShopInfo(e) {
const shopId = e.target.parentElement.dataset.shopId;
const res = await fetch(`/api/shop/${shopId}`, {method: 'DELETE'});
await refreshShopList();}
拜访 http://localhost:9000/ 即可体验店铺治理性能:
本章源码
host1-tech/nodejs-server-examples – 01-api-and-layering
更多浏览
从零搭建 Node.js 企业级 Web 服务器(零):动态服务
正文完
发表至: javascript
2020-07-21