nodejs后台渲染核心知识点
express
express路由
模块化
- app.use(path, ‘exported router’)
- 分文件嵌套使用,按功能命名划分,放置在routes
路由传参
见url参数一节
重定向:
res.redirect('/admin/open-course')
自定义中间件
- 自定义一个模块并导出
- app中require引入该模块,
- 并且用app.use注册该中间件
1 | //功能:初始化全局变量, 应用级中间件,在每个路由生效 |
错误处理:
- createError+error handler(带err参数的callback)
- 一般放在最后,按中间件的顺序,若 执行到这,说明有错误,createError进入 error handler
1 | // createError |
全局变量
- 见自定义中间件例子,除了res.locals还有app.locals
- 全局变量的使用: 比如 在layout.hbs使用按登录状态的nav
whichPartial navName
模板引擎
设置hbs模板引擎
1 | // 设置视图模板目录 |
render
- 以views目录为基准
res.render('path' , options)
- options中layout默认为’layout’,渲染布局页,布局页用 三括号 body三括号 承载其他模板
hbs语法
官方文档:https://handlebarsjs.com/guide/partials.html#basic-partials
插值表达式
条件判断if switch
循环each (数组/对象)
partials/helpers
注册partial目录:
hbs.registerPartials(path.join(__dirname, '../views/partials'));
自定义helper:
var helper = require('./helper');
,app中引入即可,hbs是单例如代码搬家context.fn(this)/context.inverse(this)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const blocks = {}; //代码块缓存对象 全局变量,服务器开着就不会消失,,
hbs.registerHelper('extend', function(name, context) {
// context 是上下文,保存有用方法和数据,最后一个参数永远是context,,hbs内部调用时传入
let block = blocks[name]; // block用来存放代码块
if (!block) {
block = blocks[name] = [];
}
// 编译指令中代码块并放入block
block.push(context.fn(this)); //context.fn方法将数据编译到非else的模板中 传递this,块级helper中的变量才会编译进模板内容
// 与context.fn()配对还有一个方法
//block.push(context.inverse(this));// //context.inverse()方法将数据编译到else的分支模板中,,块级partial中可以用else
//总之:context.fn和inverse是取得块级模板中的模板内容,传递this则会把块级模板内的变量也编译进模板内容中,最后输出到页面(helper有返回值才行,没有返回值就不会有东西输出给页面)
});
hbs.registerHelper('block', function(name) {
const val = (blocks[name] || []).join('');
blocks[name] = []; //清空缓存
return val;
});
行级partial
块级partial
组件化
- 参考模板引擎partial使用
- 主要模式: 组件提取+代码搬家,将某一功能html/css/js写成一个组件,用代码搬家,最终整合到layout模板的指定位置
- 案例参考: views/partials/pager 分页组件的使用
url参数
完整url
‘http://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// url 中间件
url.parse结果: 一切url参数的处理 可以参考
{
protocol: 'http:', // 协议
slashes: true, //
auth: 'user:pass', // 用户登录才能访问
host: 'sub.example.com:8080', // 域名(包含端口)
port: '8080', //端口
hostname: 'sub.example.com', // 域名(不包含端口)
hash: '#hash', // 哈希值
search: '?query=string', // ?及?后面的部分
query: 'query=string', // ?后面的部分
pathname: '/p/a/t/h', //端口后面, ?之前的部分
path: '/p/a/t/h?query=string', // 端口后面的部分,不包含哈希
href:'http://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash' // 完整请求url
}
参数类型
get(url占位参,查询参), post(urlencoded, json)
get传参
原生: url模块 :
let urlObj = url.parse(str, true)
express: 封装在req.query(查询参), req.params(url参数)
post传参
原生: querystring模块
let obj = qs.parse(param)
; querystring是用来处理urlencoded形式的数据的方法,,不论get,post,, post的数据默认也是这种形式express
- express自带:
app.use(express.json())
app.use(express.urlencoded({ extended: false }));
- body-parser:
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json());
- 其他: 如multiparty,,则支持表单普通类型和文件类型数据的提交
- 除了文件类型,都是通过req.body获取表单字段数据,,,文件通过req.file;; 响应json数据可以通过res.send或res.json
- express自带:
表单(todo)
数据提交
表单默认方式
- get
- post
- enctype
ajax restful提交
- urlencoded
- json
- datatype/content-type
文件上传/数据校验
文件上传和数据校验是通过针对某一个路由,添加问价上传和数据校验的中间件进行实现
文件上传
express中multer模块为例
文件上传: 表单设置multi-formdata, 一般文件用file获取,表单项用name获取
multer:
const multer = require('multer');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// 设置存储目录和filename
const storage = multer.diskStorage({ //比单个dest内容更丰富
destination: function(req, file, cb) { // 存储目录
cb(null, 'public/images');
},
filename: function(req, file, cb) {
let extname = '';
switch (file.mimetype) {
case 'image/jpeg':
extname = '.jpg';
break;
case 'image/png':
extname = '.png';
break;
case 'image/gif':
extname = '.gif';
break;
}
cb(null, Date.now() + extname);
}
});
// 实例化multer
const upload = multer({
// dest: 'public/images',
storage,
limits: { fileSize: 2 * 1024 * 1024 }, //最大2M
fileFilter: function(req, file, cb) {
console.log(file);
// 判断文件是否合法,合法则处理,不合法则拒绝
if (file.mimetype === 'image/gif' ||
file.mimetype === 'image/jpeg' ||
file.mimetype === 'image/png') {
// 接收文件
cb(null, true);
} else {
cb(new Error('请上传图片格式'), false);
}
}
});
数据校验
1 | const { body, validationResult } = require('express-validator/check'); //解构得到body(req.body) 和 validationResult ;body针对post data,还有query params对应不同传参方式的参数 |
中间件综合上传和校验
1 | router.post('/open-course', [upload.single('file'), ...validations], // 中间件callback多个的应用 |
mysql
crud sql
常规query
1
2
3
4
5await query('select * from open_course');
await query(sql, [offset, pageSize])
await query('insert into open_course set ?', req.body)mysql 格式化sql+query,防止注入
1
2sql = mysql.format('update open_course set ? where id =?', [req.body, id]);
const result=await query(sql);
普通查询封装promise
1 | //db.js |
连接池
1 | //连接池 普通创建连接池的写法 |
sequelize
安装: npm i sequelize mysql2 -S // mysql2 为mysql对应的驱动
orm,不用再写具体sql,语义化查询
基本使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34//1 引入
const Sequelize = require('sequelize');
//2 实例化
const sequelize = new Sequelize('kkb', 'kkb_admin', 'admin', {
host: 'localhost',
dialect: 'mysql', //方言
pool: { max: 5, acquire: 30000, idle: 10000 }, //连接池
timestamps: false
});
//3 定义模型
const User = sequelize.define('user', { //模型名!, 字段名
firstName: Sequelize.STRING, //更多设置使用对象
lastName: Sequelize.STRING,
age: Sequelize.INTEGER
}, { //其他参数
//freezeTableName:true //表名默认是模型名+s 冻结则不会
});
//4 同步数据库 force:true 强制更新 先删表
// model.sync建表 model.create插入数据
User.sync({ force: false }).then(() => { //返回的promise,直接then
return User.create({ // 插入数据
firstName: 'Tom',
lastName: 'Cruise',
age: 30
})
}).then(() => { // 查询
//查询插入的数据 findAll 看执行的sql
User.findAll().then(user => {
console.log(user);
})
});模块化
将每个模型单独存放一个文件,利用import整合,统一导出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37//封装
const fs = require('fs');
const path = require('path');
const db = [Sequelize, sequelize]; //导出类和实例对象, Sequelize为了定义数据类型
//模型导入 除了db.js index.js 其他文件都是单个模型
fs.readdirSync(__dirname)
.filter(file => (file !== 'index.js' && file !== 'db.js'))
.forEach(file => {
const model = sequelize.import(path.join(__dirname, file));
db[model.name] = model; // 增加到db
});
module.exports = db;
-------------------------------------------------------------------------------
// 使用
//sequelize 模型使用案例,,动态分页
const {OpenCourse} = require('../models');
router.get('/bySeq', async (req, res, next)=>{
try{
const page = +req.query.page || 1;// 获取当前页码,如没有则默认1
const size = +req.query.size || 3;// 每页条数
//返回带总条数的对象 {rows: [], count}
const results= await OpenCourse.findAndCountAll({ // 不再需要提供sql
offset:(page-1)*size,//偏移量
limit:size,//每页大小
order:[['time','DESC']]//支持多字段排序
});
res.render('open-course', {
title:'公开课',
openCourses:results.rows,//查询结果时RowDataPacket对象数组
pagination: getPagination(results.count, page, size)
})
}
catch(err){
console.log(err);
next(err);
}
})事务
连接池+事务 Promise封装
1 | const pool = mysql.createPool(cfg);//创建连接池 |
跨域/restful
restful
- get/post/put/delete/header/option
- 简单请求/preflight
- restful api : api是提供接口数据,不对页面进行渲染
跨域
- 同源策略: 针对脚本
- 解决方式:
- jsonp(同源对script无效,传递callback search参数,返回callback(data)执行); ajax回调和jsonp不同,ajax是xhr根据状态监听执行回调
- cors: Access-Control-Allow-Origin/Headers/Methods/Credentials