🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 章节导航 [TOC] ## express - generator 1. 全局安装 express-generator 项目构建工具,express-generator 会帮忙我们快速创建一个基于 express 的 web后端服务代码,已经完成了基础需要依赖及配置。 ``` npm install -g express-generator ``` 创建项目并进入 ``` cd ~/desktop && express expressName && cd expressName ``` 下载依赖 ``` npm install ``` 启动start ``` npm start ``` 浏览器输入http://localhost:3000/, 就能看见 ## 文件结构 app.js 主文件 (实例化express还有引入依赖包) bin/www 启动入口(很少用到) package.json 依赖包管理文件(暂时没什么用到就是配置依赖包) public 静态资源目录 (存放css,js,img等文件) routes 路由目录 (存放你页面路由!impotent) views 模版目录(放置你写好的html.tpl格式) 配置统一共用模版会比较好`{% extends './../admin_ayout.tpl' %} {% endblock %}`在页面头部应用 ## 介绍app.js中的配置 ``` var indexRouter = require('./routes/indexs');//页面路由存放调用事件 var usersRouter = require('./router/api') //存放接口例如post,put,delete 发送数据为主 ``` 在indexs中我们经常存放每个页面调用的事件如果事件没生效那就得即使查看router/indexs 在api中我们放置着我们链接网页的API接口这是把数据post,put上去的主要文件有很多不同的请求方法 ``` ~~~ // 使用 morgan 日志打印 app.use(logger('dev')); // 使用对 Post 来的数据 json 格式化 app.use(express.json()); // 使用对 表单提交的数据 进行格式化 app.use(express.urlencoded({ extended: false })); // 使用 cookie 例如用户登陆产生的cookie app.use(cookieParser()); // 设置静态文件地址路径为 public app.use(express.static(path.join(__dirname, 'public'))); ~~~ ``` 捕获报错 ``` // 捕捉404错误 app.use(function(req, res, next) { next(createError(404)); }); ``` 监听页面异常 ``` app.use(function(err,req,res,next){ res.locals.message = err.message //设置错误信息 res.locals.error = req.app.get('env') === 'development' ? err: { } ; res.status(err.status || 500); res.render('error');//把错误信息渲染到模版 (render:渲染器) }); ``` 输出模版`module.exports = app;` ## nunjucks Nunjucks 是为了方便我们使用JavaScript编写的一个模版引擎因为可以复用所以我们就选它了!![官网地址点着嘿!](https://mozilla.github.io/nunjucks/) 第一步当然是要安装它啦!start! `npm install --save nunjucks` 第二步在app.js中引入 `var nunjucks = require('nunjucks');` ``` app.set('view engine','tpl');//设置模版后缀为.tpl nunjucks.configure('views',{ autoescape: true, express: app, watch: true }) ``` 修改原来存在的layout,index,error为其添上tpl后缀 **layout.tpl** ~~~ <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>{{title}}</title> {% block css %} {% endblock %} </head> <body> {% block content %} {% endblock %} {% block js %} {% endblock %} </body> </html> ~~~ **index.tpl** ~~~ {% extends './layout.tpl' %} {% block css %} <link rel="stylesheet" href="/stylesheets/style.css"> {% endblock %} {% block content %} <h1>{{title}}</h1> <p>Welcome to {{title}} with nunjucks!</p> {% endblock %} ~~~ **error.tpl** ~~~ {% extends './layout.tpl' %} {% block css %} <link rel="stylesheet" href="/stylesheets/style.css"> {% endblock %} {% block content %} <h1>{{message}}</h1> <h2>{{error.status}}</h2> <pre>{{error.stack}}</pre> {% endblock %} ~~~ ## 常用的模块语句 `if`为分支语句,和JavaScript中的`if`类似 ``` {% if variable %} It is true //判断如果为true就显示反之隐藏 {% endif %} ``` `for`遍历数组(arrays)和对象(dictionaries) ~~~js var items = [{ title: "foo", id: 1 }, { title: "bar", id: 2}]; ~~~ ~~~jinja <h1>Posts</h1> <ul> {% for item in items %} <li>{{ item.title }}</li> {% else %} <li>This would display if the 'item' collection were empty</li> {% endfor %} </ul> ~~~ 上面的示例通过使用`items`数组中的每一项的`title`属性显示了所有文章的标题。如果`items`数组是空数组的话则会渲染`else`语句中的内容。 ## axios Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。[axios]([https://github.com/axios/axios](https://github.com/axios/axios)) 下载 axios `npm install -save axios` 记得修改路由app.js ``` var apiRouter = require('./routes/apo');//引入文件 app.use('.api'/apiRouter); //使用 ``` # 实例 ## knex ### 增删改查的实现 以下我们使用 knex 使用 mysql 链接操作数据库,使用前请确保安装以下工具: 数据库下载: [Sequel Pro](https://www.sequelpro.com/) 下载依赖:`npm install -save knex mysql` 在项目根目录下,新建配置信息**config.js**,之后的配置信息涉及到数据库和密码,不上传到 Github 等托管平台,所以需要单独设置,之后使用**.gitignore**避免上传。之后敏感的配置信息,都将在此配置。host 地址为数据库的服务地址,本地为 127.0.0.1 ,也可能在虚拟机上 192.168.64.2 ,以 XAMPP 开放的地址为准。 **config.js** ~~~ const configs = { mysql: { host: '127.0.0.1', port: '3306', user: 'root', password: '', database: 'expressapp' } } module.exports = configs ~~~ 在项目根目录下,新建**.gitignore**避免上传**config.js**及**node\_modules**等不需要被上传到 Github 的文件。 **gitignore** ~~~ .DS_Store .idea npm-debug.log yarn-error.log node_modules config.js ~~~ 新建**models/knex.js**数据库配置,初始化配置 knex ~~~ // 引用配置文件 const configs = require('../config'); // 把配置文件中的信息,设置在初始化配置中 module.exports = require('knex')({ client: 'mysql', connection: { host: configs.mysql.host, port: configs.mysql.port, user: configs.mysql.user, password: configs.mysql.password, database: configs.mysql.database } ~~~ 新建**models/user.js**用户模型,并设置获取所有用户的方法 ``` const knex = require('./../models/knex');//引入 const TABLE = 'users'; //数据库中的用户表 const User = { all:function(){ //获取all用户信息 return new Promise((reslove,reject) =>{ knex.select().table(TABLE).then(res =>{ reslove(res) }).catch(err =>{ //捕获报错 reject(err) //返回拒绝 }) }) } } module.exports = User //输出方法 ``` 新建用户在用户列表上使用到了for循环语句参考上面 ~~~ {% for val in users %} <li> <span>id: {{val.id}}</span> <span>email: {{val.email}}</span> <input class="user-name" type="text" name="name" placeholder="姓名" value="{{val.name}}"> <button class="update-user" data-id="{{val.id}}">修改姓名</button> <button class="delete-user" data-id="{{val.id}}">删除用户</button> </li> {% endfor %} ~~~ 新建**controllers/user.js**用户控制器,并设置 show 方法 我们想要把数据渲染到模版上当然需要一个方法才能实现。 1 .引入用户数据模版user.js 2 .使用异步请求把数据渲染到页面/async可以无刷新页面 3 .渲染res.render() ~~~ // 引用用户模版数据 const User = require('./../models/user.js'); const userController = { // show 获取用户数据并返回到页面 show: async function(req,res,next){ try{ // 从模型中获取所有用户数据 const users = await User.all(); // 把用户数据设置到 res.locals 中 res.locals.users = users; // 渲染到 views 视图目录的 user/show.tpl 路径中。 res.render('user/show.tpl',res.locals) }catch(e){ res.locals.error = e; res.render('error',res.locals) } }, } module.exports = userController; ~~~ ### index添加路由 ~~~ var userController = require('./../controllers/user.js'); router.get('/user',userController.show)'//获取上面所用到的show方法 ~~~ ### 我们需要添加增删改的功能的话那么必须给他们添加上需要的方法 1.添加用户我们使用(insert:插入)听起来有点像造孩子运动呢? ~~~ insert: function(params){ return new Promise((reslove,reject)=>{ knex(TABLE).insert(params)//慢慢插入 .then( res => { reslove(res) //完事才几行太不争气了 }).catch( err => { reject(err) // o shit! }) }) }, ~~~ 修改用户 (update:更新) 给他弄潮一点!update ~~~ update: function(id, params ){ return new Promise((reslove,reject)=>{ knex(TABLE).where('id', '=', id).update( params ) //给他换上新衣服 .then( res => { reslove(res) //掏钱换呗!刷哇滴卡 }).catch( err => { reject(err) //穷逼被赶出店了—。— }) }) }, ~~~ 报废它!Get out of here (delete:再见了世界) ~~~ delete: function(id){ return new Promise((reslove,reject)=>{ knex(TABLE).where('id', '=', id).del() //对比一下没错就是你滚吧! .then( res => { reslove(res) //滚的好远了哈哈哈 }).catch( err => { reject(err) //老板突然觉得还不想让你滚,回来敲代码 }) }) } ~~~ 既然我们设置造孩子,给孩子打扮,以及它长大成人是时候滚了!呸不对!是让他去工作了那么我们就需要给他判断一下孩子它爸是否以及成功插入并且内se了咳咳(小火车污污污) 我们去`controllers/user.js`给他设置一个逻辑笔记它有点弱智不会自力更生啥都要我们程序员操心!连sex都是 ``` insert: async function(req,res,next){ let name = req.body.name; let email = req.body.email; let password = req.body.password; console.log(name,email,password); if(!name || !email || !password){ res.json({ code: 0, data: 'params empty!'}) //炮友名字/联系方式/银行卡密码 //没有这些?还想约 } try{ const users = await Use.insert({name,email,password}); res.json({ code: 200, data: users}) }catch(e){ res.json({ code: 0, data: e }) } } ``` 判断孩子是否需要买衣服更新一下样子了 ~~~ update: async function(req,res,next){ let id = req.body.id; let name = req.body.name; console.log(id,name); if(!name || !id){ res.json({ code: 0, data: 'params empty!' }); return } try{ const user = await User.update(id,{ name }); res.json({ code: 200, data: user}) }catch(e){ res.json({ code: 0, data: e }) } }, ~~~ 判断一下是否炒了这个程序员 ~~~ delete: async function(req,res,next){ let id = req.body.id; if(!id){ //没有id还炒个鬼 啊 res.json({ code: 0, data: 'params empty!' }); return } try{ const user = await User.delete(id); res.json({ code: 200, data: user}) }catch(e){ res.json({ code: 0, data: e }) } } } ~~~ 上面弄好后我们需要到router/api.js给他设置接口 ``` ~~~ var userController = require('./../controllers/user'); router.post('/user' , userController.insert); //创建 router.put('/user' , userController.update); //更新 router.delete('/user' , userController.delete); //删除 ~~~ ``` 我们还有最关键的一步就是得给我们的页面设置js呀 找到并且绑定 新增,更新,以及删除三个按钮。我们要找到相关的内容做判断决定是否进行异步处理 例如新增:有没有用户名和id呢,删除对象的id,更新了什么呢? ~~~ $.ajax({ url: '/api/user', data: { id }, type: 'DELETE', //不同处 success: function(data) { if(data.code === 200){ alert('删除成功!') //不同处 location.reload() }else{ console.log(data) } }, error: function(err) { console.log(err) } }) ~~~ # BaseModel 结合上面的代码我们不难发现有很多重复的代码这样会使我们工作量增大以及更多重复的代码会使人眼花缭乱,所以我们需要抽离出他们共有的简单方法,不仅方便还可以复用 建立models/base.js 使用了Class构造函数 ~~~ const knex = require('./knex'); class Base { constructor(props) { this.table = props; } all( ){ return new Promise((reslove,reject)=>{ knex(this.table).select().then( res => { reslove(res) }).catch( err => { reject(err) }) }) } select(params) { return new Promise((reslove,reject)=>{ knex(this.table).select().where(params) .then( res => { reslove(res) }).catch( err => { reject(err) }) }) } insert(params){ return new Promise((reslove,reject)=>{ knex(this.table).insert( params ) .then( res => { reslove(res) }).catch( err => { reject(err) }) }) } update(id, params ){ return new Promise((reslove,reject)=>{ knex(this.table).where('id', '=', id).update( params ) .then( res => { reslove(res) }).catch( err => { reject(err) }) }) } delete(id){ return new Promise((reslove,reject)=>{ knex(this.table).where('id', '=', id).del() .then( res => { reslove(res) }).catch( err => { reject(err) }) }) } } module.exports = Base; ~~~ 修改用户模型**models/user.js**,使其继承基础模型,并设置其**table**表格名称。 **models/user.js** ~~~ // 引用基础模型 const Base = require('./base.js'); // 定义用户模型并基础基础模型 class User extends Base { // 定义参数默认值为 users 表 constructor(props = 'users') { super(props); } } module.exports = User ~~~ 修改用户控制器**controller/user.js**,修改后的用户模型是一个**class**类而不是对象,因此需要配合**new**来使用。 **controller/user.js** ~~~ const UserModel = require('./../models/user.js'); const User = new UserModel(); ~~~ # Cookie 我们使用cookie完成登陆权限控制以及清除cookie退出登陆,登陆成果我们需要加密cookie设置页面过滤组件 主要步骤有以下几点 * cookie 的加密算法 * 登录接口,登录成功设置 cookie * 用户信息过滤组件,每个用户进来更具 cookie 鉴定身份 * 登录页面,如果登录的话重新定向到首页 * 用户管理页面,如果没有登录的话重定向到登录页面 先引用网上关于jquery的加密解密方法[小本的早晨](https://www.cnblogs.com/syp172654682/p/8858358.html) **utils/authCode.js** ~~~ /** * 加密解密 */ const crypto = require('crypto'); const key = Buffer.from('aitschool!@#$123', 'utf8'); const iv = Buffer.from('FnJL7EDzjqWjcaY9', 'utf8'); const authcode = function (str, operation){ operation ? operation : 'DECODE'; if (operation == 'DECODE') { let src = ''; let cipher = crypto.createDecipheriv('aes-128-cbc', key, iv); src += cipher.update(str, 'hex', 'utf8'); src += cipher.final('utf8'); return src; }else { let sign = ''; let cipher = crypto.createCipheriv('aes-128-cbc', key, iv); sign += cipher.update(str, 'utf8', 'hex'); sign += cipher.final('hex'); return sign; } } module.exports = authcode; ~~~ ### 在index和apl中调用渲染视图模版 ~~~ index: router.post('/login' , authController.login); apl: router.post('/login' , authController.login); ~~~ 这是调用登陆以及输入账号密码的方法 ~~~ const authController = { login:async function(req,res,next){ // 获取邮件密码参数 let email = req.body.email; let password = req.body.password; // 参数判断 if(!email || !password){ res.json({ code: 0, data: 'params empty!' }); return } try{ // 通过用户模型搜索用户 const users = await User.select({ email, password }); // 看是否有用户存在 const user = users[0]; // 如果存在 if(user){ // 将其邮箱、密码、id 组合加密 let auth_Code = email +'\t'+ password +'\t'+ user.id; auth_Code = authCodeFunc(auth_Code,'ENCODE'); // 加密防止再 cookie 中,并不让浏览器修改 res.cookie('ac', auth_Code, { maxAge: 24* 60 * 60 * 1000, httpOnly: true }); // 返回登录的信息 res.json({ code: 200, message: '登录成功!'}) }else{ res.json({ code: 0, data: { msg: '登录失败,没有此用户!'} }) } }catch(e){ res.json({ code: 0, data: e }) } }, // 渲染登录页面的模版 renderLogin:async function(req,res,next){ res.render('login',res.locals) } } ~~~ ## 过滤中间件 filters/index.js ``` module.exports = function(app) { app.use(require('./loginFilter.js')); app.use(require('./initFilter.js')) } ``` loginFilter.js ``` const authCodeFunc = require('./../utils/authCode.js'); module.exports = function (req,res,next){ res.locals.isLogin =false; res.locals.iserInfo = {}; let auth_code = req.cookies.ac; if(auth_code){ auth_Code = authCodeFunc(auth_Code,'DECODE'); authArr = auth_Code.split('\t'); let email = authArr[0]; let password = authArr[1]; let id = authArr[2]; // 注意:为了防止删改 // 这里其实应该再调用一次用户模型进行登录校验 // 然后把状态保存在 session 中来联合判断。 // 当前为了体验,所以直接把用户名和密码和id直接解密返回 res.locals.isLogin = true; res.locals.userInfo = { email,password,id } } } ``` **filters/initFilter.js** ~~~ module.exports = function (req, res, next) { res.locals.seo = { title: 'ExpressApp', keywords: 'Express、Nodejs', description: 'ExpressApp to study Nodejs on Web' } next(); } ~~~ 记得要在app.js中引入才会其效果哦 ~~~ var filters = require('./filters/index') ... filters(app); app.use('/', indexRouter); app.use('/api', apiRouter); ~~~ ### 登陆页面如果登陆的话重新定向到首页 ``` const authController = { ... renderLogin: async function(req,res,next){ if(res,locals.isLogin){ res.redirect('/user'); return } res.render('login',res.locals) } } ``` 如果没有登陆那肯定是得定向回登陆页的呀 ~~~ const userController = { show: async function(req,res,next){ // 如果用户没有登录,重定向到登录页面 if(!res.locals.isLogin){ res.redirect('/login') return } try{ const users = await User.all(); res.locals.users = users; res.render('user/show.tpl',res.locals) }catch(e){ res.locals.error = e; res.render('error',res.locals); } }, } ~~~