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
 19- const 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
 5- await query('select * from open_course'); 
 await query(sql, [offset, pageSize])
 await query('insert into open_course set ?', req.body)
- mysql 格式化sql+query,防止注入 - 1 
 2- sql = 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