Express
基于Node.js 平台,快速、开放、极简的 web 开发框架。
-
新建项目文件夹
mkdir express-node
-
初始化项目
//因为是node项目,需要初始化一下,生成package.json文件 npm init -y // -y 表示使用默认,和无脑回车相同。
-
安装express
npm install express --save
-
切换镜像源小工具
npm install nrm //解决安装过慢问题 nrm ls //展示所有镜像源 例: nrm use taobao //切换淘宝镜像源 // 如果自己发布包到npm,需要再切换回来
-
Express 应用生成器
Express 应用生成器-文档npm install express-generator --save-dev //不需要全局,有的机器会需要权限 全局安装了 express 运行 未全局安装 ./node_modules/express-generator/bin/express-cli.js 运行 ./node_modules/express-generator/bin/express-cli.js ./ -f -e //在当前目录下强制创建ejs模板引擎 -h 查看所有模板引擎 //会创建文件夹:bin、public、routes、views 提示安装依赖 install dependencies: $ cd ./ && npm install npm install //安装依赖
-
启动服务器
方法一: npm start // 启动服务器 ==>浏览器打开 http://localhost:3000/ 方法二: node bin/www
-
改变端口号
// 因为npm start 才可以启动服务器, 在package.json文件中 start: "node ./bin/www" 在bin/www中 port = normalizePort(process.env.PORT || '3000'); 3000就是端口号 或者启动的时候 PORT=4000 node bin/www 启动端口为4000 80端口是默认的端口,如果使用需要权限,需要再前面加 sudo window 需要权限,需使用管理员身份运行
-
app.js
app.set('views', path.join(__dirname, 'views')); // 把views设置为当前模板路径 app.set('view engine', 'ejs'); // 模板引擎 ejs // 中间键 app.use(express.static(path.join(__dirname, 'public'))); 不走路由(routes文件夹),直接从public中获取 //路由 app.use('/', index); app.use('/users', users);
MVC
控制路由跳转的就是控制器
视图 views 的模板 就是V
通过C去控制请求的流向,通过M去获取数据,通过V去把数据渲染好,展示给用户。-
Express
- 路由
路由 - 中间键
使用中间件 - 模板引擎
在 Express 中使用模板引擎
views下的index.ejs就是模板,处理数据转化的就是模板引擎
-------------此处需要整理一片ejs语法博客。
- 路由
-
静态页面
public文件夹中的静态文件, app.use(express.static(path.join(__dirname, 'public'))); static 设置静态资源,从public中查找对应文件夹,根据文件夹目录进行查找 如果找到了,就表示要的是文件,就把文件传输过来,不再往下查找 例:注释app.use(express.static(path.join(__dirname, 'public'))); app.use('/stylesheets/style.css',function(req,res(){ res.send('get style.css...') })) //就会走路由,把get style.css...传到页面。不注释,就会传文件。
换句话说:用户请求的任何东西,对这个网站来说都是路由,static是专门对静态资源进行的拦截,发现路径和文件夹匹配上了,传输文件。所以,后缀名没有任何意义,只是给用户分辨而已。
新建src 文件夹
目的,使用webpack编译到public文件夹中,显示给用户-
搭建简单测试环境
index.js var obj = require('../mod/b.js') console.log(obj) a.js module.exports.a='aaaaa' b.js var a = require('./a.js').a module.exports = { b: 'bbbb', a: a } webpack.config.js var webpack = require('webpack') var path = require('path') module.exports = { entry: path.join(__dirname,"js/app/index.js"), output:{ path: path.join(__dirname,"../public/js"), filename: "index.js" } }
-
webpack压缩
安装webpack npm install webpack --save-dev 在package.json的script中添加 "webpack":"webpack --config=src/webpack.config.js" npm run webpack 压缩路径public/js/index.js,检查文件代码是否压缩成功
-
自动上传小工具
每次执行npm run webpack 太麻烦,使用自动上传小工具,每次更改自动上传 npm install --save-dev onchange 在package.json的script中添加 "watch": "onchange src/**/*.js src/**/*.less -- npm run webpack " npm run watch 运行自动压缩
-
切换node.js版本小工具
npm install -g n n 6.10.0 切换版本
-
新建toast板块,提示便利贴增删改查状态
- src --> mod --> toast.js
function toast(msg,time){ this.msg = msg; this.dismissTime = time||1000, this.createToast(); this.showToast(); } toast.prototype = { createToast: function(){ var tpl = '<div class="toast">'+this.msg+'</div>'; this.$toast = $(tpl); $('body').append(this.$toast); }, showToast:function(){ var self = this; this.$toast.fadeIn(300,function(){ setTimeout(function(){ self.$toast.fadeOut(300,function(){ self.$toast.remove(); }); },self.dismissTime); }) } } function Toast(msg,time){ return new toast(msg,time); } Toast('hello') module.exports.Toast = Toast;
- src --> less --> toast.js
.toast{ position: fixed; left: 50%; transform: translateX(-50%); bottom: 20px; color: #D15A39; background: #fff; padding: 5px 10px; border-radius: 3px; box-shadow: 0px 0px 3px 1px rgba(255,255,255,0.6); display: none; }
-
-
运行toast
删除a.js b.js index.js ===> var Toast = require('../mod/toast.js').Toast; Toast('hello world') 在src ==> js ==> lib ==>添加jQuery 在toast.js中添加 var $ = require('../lib/jquery.min.js') 引用jQuery 简化引入jQuery代码 在webpack.config.js中添加 resolve:{ alias:{ //当require引入模块的时候,简化路径 jquery: path.join(__dirname,"js/lib/jquery.min.js"), mod: path.join(__dirname,"js/mod"), less: path.join(__dirname,"less") } }, toast.js代码var $ = require('../lib/jquery.min.js') 改成 var $ = require('jquery') 在webpack.config.js中添加 plugins: [ //所有页面都可以使用jQuery new webpack.ProvidePlugin({ $: 'jquery' }), ] toast.js代码var $ = require('jquery') 删除也可以使用jQuery了。 在toast.js中添加require('less/toast.less'),使用less 在webpack.config.js中添加 module:{ rules: [ { //当require一个东西的时候,会进行检测 test: /\.less$/, //正则表达式,以less为后缀 use: ["style-loader","css-loader","less-loader"] //使用这些loader,向前解析 less解析成css,放到页面上 } ] }, npm install --save css-loader less-loader style-loader less 安装 使用less
-
新建event.js(src ==> js ==> mod == event.js)
// 设计模式:发布订阅模式;用于组件之间进行一个解耦 var EventCenter = (function(){ var event = {}; function on(evt,handler){ events[evt] = events[evt] || []; events[evt].push({ handler: handler }); } function fire(evt,args){ if(!events[evt]){ return } for(var i = 0;i>events[evt].length;i++){ events[evt][i].handler(args); } } return { on: on, fire:fire } })();
-
新建waterfall.js(src ==> js ==> mod == waterfall.js)
//瀑布流布局 var WaterFall = (function(){ var $ct; var $items; function render($c){ $ct = $c; $items = $ct.children(); var nodeWidth = $items.outerWidth(true), colNum = parseInt($(window).width()/nodeWidth), colSumHeight = []; for(var i = 0 ;i<colNum;i++){ colSumHeight.push(0); } $items.each(function(){ var $cur = $(this); // colSumHeight = [100, 250, 80 , 200] var idx = 0, minSumHeight = colSumHeight[0]; for(var i = 0;i<colSumHeight.length;i++){ if(colSumHeight[i] < minSumHeight){ idx = i; minSumHeight = colSumHeight[i]; } } $cur.css({ left: nodeWidth*idx, top: minSumHeight }); colSumHeight[idx] = $cur.outerWidth(true) + colSumHeight[idx]; }); } $(window).on('resize',function(){ render($ct); }) return{ init: render } })() // WaterFall.init($('#content')) module.exports = WaterFall
-
新建note.js (src ==> js ==> mod == note.js)
require('less/note.less'); //拿到样式 /* { id: ''; text: 'hello';} */ var Toast = require('./toast.js').Toast; var Event = require('mod/event.js'); function Note(opts){ this.initOpts(opts); this.createNote(); //创建便利贴 this.setStyle(); //设置样式 this.bindEvent(); } Note.prototype = { colors: [ ['#ea9b35','#efb04e'], // headColor, containerColor ['#dd598b','#e672a2'], ['#eee34b','#f2eb67'], ['#c24226','#d15a39'], ['#c1c341','#d0d25c'], ['#3f78c3','#5591d2'] ], defaultOpts: { id: '', //Note的 id $ct: $('#content').length>0?$('#content'):$('body'), //默认存放Note 的容器 context: 'input here' //Note 的内容 }, initOpts: function (opts) { //默认一个初始化,设置id和参数 this.opts = $.extend({}, this.defaultOpts, opts||{}); if(this.opts.id){ this.id = this.opts.id; } }, createNote: function () { var tpl = '<div class="note">' + '<div class="note-head"><span class="delete">×</span></div>' + '<div class="note-ct" contenteditable="true"></div>' +'</div>'; // 创建html,放到网页上 // contenteditable 不是input,设置H5属性,可以编辑 this.$note = $(tpl); this.$note.find('.note-ct').html(this.opts.context); this.opts.$ct.append(this.$note); if(!this.id) this.$note.css('bottom', '10px'); //新增放到右边 }, setStyle: function () { var color = this.colors[Math.floor(Math.random()*6)]; this.$note.find('.note-head').css('background-color', color[0]); this.$note.find('.note-ct').css('background-color', color[1]); }, setLayout: function(){ var self = this; if(self.clk){ clearTimeout(self.clk); } self.clk = setTimeout(function(){ Event.fire('waterfall'); //发送一个瀑布流 },100); }, bindEvent: function () { var self = this, $note = this.$note, $noteHead = $note.find('.note-head'), $noteCt = $note.find('.note-ct'), $delete = $note.find('.delete'); $delete.on('click', function(){ self.delete(); //调用删除 }) //contenteditable没有 change 事件,所有这里做了模拟通过判断元素内容变动,执行 save $noteCt.on('focus', function() { if($noteCt.html()=='input here') $noteCt.html(''); $noteCt.data('before', $noteCt.html()); }).on('blur paste', function() { if( $noteCt.data('before') != $noteCt.html() ) { $noteCt.data('before',$noteCt.html()); self.setLayout(); if(self.id){ self.edit($noteCt.html()) }else{ self.add($noteCt.html()) } } }); //设置笔记的移动 $noteHead.on('mousedown', function(e){ // 当鼠标点下去的时候 var evtX = e.pageX - $note.offset().left, //evtX 计算事件的触发点在 dialog内部到 dialog 的左边缘的距离 evtY = e.pageY - $note.offset().top; $note.addClass('draggable').data('evtPos', {x:evtX, y:evtY}); //把事件到 dialog 边缘的距离保存下来 }).on('mouseup', function(){ // 鼠标移开的时候,把class删除掉 $note.removeClass('draggable').removeData('pos'); }); $('body').on('mousemove', function(e){ $('.draggable').length && $('.draggable').offset({ top: e.pageY-$('.draggable').data('evtPos').y, // 当用户鼠标移动时,根据鼠标的位置和前面保存的距离,计算 dialog 的绝对位置 left: e.pageX-$('.draggable').data('evtPos').x }); }); }, edit: function (msg) { //当需要编辑的时候 var self = this; $.post('/api/notes/edit',{ id: this.id, note: msg }).done(function(ret){ if(ret.status === 0){ Toast('update success'); }else{ Toast(ret.errorMsg); } }) }, add: function (msg){ console.log('addd...'); var self = this; $.post('/api/notes/add', {note: msg}) .done(function(ret){ if(ret.status === 0){ Toast('add success'); }else{ self.$note.remove(); Event.fire('waterfall') Toast(ret.errorMsg); } }); //todo }, delete: function(){ var self = this; $.post('/api/notes/delete', {id: this.id}) .done(function(ret){ if(ret.status === 0){ Toast('delete success'); self.$note.remove(); Event.fire('waterfall') }else{ Toast(ret.errorMsg); } }); } }; module.exports.Note = Note;
-
新建note-manager.js(src ==> js ==> mod == note-manager.js)
// 获取数据,添加数据 var Toast = require('./toast.js').Toast; var Note = require('./note.js').Note; var Toast = require('./toast.js').Toast; var Event = require('mod/event.js'); var NoteManager = (function(){ function load() { $.get('/api/notes') .done(function(ret){ if(ret.status == 0){ $.each(ret.data, function(idx, article) { new Note({ id: article.id, context: article.text }); }); Event.fire('waterfall'); }else{ Toast(ret.errorMsg); } }) .fail(function(){ Toast('网络异常'); }); } function add(){ new Note(); } return { load: load, add: add //获取数据,和添加 } })(); module.exports.NoteManager = NoteManager
-
修改index.js (src ==> js ==> app == index.js)
清空测试代码,添加以下 require('less/index.less'); var NoteManager = require('mod/note-manager.js').NoteManager; var Event = require('mod/event.js'); var WaterFall = require('mod/waterfall.js'); NoteManager.load(); //加载所有的数据 $('.add-note').on('click', function() { // 点击添加按钮,调用添加 NoteManager.add(); }) Event.on('waterfall', function(){ //事件听到waterfall的时候,执行一次瀑布流 WaterFall.init($('#content')); })
添加index.less
-
约定接口
1.获取所有的note: GET /api/notes req:{} res:{ status: 0, data:[{},{}]} {status: 1,errorMsg:'失败的原因'} 2.创建一个note:POST: /api/notes/add req:{note: 'hello world'} res:{ status:0} {status: 1,errorMsg:'失败的原因'} 3.修改一个note:POST:/api/notes/edit req:{note: 'new note',id:100} 4.删除一个note:POST:/api/notes/detele req:{id:100} app.js 添加 app.use('/api',api) routes中api.js添加以上接口 router.get('/notes', function(req, res, next) { console.log('/notes') }); router.post('/notes/add', function(req, res, next) { var note = req.body.note; console.log('add...') }); router.post('/notes/edit', function(req, res, next) { }); router.post('/notes/delete', function(req, res, next) { });
-
数据库
SQL 教程
npm mysql
sequelize
sequelize文档 V3
npm install --save sequelize
npm install --save sqlite3 如果node-v48-win32-x64.tar.gz卡住了,赋值url,fq下载, 替换到node_modules/sqlite3/lib/binding 里面的 node-v48-linux-x64
使用sequelize var Sequelize = require('sequelize'); var sequelize = new Sequelize('database', 'username', 'password'); // 连接数据库 //定义一个表,就是一个模型,对应数据库里一个表 var User = sequelize.define('user', { // 对应数据库里,user表 username: Sequelize.STRING, //string birthday: Sequelize.DATE //date }); sequelize.sync().then(function() { //创建这个表 return User.create({ //创建一个数据 username: 'janedoe', birthday: new Date(1980, 6, 20) }); }).then(function(jane) { console.log(jane.get({ plain: true })); });
-
node端如何调试
npm install -g node-inspector 安装调试工具 node-inspector 启动调试软件(默认占用8080端口) // 注意提示调试网址,通过该网址进行调试 还需要开启服务器 node --debug bin/www 刷新服务器网址,再刷新调试网站即可。
-
检测数据能否使用
在项目目录下新建model ==> note.js var sequelize = new Sequelize(undefined, undefined, undefined, { //不需要用户名密码,如果是其他数据库需要做义工配置 host: 'localhost', dialect: 'sqlite', storage: '../database/database.sqlite' }); //测试是否成功,使用完成就可注释掉 sequelize .authenticate() .then(function(err){ console.log('Connection has been established successfully.'); }) .catch(function(err){ console.log('Unable to connect to the database:',err); }) routes ==》 aip.js var Note = require('../model/note') router.get('/notes', function(req, res, next) { var data = Note.getAll() //模型,用于具体的和数据进行操作。 //假设有个对象叫Note,有getAll方法,把他所有数据赋值给data res.send({status: 0, data: notes}) }); cd model node note.js 会在database文件夹下生成空的database.sqlite
-
向数据库添加数据
note.js添加数据,创建模型,一个模型,对应数据库的一个表 // id [字段] [添加时间] [更新时间] //模型起名叫note var Note = sequelize.define('note', { text: { type: Sequelize.STRING //向数组添加字段,类型为string,会默认添加id 添加时间 更新时间 } }); //sync({force: true}) 假设数据库存在这个表,force:true删除。 Note.sync().then(function(){ Note.create({text:'hello world'}) }).then(function(){ Note.findAll().then(function (notes) { //findOne({raw: true}) 获取里面的数据 console.log(notes); }) }) //添加完成后注释 cd model 运行 node note.js Note.findOne({raw: true, where:{id:2}}).then(function(notes){ console.log(notes) }) //findOne({raw: true}) 获取里面的数据,如果不加,会把所有数据展示 // 会输出 id 为 2 的数据。
-
api.js中的数据增删改查
router.post('/notes/add', function(req, res, next) { if(!req.session || !req.session.user){ return res.send({status: 1, errorMsg: '请先登录'}) } var uid = req.session.user.id var note = req.body.note; Note.create({text: note, uid: uid}).then(function(){ res.send({status: 0}) //status: 0 成功 }).catch(function(){ res.send({status: 1,errorMsg: '数据库出错'}) //status: 1 出错 }) console.log('add...',note) }) router.post('/notes/edit', function(req, res, next) { if(!req.session || !req.session.user){ return res.send({status: 1, errorMsg: '请先登录'}) } var uid = req.session.user.id Note.update({text: req.body.note},{where:{id:req.body.id,uid:uid}}).then(function(){ console.log(arguments) res.send({status:0}) }).catch(function(){ res.send({status: 1,errorMsg: '数据库出错'}) //status: 1 出错 }) }); router.post('/notes/delete', function(req, res, next) { if(!req.session || !req.session.user){ return res.send({status: 1, errorMsg: '请先登录'}) } var uid = req.session.user.id Note.destroy({where:{id:req.body.id,uid:uid}}).then(function(){ res.send({status:0}) }).catch(function(){ res.send({status: 1,errorMsg: '数据库出错'}) //status: 1 出错 }) });
-
修改路径
使用更稳定的路径方式 model ==》 note.js var path = require('path') //添加到页面 storage: path.join(__dirname,'../database/database.sqlite') //修改页面代码 module.exports.Note = Note; //导出,提供给其外部使用 routes ==> api.js var Note = require('../model/note').Note; //修改代码,引用note.js
-
登录功能
阮一峰 oauth 2.0文章
oauth 2是一个认证协议。当点击第三方登录的时候,跳转到第三方网站,登录成功后,返回当前页面。中间oauth2.0做一个支撑。(点击登录,向后台发起请求,后台向第三方接口请求,登录成功,网站后台拿到一个key,返回页面。)- app.js增加第三方登录接口
var auth = require('./routes/auth'); app.use('/auth', auth);
- 增加auth路由
routes ==> 新建auth.js
- 使用passport,第三方登录
npm passport
npm install passport
passport-github
npm install passport-github
GitHub申请第三方登录授权。https://github.com/settings/ - app.js 添加session中间键
npm install express-session
var passport = require('passport'); var session = require('express-session'); app.use(session({secret: '[随便写一串字符串,作为一个秘钥]'})); app.use(passport.initialize()); app.use(passport.session());
cookie 和 session的关系
http是一个无状态的协议,每次请求不知道是谁发的请求,只知道有一个请求url过来,需要做什么事情。
需求:用户登录了之后,刷新了页面,下次还是登录状态。需要记录用户的状态。
这个时候,需要使用cookie和session,当使用用户名和密码提交给后台的时候,后台会从数据库里面查询,(一般密码都不是明文,都是经过MD5和SHA1加密的,会把密码从新进行一次加密。),加密好了之后,从数据库查询用户名,得到之后,再把加密后的密码进行匹配。如果匹配上了,就表示登录上了,表示登录上了也没用,所有需要记录登录状态,把用户的信息,再服务器里面创建一个session,一个session可以认为是一个数据对象,在内存里面,session有一个key,key对应的value就是session,session可以存储在任何地方,默认存在内存里,没有把他存在某个地方,是一个变量,存在服务器内存,key就是一串很长的数值。
用户请求过来,向用户展示页面,在服务器通过send cookie,相当于http请求,当用户打开页面的时候,发现http的请求里面,有一个叫cookie,会把key存到cookie里面,里面的文件connect.sid传字符串。
在刷新页面,就会把所有请求都带上,同时cookie也发到服务器里面,服务器就通过这个cookie,就从刚才的数据内存里面去查找,得到这个对象,然后得到这个用户,展示和这个用户相关的信息。
session是服务端的对象,有个key,把key发给浏览器,浏览器记录下来,这就是cookie。-
添加登录注销效果
- index.ejs
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta property="qc:admins" content="4562636714562571563145" /> <title><%= title %></title> <link rel="stylesheet" href="/css/index.css"> </head> <body> <div id="header"> <a class="add-note" title="添加笔记" href="#"><span class="fa fa-plus"></span> 添加</a> <ul class="user-area"> <% if (isLogin){ %> <li><img src="<%= user.avatar %>" alt=""></li> <li><span title="<%= user.username %>"><%= user.username %></span></li> <li><span class="line"> | </span> </li> <li><a class="logout" href="/auth/logout">注销</a></li> <%} else { %> <li><a class="login" title="GitHub登录" href="/auth/github"> GitHub登录</a> </li> <% } %> </ul> </div> <div id="content"> </div> <div class="stars"></div> <script src="/js/index.js"></script> <!-- <div class="twinkling"></div> --> </body> </html>
- index.js
var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { res.render('index', { title: '我的便利贴' }); }); router.get('/', function(req, res, next) { var data; if(req.session.user){ data = { isLogin: true, user: req.session.user } }else{ data = { isLogin: false } } console.log(data) res.render('index', data); }); module.exports = router;
- auth.js
var express = require('express'); var router = express.Router(); var passport = require('passport'); //引入passport,专门负责auth2的认证,所有的第三方登录,都可以使用passport var GitHubStrategy = require('passport-github').Strategy; //在passport基础上,对整个协议进行的封装。 passport.serializeUser(function(user, done) { console.log('---serializeUser---') console.log(user) done(null, user); }); //把用户登陆过来的信息,传递到passport之后,让它生成一个session,存储到内存里面 passport.deserializeUser(function(obj, done) { console.log('---deserializeUser---') done(null, obj); }); // 用户刷新页面的时候,会再从内存里面,把对应的session拿出来,解析,知道这个用户 passport.use(new GitHubStrategy({ clientID: '28928eae8774e53d2247', clientSecret: '0485fe61b10dac188ff17d977253ba091113f94d', callbackURL: "http://127.0.0.1:3000/auth/github/callback" }, function(accessToken, refreshToken, profile, done) { // User.findOrCreate({ githubId: profile.id }, function (err, user) { // }); done(null, profile); } )); //入口 router.get('/github', passport.authenticate('github')); router.get('/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), function(req, res) { req.session.user = { id: req.user.id, username: req.user.displayName || req.user.username, avatar: req.user._json.avatar_url, provider: req.user.provider }; res.redirect('/'); }); //注销 router.get('/logout',function(req,res){ req.session.destroy() //销毁session res.redirect('/') //跳转到首页 }) module.exports = router;
npm start 开启本地服务器