NodeJs——(16)用Nodejs 4.X版本,制作一个微博网站(多图,详细步骤)

添加微信交流群(需加微信拉您进入)

 

http://blog.csdn.net/qq20004604/article/details/52019904

(填坑中……进度略慢了一些)

最后更新时间8/1凌晨

注册、登录、语言切换,发表博客功能完成

范例网址:121.41.66.68

 

【0】涉及到的框架、引擎、数据库:

① express 4.X

② jade

③ mysql 5.7.x

 

注:

①内容较长,我会尽力把整个框架、结构、顺序、思考方式说明白。

②基础是《node.js开发指南》这本书,作者:BYVoid。但他书的版本较老,很多东西现在已经无法应用,故进行更新,使用目前普遍的express 4.x版本(原书似乎是2.X版本),mysql(原书是mongodb),jade(原书是ejs)

 

【1】基本需求:

①有首页;

②支持注册;

③支持登录、登出;

④可以发表博客,发表的博客可以保存到数据库;

⑤可以查看博客,博客从数据库中读取,为了简化,这里设置为查看所有人的博客;

⑥查看博客时,初始查看的数量有限,但可以无限加载新的博客,直到查看完全部博客;

⑦一定程度上实现多语言(即可以切换显示的语言版本),但由于复杂度所限,因此只部分实现(但框架已建立好,可以通过继续完善代码实现整体的国际化);

⑧根据登录状态,对可以访问的页面进行控制(某些允许某些禁止)

 

 

【2】前后端交互的简单过程:

 

 

【3】关于客户端请求的几种情况(涉及到数据库的)

 

 

【4】npm

npm是包管理器,在新版本里是默认安装好的,可以输入:

npm -v

来查看npm的版本

 

 

 

 

【5】express框架:

首先安装基础的express框架,他是封装好的web开发框架,里面包含了:

①路由控制、

②log显示、

③解析客户端请求、

④cookies解析、

⑤静态文件的控制

等多种功能。

 

安装前注:

①有的人只需要简单的

npm install -g express

npm install -g express-generator

就可以愉快的跑起express了,有的人就像向我一样苦逼,尝试各种办法,最后勉强可以用。

 

如果在这两行命令后,输入(V是大写的)

express -V

会返回版本号,那么直接跳到最后来看,如果不是这样,可以参考我写的记录来安装。

 

或者直接看后面的终极解决方案

 

 

 

②express设置全局方法:

ln -s /usr/nodejs4.4.7/node-v4.4.7-linux-x64/bin/express /usr/local/bin/express

其他需要全局的方法,理论上同理,即将nodejs安装目录下的bin文件夹下的模块,映射到/usr/local/bin/同名文件即可

 

 

 

③express命令可以使用的人:

输入:

express -t jade myblog

 

效果是建立一个文件夹名为myblog的文件夹,里面会有一些文件。

正常如下图:

 

 

 

 

 

cd myblog

npm install

 

这时,npm会根据package.json来安装一些东西。

 

按提示输入:

SET DEBUG=myblog:*

npm start

 

可以启动项目。

本机的话,通过访问http://127.0.0.1:3000/ 来查看效果

服务器的话,访问其公网ip,端口是3000

 

查看package.json

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. "scripts": {  
  2.   "start""node ./bin/www"  
  3. },  

 

 

 

这条属性告诉我们,需要通过bin文件夹下的www来启动,这也就是上面npm start命令的作用。

 

www文件应该是设置自启动的,然而我这里并不需要。但若直接启动app.js是启动不了的,因为在www文件里面,设置了端口是3000,他是通过www文件来启动监听的。

解决办法:

在app.js里面,在最后一行代码之前,添加:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. app.listen(80);  

 

 

 

于是,便可以通过app.js来启动服务器了。

 

访问效果如图:

 

 

 

 

 

 

假如如果像我一样倒霉,无法用express命令,打开npm也特别慢,可以先找个系统,将这些文件下载好,然后将这些文件复制到不可以用express命令的linux系统下面。

 

 

或者看最后的终极解决方案

 

ps:

如果npm很慢的话,可以考虑装cnpm

npm install -g cnpm --registry=https://registry.npm.taobao.org

 

然后在npm install这一步,使用cnpm install来替代

 

————————分割线————————

 

④超级终极解决方案:

我传一个装好express的压缩包,直接解压缩后就可以用。

链接:

http://download.csdn.net/detail/qq20004604/9587054

 

 

 

⑤另一个启动方法

即不通过app.js来启动;

在之前④的基础上,打开app.js,删除

app.listen(80);

 

打开package.json,将

"start":"node ./app.js"

改回

start":"node ./bin/www"

 

然后

cd bin

vi www

 

var port =normalizePort(process.env.PORT||'3000');

修改为

var port =normalizePort(process.env.PORT||'80');

 

然后

cd ..

npm start

 

启动成功,可以直接访问地址来访问页面

 

 

【6】把启动的app.js放在后台运行,并且在操作端解除控制后依然可以运行

原本使用npm start的地方,输入

nohup npm start&

即可

 

 

【7】app.js之解释

app.js可以说是一切的根本,因此请看注释,解释了每行代码的作用:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express');  //这个模块在node_modules文件夹中  
  2. var path = require('path'); //nodejs自带的,路径相关  
  3. var favicon = require('serve-favicon'); //node_modules文件夹中  
  4. var logger = require('morgan'); //日志相关,node_modules文件夹中,具体看第19行代码  
  5. var cookieParser = require('cookie-parser');//node_modules文件夹中,用来解析cookie的,被express所调用  
  6. var bodyParser = require('body-parser');    //用于解析请求体的(应该),被express所调用  
  7.   
  8. var routes = require('./routes/index'); //这里是默认的路由,路径是routes/index.js  
  9. var users = require('./routes/users'); //这里是默认的路由,路径是routes/users.js  
  10. //关于路由的更多内容看下面的app.use('/', routes);和app.use('/users', users);的注释  
  11.   
  12. var app = express();    //生成一个express的实例  
  13.   
  14. //设置模板引擎  
  15. app.set('views', path.join(__dirname, 'views'));    //__dirname表示绝对路径  
  16. //console.log(__dirname)  //可以取消这一行注释,然后看看__dirname的效果  
  17. app.set('view engine''jade'); //表示express是使用jade格式的模板的  
  18.   
  19.   
  20. //下面这行代码是设置左上角的小图标的,将其命名为favicon.ico并放在public文件夹下,如果有的话,取消这行注释  
  21. //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));  
  22.   
  23. app.use(logger('dev')); //设置日志显示,如果不需要的话可以注释掉这行代码,建议练手的时候不要注释掉  
  24. //可替换参数有default,combined,common,short,tiny,dev(相对tiny有染色)。可以自己试试效果,默认是dev  
  25.   
  26. app.use(bodyParser.json()); //解析客户端的请求,通常是通过post发送的内容  
  27. app.use(bodyParser.urlencoded({extended: false})); //另一种解析方式,具体不太明白(如果上面解析失败的话会通过这个来)  
  28. app.use(cookieParser());    //cookies的解析  
  29. app.use(express.static(path.join(__dirname, 'public')));    //普通静态html文件、js文件、css文件,都放在public文件夹下,可以直接通过url来访问  
  30.   
  31. //路由的处理  
  32. app.use('/', routes);   //假如访问的网址是根目录,例如http://121.41.66.68/,交给routes这个js文件来处理,具体请查看routes  
  33. app.use('/users', users);   //假如访问的是/users这样的路径,那么交给users这个js文件来处理,具体略  
  34. //我们最后要对这个路由进行处理,让他按照我们想要的方式来做  
  35.   
  36. //这里对非法路径进行处理的,next表示这是一个中间件(即执行完他之后,还会执行下一个,而不是直接在这里结束了)  
  37. //如果上面没有静态文件(29行)、没有找到被路由处理的文件(32,33行),就会交给这个来处理。  
  38. app.use(function (req, res, next) {  
  39.     var err = new Error('Not Found');  
  40.     err.status = 404;  
  41.     next(err);  //由下面的2个app.use中的一个来处理(一般是第一个,除非第一个被注释)  
  42. });  
  43.   
  44.   
  45. //原注释说是会对错误进行加亮处理  
  46. //这部分和下面的区别在于,这部分会将错误信息暴露给用户,而下面的不会,因此注释掉这部分  
  47. //if (app.get('env') === 'development') {  
  48. //    app.use(function (err, req, res, next) {  
  49. //        res.status(err.status || 500);  
  50. //        res.render('error', {  
  51. //            message: err.message,  
  52. //            error: err  
  53. //        });  
  54. //    });  
  55. //}  
  56.   
  57. // production error handler  
  58. // no stacktraces leaked to user  
  59. app.use(function (err, req, res, next) {  
  60.     res.status(err.status || 500);  
  61.     res.render('error', {   //这里的error指调用views下的error模板  
  62.         message: err.message,  
  63.         error: {}  
  64.     });  
  65. });  
  66.   
  67. //这是我自行添加的,用于显示服务器启动的时间  
  68. console.log("Server start at :" + new Date());  
  69.   
  70. //导出app,在./bin/www里会被调用  
  71. module.exports = app;  

 

 

 

【8】页面关系结构

请忽略文件名的大小写问题(手动微笑)

 

 

 

 

因此,首先有三个需要我们自定义的路由:

①当访问根/时,输出index;

②当访问/login时,输出login

③当访问/reg时,输出reg

 

其他可能的路由:

④后续可能添加的页面,暂缺;

⑤不符合要求的页面,输出404(express已经完成)

 

 

 

【9】index页面需要包含的功能:

 

 

 

 

 

【10】index的路由:

查看app.js中的代码:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. app.use('/', routes);    

这部分代码已经说明了,当访问根的时候,交给routes来处理;

 

再次查看导入routes的地方:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var routes = require('./routes/index');  

说明负责这部分的文件是routes文件夹的index文件

 

这时打开index.js,

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. router.get('/'function(req, res, next) {  
  2.   res.render('index', { title: 'Express' });  
  3. });  

这部分代码,表示当访问根的 ’/’ 页面时,由index这个引擎模板来处理,模板中的title变量,其值为Express。

 

————————————————————————

代码说明:

router.get('/'

以上这段代码是在路由基础上进行二段捕获url的,举例:

①假如在app.js里,

app.use('/',

然后在路由的处理里,是

outer.get('/'

那么最终针对的url是/

 

②假如在app.js里,

app.use('/test',

然后在路由的处理里,是

outer.get('/'

那么最终针对的url是/test

 

③假如在app.js里,

app.use('/,

然后在路由的处理里,是

outer.get('/test'

那么最终针对的url依然是/test

 

④假如在app.js里,

app.use('/testA,

然后在路由的处理里,是

outer.get('/testB'

那么最终针对的url是/testA/testB

 

⑤捕获优先度说明:

首先根据app.js中的app.use出现顺序决定,假如②和③同时出现,那么看②和③在app.js中的代码谁先出现,就交给谁来处理;

 

再不调用next()方法的情况下,只会被最先出现的那个处理;

 

如果第一个调用了next()方法,那么第一个处理完后会执行第二个的

————————————————————————

 

index.js代码说明:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); //调用express  
  2. var router = express.Router();  //生成express的Router方法的一个实例  
  3.   
  4. //处理函数  
  5. router.get('/'function (req, res, next) {  //捕获根url  
  6.     res.render('index', {title: 'Express'});  
  7.     //res.render方法渲染一个引擎模板,  
  8.     //第二个参数是一个对象,对象里的变量可以在引擎中使用,  
  9.     //第三个参数是回调函数,提供两个参数,分别是err和html,err是错误,html是渲染后的页面。如果使用这个回调函数,那么将不会自动响应,即要用户自己写返回html的命令  
  10. });  
  11.   
  12. module.exports = router;  

 

注意,被引擎渲染的文件,默认是在myblog/views文件夹下

 

 

【11】使用Bootstrap界面

让我们自己设计界面不是不可以,不过太麻烦,因此我和原书保持一致,使用Twitter Bootstrap风格的页面。

 

下载他的压缩包文件,并解压缩他,

将css文件放在项目myblog/public/stylesheets下;

将img文件夹直接复制粘贴在myblog/public下;

将js文件放在myblog/public/javascripts下,

 

我的Bootstrap的版本是v2.3.2

 

 

另外,下载jquery,命名为jq.js,放在myblog/public/javascripts下

 

 

 

【12】index.jade说明

下来我们就要修改jade文件了,如果不知道怎么使用的话,可以看我的博客:

http://blog.csdn.net/qq20004604/article/details/51773574

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. extends layout  
  2.   
  3. block content  
  4.   h1= title  
  5.   p Welcome to #{title}  

 

 

 

 

第一行的extends layout表示他导入的layout模板(即layout.jade)

 

block content表示以下这部分将套入layout的content位置

 

内容略。

 

这时候再过去看layout.jade

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. doctype html  
  2. html  
  3.   head  
  4.     title= title  
  5.     link(rel='stylesheet', href='/stylesheets/style.css')  
  6.   body  
  7.     block content  

 

 

 

他导入的是style.css这个样式表,页面的title是变量title

上面的index.jade中的内容将导入这里的block content区域(因为变量名一样,会对应替换)。

 

其结构大概如下:

 

 

 

 

然后我们根据自己实际需求来改造:

 

直接给出代码:

首先在stylesheets文件夹下创建一个blog.css文件,内里样式为:

[css] view plain copy  在CODE上查看代码片派生到我的代码片

  1. body {  
  2.     padding-top: 60px;  
  3.     padding-botom: 40px;  
  4. }  
  5.   
  6. #textarea {  
  7.     resize: none;  
  8.     width: 300px;  
  9.     height: 100px;  
  10.     cursor: text;  
  11. }  
  12.   
  13. #postBlog {  
  14.     position: relative;  
  15.     left: 20px;  
  16.     vertical-align: top;  
  17. }  
  18.   
  19. #clearBlog {  
  20.     position: relative;  
  21.     left: -90px;  
  22.     top: 27px;  
  23.     width: 110px;  
  24.     height: 44px;  
  25. }  
  26.   
  27. .myalert {  
  28.     position: absolute;  
  29. }  
  30.   
  31. .displayNONE {  
  32.     display: none;  
  33. }  
  34.   
  35. #scrollToFoot {  
  36.     border: 1px solid #ccc;  
  37.     text-align: center;  
  38.     font-size: 18px;  
  39.     padding: 20px 0;  
  40. }  
  41.   
  42. fotter p {  
  43.     margin-top: 40px;  
  44.     padding-top: 20px;  
  45.     border-top: 1px solid #aaa;  
  46.     font-size: 18px;  
  47. }  
  48.   
  49. .row {  
  50.     color: #555;  
  51. }  

 

 

 

 

其次是layout.jade

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. doctype html  
  2. html  
  3.     head  
  4.         title MyBlog By qq20004604  
  5.         link(rel='stylesheet', href='./stylesheets/bootstrap.min.css')  
  6.         link(rel='stylesheet', href='./stylesheets/bootstrap-responsive.min.css')  
  7.         link(rel='stylesheet', href='./stylesheets/blog.css')  
  8.         script(type="text/javascript",src='javascripts/jq.js')  
  9.         script(type="text/javascript",src='javascripts/bootstrap.min.js')  
  10.         script(type="text/javascript",src='javascripts/blog.js')  
  11.     body  
  12.         div.navbar.navbar-fixed-top  
  13.             div.navbar-inner  
  14.                 div.container  
  15.                     a.btn.btn-navbar(data-toggle="collapse",data-target=".nav-collapse")  
  16.                         span.icon-bar  
  17.                         span.icon-bar  
  18.                         span.icon-bar  
  19.                     a.brand(href="/") MyBlog  
  20.                     div.nav-collapse  
  21.                         ul.nav  
  22.                             li  
  23.                                 a(href="/") 首页  
  24.                             li  
  25.                                 a(href="/logout") 登出  
  26.                             li  
  27.                                 a(href="/login") 登入  
  28.                             li  
  29.                                 a(href="/reg") 注册  
  30.                             li  
  31.                                 a(href="/language") 切换语言  
  32.         div#contrainer.container  
  33.             block content  
  34.             hr  
  35.         fotter  
  36.             p   作者:王冬   QQ:20004604  

 

效果如图:

 

 

 

然后是index.jade:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. extends layout  
  2. block content  
  3.     div.hero-unit  
  4.         h1  我的博客  
  5.         p   这个是基于Nodejs作为后台,jade作为模板来,使用了Express作为框架  
  6.             br  
  7.             br  
  8.             //这部分暂时用1替代,后续会被更新  
  9.             if(1)  
  10.                 br  
  11.                 br  
  12.                 a.btn.btn-primary.btn-large(href="/login")  登录  
  13.                 a.btn.btn-large(href="/reg")    立即注册  
  14.             else  
  15.                 textarea#textarea.uneditable-input  
  16.                 button#postBlog.btn.btn-large 提交微博  
  17.                 button#clearBlog.btn.btn-large 清空  
  18.             div#submitError.alert.alert-error.displayNONE.myalert  
  19.             div#submitSuccess.alert.alert-success.displayNONE  
  20.   
  21.     div.row.content  
  22.         div.span4  
  23.             h2  烟雨江南说  
  24.             p   当欲望没有了枷锁,就没有了向前的路  
  25.             p   只有转左,或者向右  
  26.             p   左边是地狱,右边也是地狱  
  27.         div.span4  
  28.             h2  烟雨江南说  
  29.             p   那些都是极好极好的  
  30.             p   可是我偏偏不喜欢  
  31.         div.span4  
  32.             h2  烟雨江南说  
  33.             p   我不怕傻  
  34.             p   只怕  
  35.             p   遇不到  
  36.             p   可以让我变傻的人  
  37.         div.span4  
  38.             h2  烟雨江南说  
  39.             p   人在年轻的时候总会有些莫名的坚持,  
  40.             p   并且以此感动着自己,  
  41.             p   却时常会在不经意间让真正重要的东西从指间流走。  
  42.         div.span4  
  43.             h2  烟雨江南说  
  44.             p   记忆真是一种奇怪的东西,  
  45.             p   有时候会涤荡所有的苦难,只留下温情,  
  46.             p   有时候却磨灭掉曾有的欢乐,唯剩下苍白和丑陋。  
  47.         div.span4  
  48.             h2  烟雨江南说  
  49.             p   那存在的,都是幻影。  
  50.             p   那永恒的,终将毁灭。  
  51.             p   世界万物,缤纷色彩,都是被蒙蔽的人心罢了。  
  52.         div.span4  
  53.             h2  烟雨江南说  
  54.             p   诸神以真相示人,而世人却视而不见  
  55.         div.span4  
  56.             h2  烟雨江南说  
  57.             p   只有绵羊会向狮子要求平等,  
  58.             p   而狮子们从来不会这样想。  
  59.         div.span4  
  60.             h2  烟雨江南说  
  61.             p   愿迷途的旅人,从此得享安息。  
  62.             p   因理想而不朽,因归返而救赎。  
  63.     div#scrollToFoot 滚动到底部然后加载内容  

 

 

 

效果如图:

 

 

 

 

但此时,上面的页面切换目前还都是无效状态;

滚动然后加载内容,也正处于无效状态;

注册和登录按钮,点击后也无法正常跳转;

 

我们需要依次解决这些问题。

 

 

【13】登录页面

接下来我们添加登录页面的路由和模板。

 

路由,打开app.js,在

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. app.use('/', routes);   
  2. app.use('/users', users);  

 

之前添加

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var reg = require('./routes/reg');  
  2. var login = require('./routes/login');  

 

之后添加:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. app.use('/reg', reg);    //注册的,reg.js来处理  
  2. app.use('/login', login);  //登录的,login来处理  

 

这样的话,就添加了注册和登录页面的路由了,但目前,我们还缺其具体文件和代码。

 

 

进入routes文件夹,创建reg.js和login.js

reg.js

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); //调用express模块  
  2. var router = express.Router();  //调用模块的Router方法  
  3. router.get('/'function (req, res, next) {  
  4.     res.render('reg')  
  5. });  
  6. module.exports = router;  

 

login.js

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); //调用express模块  
  2. var router = express.Router();  //调用模块的Router方法  
  3. router.get('/'function (req, res, next) {  
  4.     res.render('login')  
  5. });  
  6. module.exports = router;  

 

现在又缺模板文件了。

 

进入views文件夹,创建reg.jade和login.jade

reg.jade

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. extends layout  
  2. block content  
  3.     form.form-horizontal(method="post")  
  4.         fieldset  
  5.             legend  注册  
  6.             div.control-group  
  7.                 label.control-label(for="username") 用户名  
  8.                 div.controls  
  9.                     input.input-xlarge#username(type="text",name="username")  
  10.                     p.help-block    你的账户名称,用于登录和提示  
  11.   
  12.             div.control-group  
  13.                 label.control-label(for="password") 口令  
  14.                 div.controls  
  15.                     input.input-xlarge#password(type="password",name="password")  
  16.   
  17.             div.control-group  
  18.                 label.control-label(for="password-repeat")  重复输入口令  
  19.                 div.controls  
  20.                     input.input-xlarge#password-repeat(type="password",name="password-repeat")  
  21.   
  22.             div.form-actions  
  23.                 button.btn.btn-priamry(type="submit")   注册  

 

login.jade

 

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. extends layout  
  2. block content  
  3.     form.form-horizontal(method='post')  
  4.         fieldset  
  5.             legend  登录  
  6.             div.control-group  
  7.                 label.control-label(for='username') 用户名  
  8.                 div.controls  
  9.                     input.input-xlarge#username(name='username',type='text')  
  10.             div.control-group  
  11.                 label.control-label(for='password') 密码  
  12.                 div.controls  
  13.                     input.input-xlarge#password(name='password',type='password')  
  14.             div.form-actions  
  15.                 button.btn.btn-primary(type="submit") 登录  

 

 

 

这个时候,注册和登录页面已经可以访问了(虽然还没有添加逻辑)

 

 

 

【14】在和MySQL交互之前的准备工作

首页、注册、登录页面的页面布局已经做完,下来我们需要完成注册和登录功能。

 

注册功能的流程:

 

 

 

第①和②略过;

第③步由js完成,因此要对reg.jade进行修改,同时也需要添加处理登录逻辑的js代码;

第④步在第③步验证一致后发起ajax请求,和第③步一并完成;

第⑤步由后端完成,具体流程为:

 

 

 

 

 

第⑥步是ajax请求的回调函数,在屏幕上提示不同的内容。

也可以通过服务器重定位,推送一个提示注册成功的页面。

 

 

第③步修改:

reg.jade

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. extends layout  
  2. block content  
  3.     script(type="text/javascript",src='javascripts/reg.js')  
  4.     div.form-horizontal  
  5.         fieldset  
  6.             legend  注册  
  7.             div.control-group  
  8.                 label.control-label(for="username") 用户名  
  9.                 div.controls  
  10.                     input.input-xlarge#username(type="text",name="username")  
  11.                     p.help-block#tips    你的账户名称,用于登录和提示  
  12.                     p.help-block.displayNONE#usrname-error  用户名不能为空  
  13.                     p.help-block.displayNONE#error-alert  
  14.   
  15.             div.control-group  
  16.                 label.control-label(for="password") 口令  
  17.                 div.controls  
  18.                     input.input-xlarge#password(type="password",name="password")  
  19.                     p.help-block.displayNONE#pw-error     密码不能为空  
  20.   
  21.             div.control-group  
  22.                 label.control-label(for="password-repeat")  重复输入口令  
  23.                 div.controls  
  24.                     input.input-xlarge#password-repeat(type="password",name="password-repeat")  
  25.                     p.help-block.displayNONE#pw-rp-error     两次密码需要一致  
  26.   
  27.             div.form-actions  
  28.                 button.btn.btn-priamry#submit-button   注册  

 

在这里,取消掉原生的表单提交功能,更改为用ajax发起请求。

 

 

注册页面的逻辑单独写在一个js里面,因此在public/javascripts下创建文件:

reg.js

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. $(document).ready(function () {  
  2.     var time = 0;   //用于计时  
  3.     $("#submit-button").click(function () {  
  4.         //获取账号密码  
  5.         var name = $("#username").val();  
  6.         var pw = $("#password").val();  
  7.         var pwRepeat = $("#password-repeat").val();  
  8.   
  9.         //未输入账号名  
  10.         if (name.length === 0) {  
  11.             $("#username").parent().parent().addClass("error");  
  12.             $("#usrname-error").removeClass("displayNONE");  
  13.             $("#tips").addClass("displayNONE");  
  14.             setTimeout(function () {  
  15.                 $("#username").parent().parent().removeClass("error");  
  16.                 $("#usrname-error").addClass("displayNONE");  
  17.                 $("#tips").removeClass("displayNONE");  
  18.             }, 2000);  
  19.             return;  
  20.         }  
  21.   
  22.         //密码为空  
  23.         if (pw.length === 0) {  
  24.             $("#password").parent().parent().addClass("error");  
  25.             $("#pw-error").removeClass("displayNONE");  
  26.             setTimeout(function () {  
  27.                 $("#password").parent().parent().removeClass("error");  
  28.                 $("#pw-error").addClass("displayNONE");  
  29.             }, 2000);  
  30.             return;  
  31.         }  
  32.   
  33.         //密码不相同  
  34.         if (pw !== pwRepeat) {  
  35.             $("#pw-rp-error").parent().parent().addClass("error");  
  36.             $("#pw-rp-error").removeClass("displayNONE");  
  37.             setTimeout(function () {  
  38.                 $("#pw-rp-error").parent().parent().removeClass("error");  
  39.                 $("#pw-rp-error").addClass("displayNONE");  
  40.             }, 2000);  
  41.             return;  
  42.         }  
  43.   
  44.         var obj = {  
  45.             name: name,  
  46.             password: pw  
  47.         };  
  48.   
  49.         //防止连续点击,先禁用提交按钮,防止重复提交  
  50.         if (new Date() - time < 3000) {  
  51.             return;  
  52.         }  
  53.         //发起请求前,更新提交时间,  
  54.         time = new Date();  
  55.         //发起请求,回调内容是一个对象,注册成功会有success,注册失败会有error属性  
  56.         $.post("/reg", obj, function (data) {  
  57.             if ("error" in data) {  
  58.                 $("#error-alert").removeClass("displayNONE")  
  59.                 $("#error-alert").text(data.error);  
  60.                 $("#error-alert").parent().parent().addClass("error");  
  61.                 setTimeout(function () {  
  62.                     $("#error-alert").addClass("displayNONE")  
  63.                     $("#error-alert").parent().parent().removeClass("error");  
  64.                 }, 2000)  
  65.                 time = 0;       //注册失败,清空time计时,允许再次提交  
  66.             } else if ("success" in data) {  
  67.                 location.href = data.success;    //注册成功则重定向  
  68.             }  
  69.         })  
  70.     })  
  71. })  

 

 

 

 

 

 

 

 

 

 

这里的验证逻辑很简单:

①账号不能为空;

②密码不能为空;

③两次密码应该相同;

为了简化,目前只设置这么多,不考虑例如最短密码长度、最长密码长度、密码需要由字母、数字、特殊符号混合的情况

 

打开blog.css,添加这样一个样式,用于隐藏某些暂时不需要显示的内容:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. .displayNONE {  
  2.     display: none;  
  3. }  

 

 

 

【15】和MySQL交互

这里采用连接池的模式和MySQL交互;

 

①毫无疑问,我们首先应该安装MySQL模块;

进入项目的根目录,输入

npm install mysql

 

等待出现以下画面就表示装好了:

 

 

 

 

 

②在项目的根目录下面创建文件夹models,这个文件夹用于保存各种类模型

并在其中新建db.js

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. ar mysql = require("mysql");   //调用nodejs和mysql交互的模块;  
  2. var pool = mysql.createPool({   //创建连接池  
  3.     host: '127.0.0.1',  //表示本地的数据库  
  4.     user: 'root',       //账号  
  5.     password: '123456'//密码  
  6.     port: '3306',       //默认端口  
  7.     database: 'blog'    //库名  
  8. })  
  9. var db = {};  
  10. db.con = function (callback) {   //callback是回调函数,连接建立后的connection作为其参数  
  11.     pool.getConnection(function (err, connection) { //创建一个链接  
  12.         if (err) {      //对异常进行处理  
  13.             throw err;  //抛出异常  
  14.         } else {  
  15.             callback(connection);   //如果正常的话,执行回调函数(即请求),具体请求在回调函数中写  
  16.         }  
  17.         connection.release();   //释放连接  
  18.     })  
  19. }  
  20. module.exports = db;  

 

这段代码做了这些事

 

 

 

 

我们该如何使用呢,很简单。

①生成一个db的实例;

②调用db的con方法,将请求写在回调函数之中,请求对象是回调函数的参数;

③例如:

我们打开routes/reg.js,将代码修改为如下样子:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); //调用express模块  
  2. var router = express.Router();  //调用模块的Router方法  
  3. var db = require('../models/db');  
  4.   
  5.   
  6. router.get('/'function (req, res, next) {  
  7.     res.render('reg')  
  8. });  
  9.   
  10. router.post('/'function (req, res, next) {    //当路由捕捉到url为/reg的post请求时,会执行以下函数  
  11.     db.con(function (connect) {     //db是我们导入的对象,connect是数据库的连接,此时数据库连接无错误,是一个正常的连接  
  12.         //这是我们自定义的回调函数  
  13.         connect.query("SELECT 1 + 1 AS solution"function (err, result) {  //这里是一个对数据库的请求  
  14.             if (err) {  //假如请求出错,抛出异常  
  15.                 throw err;  
  16.                 return;  
  17.             }  
  18.             console.log(result);    //控制台输出结果  
  19.             res.send({  //将结果作为对象的error属性发送给客户端  
  20.                 error: result  
  21.             });  
  22.         }) //执行完回调函数后,会跳回db的con方法,执行接下来的属性(即释放连接)  
  23.     })  
  24. })  
  25. module.exports = router;  

 

这些代码做了一件事,在用户发起reg请求时,将查询结果(返回);

还记不记得我们在注册页面的js回调函数,假如返回值有error属性,那么他会将error属性显示出来;

 

当然,由于并不能显示对象(error属性是一个对象),因此显示出来的是这样的

 

这只是一个测试用的代码,但通过这样的方法,说明我们的前后端交互是走通了的

 

 

【16】封装涉及到user的代码

初步考虑,我们现在要注册,因此应该写一个向MySQL插入项的方法;

 

那是否我们就应该直接将这部分代码直接写在reg.js里面呢?

 

显然是不好的,这种做法违背了OOP的思想——封装;

 

简单来说,我们应该将用户视为一个类:

①至少包含两个方法:注册(reg)、查询(login);

②至少包含两个属性:用户名(username)、密码(password)。

 

因此,新建user.js,放置于models文件夹内。

内容如下:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var db = require('./db')  
  2. function User(user) {       // 这是一个User类,传递的参数是一个对象,这个对象可以具有两个属性,分别是name和password  
  3.     this.name = user.name;  // 如果传递的user不是空,那么将其赋值给User类的实例的name属性  
  4.     this.password = user.password;  // 同上,赋给password属性  
  5. }  
  6.   
  7. // 这个是插入方法  
  8. User.prototype.save = function (callback) {  
  9.     var self = this  
  10.     if (this.name.length == 0 || this.password.length == 0) {    //如果在没账号/密码的情况下就调用插入方法,则提示错误并返回  
  11.         console.log("You can't save user information without NAME or PASSWORD!");  
  12.         return callback("You can't save user information without NAME or PASSWORD!");  
  13.     }  
  14.     db.con(function (connect) {  
  15.         // 数据库的表名为user,字段名为name和password  
  16.         connect.query("INSERT INTO user(name,password) VALUES (?,?)", [self.name, self.password], function (err, result) {  
  17.             // 上面的两个问号,表示第二个参数的self.name和self.password依次被替换到问号的位置;  
  18.             // 需要注意的是:  
  19.             // ①必须以数组形式依次排列;  
  20.             // ②必须是字符串形式(不能是number)  
  21.             if (err) {  //如果出错,那么错误信息作为回调函数的参数返回  
  22.                 console.log("INSERT name:" + self.name + ", password:" + self.password + " error, the err information is " + err);  
  23.                 return callback(err);  
  24.             }  
  25.             callback(null, result); //如果正常执行,那么第一个参数为null(无错误),第二个参数为返回的结果  
  26.         })  
  27.     })  
  28. }  
  29. // 这个是查询方法  
  30. User.prototype.get = function (callback) {  
  31.     var self = this;  
  32.     if (this.name.length == 0) {    //如果在没账号/密码的情况下就调用插入方法,则提示错误并返回  
  33.         console.log("You can't select user information without NAME!");  
  34.         return callback("You can't select user information without NAME!");  
  35.     }  
  36.     var selectResult;  
  37.     db.con(function (connect) {  
  38.         connect.query('SELECT * FROM user WHERE name = ?', [self.name], function (err, result) {  
  39.             if (err) {  //报错  
  40.                 console.log("select name:" + self.name + " error, the err information is " + err);  
  41.                 return callback(err);  
  42.             }  
  43.             //注意,这里返回的是带账号和密码的,另外,理论上是有可能有多个元素的,但由于在注册时,用户名限制了重复,因此只会返回一个  
  44.             selectResult = result;  //这里的result是一个数组,只包含一个元素(或者是空)  
  45.             if (selectResult.length) {  //查询到的话,数组是有元素的(即length > 0)  
  46.                 return callback(null, selectResult[0]) //这里的selectResult就是user对象,包含name和password属性  
  47.             } else {  
  48.                 return callback(nullnull);    //如果查询不到,两个参数都为空  
  49.             }  
  50.         })  
  51.     })  
  52. }  
  53.   
  54. module.exports = User;  

 

 

 

这个封装好的User类满足了我们的需求,下来需要在reg里面调用这个类。

 

下面是修改后的routes/reg.js的代码

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); // 调用express模块  
  2. var router = express.Router();  // 调用模块的Router方法  
  3. var User = require('../models/user');   // 调用刚才封装好的user类  
  4. var crypto = require("crypto"); // 这个是加密用,nodejs本身提供  
  5.   
  6.   
  7. router.get('/'function (req, res, next) {  
  8.     res.render('reg')  
  9. });  
  10.   
  11. router.post('/'function (req, res, next) {    //当路由捕捉到url为/reg的post请求时,会执行以下函数  
  12.     // 获取md5这个对象;(表示调用这个对象的方法,加密的形式是MD5);  
  13.     var md5 = crypto.createHash('md5');  
  14.     // 调用md5的update方法,update的第一个参数是被处理的内容,第二个是可选的,但如果要对中文进行处理,那么就需要加上'utf-8'这个参数;  
  15.     // 因此,我们的密码其实可以支持中文(理论上)  
  16.     // digest指的是以什么形式进行编码,这里选择了base64,除此之外,还有hex(十六进制)、binary(二进制)这两种方法;  
  17.     var password = md5.update(req.body.password, 'utf-8').digest('base64');  
  18.   
  19.     var newUser = new User({   //生成一个User的实例,并赋给他name和passowrd属性  
  20.         name: req.body.name,  
  21.         password: password  //这里的password是加密过的(存储在数据库里也是加密过后的形式)  
  22.     })  
  23.     newUser.save(function (err, result) {  
  24.         //do something  
  25.     })  
  26.   
  27. })  
  28. module.exports = router;  

 

这部分并没有完成,之所以这样,是因为还欠缺一个东西,那就是登录状态的设置。

 

 

【17】登录状态和session

如何确定一个人是否在登录中?

有两个办法:①cookie;②session;

 

简单来说,他们之间的区别在于:

cookie保存在客户端,session保存在服务器端;

 

顺便补一个Cookie、LocalStorage和SessionStorage之间区别的链接:

http://jerryzou.com/posts/cookie-and-web-storage/

注意,这里的SessionStorage和session并不是同一个东西,前者保存在浏览器端,后者保存在服务器端。

 

再补一个express使用cookie和session的文章:

http://wiki.jikexueyuan.com/project/node-lessons/cookie-session.html

 

 

由于cookie可以在本地被修改,因此用cookie来保存用户状态是不安全的。

这里采用session来保存用户状态。

 

在高版本的express里(4.x及以上),cookie和session等模块并不直接包含在express里了,因此我们要安装express-session。

在项目的根目录输入:

npm install express-session

 

安装好后如图:

 

打开app.js,在

 

var app = express();

后添加如下代码:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. //session  
  2. var session = require('express-session');  
  3. app.use(session({  
  4.     secret: 'recommend 128 bytes random string',  
  5.     cookie: {maxAge: 3600 * 1000}  
  6. }))  

 

 

 

这种设置表示:

①默认session存储在内存中(默认设置,效果是重启后session失效);

②最大有效时间(过期时间)是3600秒;

 

然后重新打开reg.js,在原先的空白处,添加以下代码:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. if (user) { //如果第二个参数存在,说明用户名重复了,返回提示  
  2.     return res.send({  
  3.         error: "Username already exists."  
  4.     });  
  5. }  
  6. if (err) {  //如果报错,返回报错信息  
  7.     console.log(err);  
  8.     return res.send({  
  9.         error: err  
  10.     });  
  11. }  
  12. //此时说明无重复无报错,可以将用户信息存入到数据库之中  
  13. newUser.save(function (err, result) {  
  14.     if (err) {  //如果存入时报错  
  15.         return res.send({  
  16.             error: err  
  17.         });  
  18.     }  
  19.     //以上,done  
  20.     //存储成功后返回还有点问题,需要搞明白  
  21.     req.session.user = {  
  22.         name: this.name,  
  23.         password: this.password  
  24.     };  
  25.     req.session.success = "注册成功!";  //添加session的注册成功信息  
  26.     res.send({  //发送一个对象,属性为success,值为/  
  27.         success: "/"  
  28.     })  
  29.     return;  
  30. })  

但此时还没结束,我们需要对数据库进行操作:(建议用数据库软件完成)

先建立一个库,库名为blog

在库中建立一个表,表名为user;

表中有三个字段:

①第一个为id,类型为int,自动增量,不允许为空;

②第二个为name,类型为varchar,长度为255,字符集为utf8,不允许为空;

③第三个为password,类型为varchar,长度为255,字符集为utf8,不允许为空;

 

于是,注册流程已经走通,用户可以顺利注册了

账号和密码用软件创建时设置如图:

 

 

 

 

【18】设置登录状态

在之前,已经可以通过session保存用户了,下来,需要根据session的记录,区分用户登录和未登录的状态。

 

简单来说,有两种情况:

①session中的user属性为空,用户未登录;

②session中的user属性不为空,用户已登录,登录人士为user的name属性;

 

为了简化,这里并没有对同时登录的检测(避免一个账号在两个地方登录)。

 

 

而在访问页面时,应该检测登录状态:

①假如已经登录,那么就不应该显示登录窗口、注册窗口,以及发表微博相关的内容;

②假如还没有登录,那么就不应该显示登出的按钮;

 

为了完成这个目的,那么应该在引擎渲染时进行检查,最笨的做法是,将变量设置于res.render的第二个参数的属性之中:

打开layout.jade,将代码如下修改:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. doctype html  
  2. html  
  3.     head  
  4.         title MyBlog By qq20004604  
  5.         link(rel='stylesheet', href='./stylesheets/bootstrap.min.css')  
  6.         link(rel='stylesheet', href='./stylesheets/bootstrap-responsive.min.css')  
  7.         link(rel='stylesheet', href='./stylesheets/blog.css')  
  8.         script(type="text/javascript",src='javascripts/jq.js')  
  9.         script(type="text/javascript",src='javascripts/bootstrap.min.js')  
  10.         script(type="text/javascript",src='javascripts/blog.js')  
  11.     body  
  12.         div.navbar.navbar-fixed-top  
  13.             div.navbar-inner  
  14.                 div.container  
  15.                     a.btn.btn-navbar(data-toggle="collapse",data-target=".nav-collapse")  
  16.                         span.icon-bar  
  17.                         span.icon-bar  
  18.                         span.icon-bar  
  19.                     a.brand(href="/") MyBlog  
  20.                     div.nav-collapse  
  21.                         ul.nav  
  22.                             li  
  23.                                 a(href="/") 首页  
  24.                             //以下是修改后的部分  
  25.                             if(user())  
  26.                                 li  
  27.                                     a(href="/logout") 登出  
  28.                             else  
  29.                                 li  
  30.                                     a(href="/login") 登入  
  31.                                 li  
  32.                                     a(href="/reg") 注册  
  33.                             li  
  34.                                 a(href="/language") 切换语言  
  35.         div#contrainer.container  
  36.             block content  
  37.             hr  
  38.         fotter  
  39.             p   作者:王冬   QQ:20004604  

 

 

打开index.jade

 

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. extends layout  
  2. block content  
  3.     div.hero-unit  
  4.         h1  我的博客  
  5.         p   这个是基于Nodejs作为后台,jade作为模板来,使用了Express作为框架  
  6.             br  
  7.             br  
  8.             //修改以下部分  
  9.             if(!user())  
  10.                 br  
  11.                 br  
  12.                 a.btn.btn-primary.btn-large(href="/login")  登录  
  13.                 a.btn.btn-large(href="/reg")    立即注册  
  14.             else  
  15.                 textarea#textarea.uneditable-input  
  16.                 button#postBlog.btn.btn-large 提交微博  
  17.                 button#clearBlog.btn.btn-large 清空  
  18.             div#submitError.alert.alert-error.displayNONE.myalert  
  19.             div#submitSuccess.alert.alert-success.displayNONE  
  20.   
  21.     div.row.content  
  22.         div.span4  
  23.             h2  烟雨江南说  
  24.             p   当欲望没有了枷锁,就没有了向前的路  
  25.             p   只有转左,或者向右  
  26.             p   左边是地狱,右边也是地狱  
  27.         div.span4  
  28.             h2  烟雨江南说  
  29.             p   那些都是极好极好的  
  30.             p   可是我偏偏不喜欢  
  31.         div.span4  
  32.             h2  烟雨江南说  
  33.             p   我不怕傻  
  34.             p   只怕  
  35.             p   遇不到  
  36.             p   可以让我变傻的人  
  37.         div.span4  
  38.             h2  烟雨江南说  
  39.             p   人在年轻的时候总会有些莫名的坚持,  
  40.             p   并且以此感动着自己,  
  41.             p   却时常会在不经意间让真正重要的东西从指间流走。  
  42.         div.span4  
  43.             h2  烟雨江南说  
  44.             p   记忆真是一种奇怪的东西,  
  45.             p   有时候会涤荡所有的苦难,只留下温情,  
  46.             p   有时候却磨灭掉曾有的欢乐,唯剩下苍白和丑陋。  
  47.         div.span4  
  48.             h2  烟雨江南说  
  49.             p   那存在的,都是幻影。  
  50.             p   那永恒的,终将毁灭。  
  51.             p   世界万物,缤纷色彩,都是被蒙蔽的人心罢了。  
  52.         div.span4  
  53.             h2  烟雨江南说  
  54.             p   诸神以真相示人,而世人却视而不见  
  55.         div.span4  
  56.             h2  烟雨江南说  
  57.             p   只有绵羊会向狮子要求平等,  
  58.             p   而狮子们从来不会这样想。  
  59.         div.span4  
  60.             h2  烟雨江南说  
  61.             p   愿迷途的旅人,从此得享安息。  
  62.             p   因理想而不朽,因归返而救赎。  
  63.     div#scrollToFoot 滚动到底部然后加载内容  

 

打开routes/index.js,将代码如下修改:

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); //调用express  
  2. var router = express.Router();  //生成express的Router方法的一个实例  
  3.   
  4. //处理函数  
  5. router.get('/'function (req, res, next) {  //捕获根url  
  6.     res.render('index', {  
  7.         user: function () { //user将读取session的属性,然后给予不同的返回值  
  8.             if (req.session.user)  
  9.                 return req.session.user;  
  10.             else  
  11.                 return null;  
  12.         }  
  13.     });  
  14.     //res.render方法渲染一个引擎模板,  
  15.     //第二个参数是一个对象,对象里的变量可以在引擎中使用,  
  16.     //第三个参数是回调函数,提供两个参数,分别是err和html,err是错误,html是渲染后的页面。如果使用这个回调函数,那么将不会自动响应,即要用户自己写返回html的命令  
  17. });  
  18.   
  19. module.exports = router;  

 

 

 

 

代码解释:

①在之前的代码中,我们如果注册成功,那么会将user属性设置在session之中;

②而在index.js里,会传递一个变量,那就是user;

③这个user会读取session的user属性,由于session默认的属性是没有user的,因此返回值为null(空),而注册成功后,返回值则为user(不为空);

④在layout.jade中,会执行这个user函数。假如未登录,user为空,那么执行else语句(显示登入和注册标签);否则显示登出标签;

⑤在index.jade中,同样执行,假如未登录,显示登录、注册按钮;假如已经登录了,显示输入微博的窗口和提交按钮;

 

于是,登录后的页面是这样的:

 

 

 

 

但是这种行为个人觉得比较傻,并且也不利于做多语言扩展,因此换一种方式。

 

 

 

【19】动态视图助手和多语言支持

关于动态视图助手的说明请看我之前的博客:

http://blog.csdn.net/qq20004604/article/details/51934991

 

简单来说,使用动态视图助手时,我们可以在引擎渲染的时候,直接调用视图助手中的变量;

 

首先在models下面建立language.js文件,内容如下

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. function internation() {  
  2.   
  3. }  
  4.   
  5. internation.prototype.languagePacks = {  
  6.     zh_cn: {  
  7.         index: {  
  8.             title: "我的博客",  
  9.             description: "这个是基于Nodejs作为后台,jade作为模板来,使用了Express作为框架",  
  10.             login: "登录",  
  11.             regNow: "立即注册",  
  12.             submit: "提交微博",  
  13.             emptyInput: "清空"  
  14.         },  
  15.         layout: {  
  16.             title: "MyBlog By qq20004604",  
  17.             topTitle: "MyBlog",  
  18.             indexButton: "首页",  
  19.             logout: "登出",  
  20.             login: "登入",  
  21.             reg: "注册",  
  22.             language: "切换语言",  
  23.             foot: "作者:王冬   QQ:20004604"  
  24.         }  
  25.     },  
  26.     eng: {  
  27.         index: {  
  28.             title: "My blog",  
  29.             description: "I use NodeJS, Express, and MySQL to design this site.",  
  30.             login: "Login",  
  31.             regNow: "Reg",  
  32.             submit: "Submit",  
  33.             emptyInput: "Clear"  
  34.         },  
  35.         layout: {  
  36.             title: "MyBlog By qq20004604",  
  37.             topTitle: "MyBlog",  
  38.             indexButton: "Index",  
  39.             logout: "Logout",  
  40.             login: "Login",  
  41.             reg: "Reg",  
  42.             language: "Language",  
  43.             foot: "I'm WangDong, you can contact me with QQ:20004604"  
  44.         }  
  45.     }  
  46. }  
  47.   
  48. //如果有语言设置,则设置为对应的语言,否则,设置为中文;  
  49. internation.prototype.getPacks = function (setting) {  
  50.     if (!setting) {  
  51.         return this.languagePacks["zh_cn"]  
  52.     }  
  53.     if (setting in this.languagePacks) {  
  54.         return this.languagePacks[setting];  
  55.     } else {  
  56.         return this.languagePacks["zh_cn"]  
  57.     }  
  58. }  
  59.   
  60. internation.prototype.set = function (app) {  
  61.     var self = this;  
  62.     app.use(function (req, res, next) {  
  63.         //这个是示例,定义一个动态视图助手变量  
  64.   
  65.         //如果有语言设置,则设置为对应的语言,否则,设置为中文;  
  66.         res.locals = self.getPacks(req.session.language)  
  67.   
  68.         //下面两个因为要调用req和res,所以特殊设置  
  69.         res.locals.user = function () {  
  70.             if ('session' in req && 'user' in req.session)  
  71.                 return req.session.user;  
  72.             else  
  73.                 return null;  
  74.         };  
  75.   
  76.         //这里不能用error这个变量名  
  77.         res.locals.err = function () {  
  78.             //如果session不存在,或者session中没有err属性  
  79.             if (!'session' in req || !'err' in req.session)  
  80.                 return null;    //返回空  
  81.             //否则返回err,并将session中的err重置为空(因此只返回一次)  
  82.             var err = req.session.err;  
  83.             req.session.err = null;  
  84.             return err;  
  85.         };  
  86.   
  87.         //注册成功的提示  
  88.         res.locals.success = function () {  
  89.             //原理同err  
  90.             if (!'session' in req || !'success' in req.session)  
  91.                 return null;  
  92.             var success = req.session.success;  
  93.             req.session.success = null;  
  94.             return success;  
  95.         }  
  96.         next();  
  97.     })  
  98. }  
  99.   
  100.   
  101. module.exports = internation;  

 

 

 

在以上代码中,程序做了以下事情:

①创建一个语言包,他包含中文和英文两种;

②设置了一个方法,他可以根据session的language属性来决定获取哪个语言包;

 

但也有一定缺陷:

①假如在zh_cn的index中有一条test属性,他有一个对应的字符串;但在eng中相应的位置不存在,那么切换为eng时,这里就会显示错误;

②调用的时候,需要通过例如index.title这样的形式来调用

 

 

然后我们将这个模块加入到我们的代码之中;

打开app.js,在之前添加的关于session的代码后面,添加如下代码:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var language = require('./models/language');    //多语言  
  2. var internation = new language();  
  3. internation.set(app);  

 

修改layout.jade

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. octype html  
  2. html  
  3.     head  
  4.         title #{layout.title}  
  5.         link(rel='stylesheet', href='./stylesheets/bootstrap.min.css')  
  6.         link(rel='stylesheet', href='./stylesheets/bootstrap-responsive.min.css')  
  7.         link(rel='stylesheet', href='./stylesheets/blog.css')  
  8.         script(type="text/javascript",src='javascripts/jq.js')  
  9.         script(type="text/javascript",src='javascripts/bootstrap.min.js')  
  10.         script(type="text/javascript",src='javascripts/blog.js')  
  11.     body  
  12.         div.navbar.navbar-fixed-top  
  13.             div.navbar-inner  
  14.                 div.container  
  15.                     a.btn.btn-navbar(data-toggle="collapse",data-target=".nav-collapse")  
  16.                         span.icon-bar  
  17.                         span.icon-bar  
  18.                         span.icon-bar  
  19.                     a.brand(href="/") #{layout.topTitle}  
  20.                     div.nav-collapse  
  21.                         ul.nav  
  22.                             li  
  23.                                 a(href="/") #{layout.indexButton}  
  24.                             //以下是修改后的部分  
  25.                             if(user())  
  26.                                 li  
  27.                                     a(href="/logout") #{layout.logout}  
  28.                             else  
  29.                                 li  
  30.                                     a(href="/login") #{layout.login}  
  31.                                 li  
  32.                                     a(href="/reg") #{layout.reg}  
  33.                             li  
  34.                                 a(href="/language") #{layout.language}  
  35.         div#contrainer.container  
  36.             block content  
  37.             hr  
  38.         fotter  
  39.             p   #{layout.foot}  

 

 

 

重新访问页面,我们会发现页面跟以前并没有什么区别,说明我们的语言包已经正常运行了。

 

 

 

【20】添加语言切换功能;

根据我们之前的代码,语言切换很简单,只需要在session添加属性即可;

 

首先,设置路由,routes文件夹下新建language.js

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); //调用express  
  2. var router = express.Router();  //生成express的Router方法的一个实例  
  3.   
  4. //处理函数  
  5. router.get('/'function (req, res, next) {  //捕获根url  
  6.     if (!('language' in req.session) || req.session.language === 'zh_cn')  
  7.         req.session.language = 'eng';   //更改属性  
  8.     else  
  9.         req.session.language = 'zh_cn';  
  10.     res.redirect('/');  //重定向  
  11. });  
  12.   
  13. module.exports = router;  

 

在app.js的路由代码附近添加如下代码:

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var lan = require('./routes/language');  
  2. app.use('/language', lan);  //切换语言的  

 

重启程序,然后进入首页点击切换语言,页面顶端变为如下状态:

 

 

此时如果再次点击‘Language’,那么页面又会切回中文;

 

我们顺便修改layout.jade文件为:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. doctype html  
  2. html  
  3.     head  
  4.         title #{layout.title}  
  5.         link(rel='stylesheet', href='./stylesheets/bootstrap.min.css')  
  6.         link(rel='stylesheet', href='./stylesheets/bootstrap-responsive.min.css')  
  7.         link(rel='stylesheet', href='./stylesheets/blog.css')  
  8.         script(type="text/javascript",src='javascripts/jq.js')  
  9.         script(type="text/javascript",src='javascripts/bootstrap.min.js')  
  10.         script(type="text/javascript",src='javascripts/blog.js')  
  11.     body  
  12.         div.navbar.navbar-fixed-top  
  13.             div.navbar-inner  
  14.                 div.container  
  15.                     a.btn.btn-navbar(data-toggle="collapse",data-target=".nav-collapse")  
  16.                         span.icon-bar  
  17.                         span.icon-bar  
  18.                         span.icon-bar  
  19.                     a.brand(href="/") #{layout.topTitle}  
  20.                     div.nav-collapse  
  21.                         ul.nav  
  22.                             li  
  23.                                 a(href="/") #{layout.indexButton}  
  24.                             //以下是修改后的部分  
  25.                             if(user())  
  26.                                 li  
  27.                                     a(href="/logout") #{layout.logout}  
  28.                             else  
  29.                                 li  
  30.                                     a(href="/login") #{layout.login}  
  31.                                 li  
  32.                                     a(href="/reg") #{layout.reg}  
  33.                             li  
  34.                                 a(href="/language") #{layout.language}  
  35.         div#contrainer.container  
  36.             block content  
  37.             hr  
  38.         fotter  
  39.             p   #{layout.foot}  

 

再修改routes/index.js为:

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); //调用express  
  2. var router = express.Router();  //生成express的Router方法的一个实例  
  3.   
  4. //处理函数  
  5. router.get('/'function (req, res, next) {  //捕获根url  
  6.     res.render('index');  
  7. });  
  8.   
  9. module.exports = router;  

 

 

 

 

 

 

 

【21】登录功能的完成

与注册(reg.js)功能的代码类似,

首先在models的语言包中分别添加如下属性:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. message: {  
  2.     reg_success: "注册成功!",  
  3.     reg_fail: "注册失败!",  
  4.     login_success: "登录成功!",  
  5.     login_fail: "登录失败",  
  6.     errorName: "用户名不存在",  
  7.     errorPW: "密码错误!"  
  8. }  


 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. message: {  
  2.     reg_success: "Reg Success!",  
  3.     reg_fail: "Reg Failed!",  
  4.     login_success: "Login Success!",  
  5.     login_fail: "Login Failed!",  
  6.     errorName: "The username is not exists.",  
  7.     errorPW: "Error Password!"  
  8. }  

 

 

 

 

打开routes/login.js文件

 

将代码修改如下:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); //调用express模块  
  2. var router = express.Router();  //调用模块的Router方法  
  3. var User = require('../models/user');   // 调用刚才封装好的user类  
  4. var crypto = require("crypto"); // 这个是加密用,nodejs本身提供  
  5.   
  6.   
  7. router.get('/'function (req, res, next) {  
  8.     res.render('login')  
  9. });  
  10.   
  11. router.post('/'function (req, res, next) {  
  12.     var md5 = crypto.createHash('md5');  
  13.     var password = md5.update(req.body.password, 'utf-8').digest('base64');  
  14.   
  15.     var newUser = new User({   //生成一个User的实例,并赋给他name和passowrd属性  
  16.         name: req.body.name  
  17.     })  
  18.     newUser.get(function (err, user) {  
  19.         if (!user) {    //用户名不存在  
  20.             return res.send({errorName: res.locals.message.errorName});  
  21.         }  
  22.         else if (user) { //如果第二个参数存在,说明用户名重复了,那么监测密码是否相同  
  23.             if (user.password === password) {   //密码正确,登录成功  
  24.                 req.session.user = user;  
  25.                 req.session.success = res.locals.message.login_success;  
  26.                 return res.send({success: '/'});  
  27.             } else {  
  28.                 return res.send({errorPW: res.locals.message.errorPW});  
  29.             }  
  30.         }  
  31.         else if (err) {  //如果报错,返回报错信息  
  32.             console.log(err);  
  33.             return res.send({  
  34.                 error: err  
  35.             });  
  36.         }  
  37.   
  38.     })  
  39. })  
  40. module.exports = router;  

 

修改login.jade

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. xtends layout  
  2. block content  
  3.     script(type="text/javascript",src='javascripts/login.js')  
  4.     div.form-horizontal  
  5.         fieldset  
  6.             legend  登录  
  7.             div.control-group  
  8.                 label.control-label(for='username') 用户名  
  9.                 div.controls  
  10.                     input.input-xlarge#username(name='username',type='text')  
  11.                     p.help-block.displayNONE#name-alert  
  12.             div.control-group  
  13.                 label.control-label(for='password') 密码  
  14.                 div.controls  
  15.                     input.input-xlarge#password(name='password',type='password')  
  16.                     p.help-block.displayNONE#pw-alert  
  17.             div.form-actions  
  18.                 button.btn.btn-primary#submit 登录  

 

 

 

在public/javascripts下新建login.js,内容如下:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. $(document).ready(function () {  
  2.     var time = 0;  
  3.     $("#submit").click(function () {  
  4.         var user = {  
  5.             name: $("#username").val(),  
  6.             password: $("#password").val()  
  7.         }  
  8.   
  9.         //禁止用户名、密码为空  
  10.         if (user.name.length === 0) {  
  11.             $("#name-alert").text("用户名不能为空");  
  12.             return;  
  13.         } else if (user.password.length === 0) {  
  14.             $("#pw-alert").text("密码不能为空");  
  15.             return;  
  16.         }  
  17.   
  18.   
  19.         if (new Date() - time < 3000)  
  20.             return  
  21.         time = new Date();  
  22.         //发起ajax请求  
  23.         $.post("login", user, function (data) {  
  24.             if ("errorName" in data) {  
  25.                 $("#name-alert").removeClass("displayNONE")  
  26.                 $("#name-alert").text(data.errorName);  
  27.                 $("#name-alert").parent().parent().addClass("error");  
  28.                 setTimeout(function () {  
  29.                     $("#name-alert").addClass("displayNONE")  
  30.                     $("#name-alert").parent().parent().removeClass("error");  
  31.                 }, 2000)  
  32.                 time = 0;  
  33.             } else if ("errorPW" in data) {    //  
  34.                 $("#pw-alert").removeClass("displayNONE")  
  35.                 $("#pw-alert").text(data.errorPW);  
  36.                 $("#pw-alert").parent().parent().addClass("error");  
  37.                 setTimeout(function () {  
  38.                     $("#pw-alert").addClass("displayNONE")  
  39.                     $("#pw-alert").parent().parent().removeClass("error");  
  40.                 }, 2000)  
  41.                 time = 0;  
  42.             } else if ("success" in data) {  
  43.                 location.href = data.success;    //注册成功则重定向  
  44.             }  
  45.         })  
  46.     })  
  47. })  

 

 

 

于是,登录相关的内容算是做完了(但没有做多语言);

 

 

 

【22】登出功能的完成

相比较登录,登出功能就要简单的多,只需要将session的user属性重置为null即可。

 

在app.js的路由代码附近添加以下代码:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var logout = require('./routes/logout');  
  2. app.use('/logout', logout);  //登出  

 

在routes文件夹下新建logout.js,代码非常简单:

 

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); //调用express模块  
  2. var router = express.Router();  //调用模块的Router方法  
  3.   
  4. //登出只需要清空session的user属性即可  
  5. router.get('/'function (req, res, next) {  
  6.     req.session.user = null;  
  7.     res.redirect('/');  
  8. })  
  9. module.exports = router;  

 

 

 

 

【23】清点需求

重新打开我们之前的需求表:

①有首页;

②支持注册;

③支持登录、登出;

④可以发表博客,发表的博客可以保存到数据库;

⑤可以查看博客,博客从数据库中读取,为了简化,这里设置为查看所有人的博客;

⑥查看博客时,初始查看的数量有限,但可以无限加载新的博客,直到查看完全部博客;

⑦一定程度上实现多语言(即可以切换显示的语言版本),但由于复杂度所限,因此只部分实现(但框架已建立好,可以通过继续完善代码实现整体的国际化);

⑧根据登录状态,对可以访问的页面进行控制(某些允许某些禁止)

 

红色部分是已经完成的,蓝色部分是尚未完成的。

 

下来,我们的任务是完成④⑤⑥这三项。

 

 

 

 

【24】博客的发表

在登录之后,我们会在首页看到一个文本输入框,还有提交和清空按钮;

 

但目前尚没有逻辑,需要进行添加,打开public\javascripts\blog.js,添加如下代码:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. $(document).ready(function () {  
  2.     //根据url,设置高亮  
  3.     if (window.location.pathname === '/') {  
  4.         $(".nav-collapse .nav li:first-child").addClass("active");  
  5.     }  
  6.     else if (window.location.pathname === '/login') {  
  7.         $(".nav-collapse .nav li:nth-child(2)").addClass("active");  
  8.     }  
  9.     else if (window.location.pathname === '/reg') {  
  10.         $(".nav-collapse .nav li:nth-child(3)").addClass("active");  
  11.     }  
  12.   
  13.     //清空输入框  
  14.     $("#clearBlog").click(function () {  
  15.         $("#textarea").val("");  
  16.     })  
  17.     var lastSubmit = null;  
  18.     var timeer = null;  
  19.     var successSubmit = null;  
  20.     //防止连续点击  
  21.     function ErrorAlert() {  
  22.         if (new Date() - lastSubmit < 3000) {  
  23.             clearTimeout(timeer);  
  24.         }  
  25.         lastSubmit = new Date();  
  26.         timeer = setTimeout(function () {  
  27.             $("#submitError").addClass("displayNONE");  
  28.         }, 3000);  
  29.     }  
  30.   
  31.     //提交输入框  
  32.     $("#postBlog").click(function () {  
  33.         //防止重复发表,因此需要间隔10秒  
  34.         if (successSubmit && new Date() - successSubmit < 10000) {  
  35.             //这里是警告提示,防止连续发送消息  
  36.             $("#submitError").text("你发的太快了,喝喝茶吧!距离下次可以发送消息的时间还有:" + (new Date() - successSubmit).toFixed(0) + " 秒。");  
  37.             $("#submitError").removeClass("displayNONE");  
  38.             //防止连续点击  
  39.             ErrorAlert()  
  40.             return;  
  41.         }  
  42.   
  43.         var text = $("#textarea").val();  
  44.         if (text.length === 0) {    //禁止发送空内容  
  45.             $("#submitError").text("请填写输入内容");  
  46.             $("#submitError").removeClass("displayNONE");  
  47.             //防止连续点击  
  48.             ErrorAlert()  
  49.             return;  
  50.         }  
  51.         var length = 0;  
  52.         //获取输入长度,英文字母为1,中文汉字为2  
  53.         for (var i = 0; i < text.length; i++) {  
  54.             if (text[i].match(/[^\x00-\xff]/ig) != null)  
  55.                 length += 2;  
  56.             else  
  57.                 length += 1;  
  58.         }  
  59.         if (length > 255) {  
  60.             $("#submitError").text("字符长度过长,限制字符长度为255个字节,你的文本长度为" + length + "个字节");  
  61.             $("#submitError").removeClass("displayNONE");  
  62.             ErrorAlert()  
  63.             return;  
  64.         }  
  65.   
  66.         //先清除输入框再提交  
  67.         $("#textarea").val("");  
  68.         successSubmit = new Date();  
  69.         $.post('/post', {text: text}, function (item) {  
  70.             if (item.code == 500) {  
  71.                 successSubmit = 0;  
  72.                 $("#submitError").text(item.data);  
  73.                 $("#submitError").removeClass("displayNONE");  
  74.         setTimeout(function () {  
  75.            $("#submitError").addClass("displayNONE");  
  76.         }, 3000);  
  77.   
  78.             } else if (item.code == 200) {  
  79.                 $("#submitSuccess").text(item.data);  
  80.                 $("#submitSuccess").removeClass("displayNONE");  
  81.                 setTimeout(function () {  
  82.                     $("#submitSuccess").addClass("displayNONE");  
  83.                 }, 3000);  
  84.             }  
  85.         })  
  86.     })  
  87.   
  88. })  

 

 

 

这段代码做了以下事:

①根据当前的url,给对应的标签高亮;

②点击清空按钮,会清除输入框的内容;

③点击提交按钮,会验证输入框内字符串的长度,禁止发送空内容和超过255字节长度的内容;

④当成功发出post请求后,禁止在短时间内连续发送post请求;当发送失败后,取消禁止状态,可以立刻再次发送

⑤验证通过后,将消息内容以post请求发送到服务器,并清空输入框;

⑥返回值有两种,一种是code属性为200,一种为500;前者为发送成功,发出提示消息;后者发送失败,提示失败原因。

 

 

接下来首先要在MySQL里创建一张表,用于保存发送的文本内容;

表名为text,字符类型为utf8,表项如下:

 

 

 

id为自动增量;

user为发表这个博客的用户;

text博客的内容;

ctime为博客的创建时间;

 

然后在routes文件夹下新建post.js

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var express = require('express'); //调用express模块  
  2. var router = express.Router();  //调用模块的Router方法  
  3. var db = require('../models/db')  
  4.   
  5. //给原生的Date添加一个方法,传递的参数是格式  
  6. Date.prototype.Format = function (fmt) { //author: meizz  
  7.     var obj = {  
  8.         "M+"this.getMonth() + 1, //月份  
  9.         "d+"this.getDate(), //日  
  10.         "H+"this.getHours(), //小时  
  11.         "m+"this.getMinutes(), //分  
  12.         "s+"this.getSeconds(), //秒  
  13.         "q+": Math.floor((this.getMonth() + 3) / 3), //季度  
  14.         "S"this.getMilliseconds() //毫秒  
  15.     };  
  16.     if (/(y+)/.test(fmt))  
  17.         fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));  
  18.     for (var key in obj)  
  19.         if (new RegExp("(" + key + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (obj[key]) : (("00" + obj[key]).substr(("" + obj[key]).length)));  
  20.   
  21.     console.log(fmt);  
  22.     return fmt; //返回值是格式化好之后的时间  
  23. }  
  24.   
  25. function BlogSubmit(user) {  
  26.     this.user = user.name;  
  27.     this.text = user.text;  
  28.     this.time = new Date().Format("yyyy-MM-dd HH:mm:ss");  
  29. }  
  30.   
  31. //存储文本内容  
  32. BlogSubmit.prototype.save = function (callback) {  
  33.     var self = this;  
  34.     console.log(self.user);  
  35.     db.con(function (connect) {  
  36.         connect.query("INSERT INTO text(user,text,ctime) VALUES (?,?,?)", [self.user, self.text, self.time], function (err, result) {  
  37.             if (err) {  
  38.                 console.log("INSERT text user:" + self.user + ", text:" + self.text + ", ctime: " + self.time + " error, the err information is " + err);  
  39.                 return callback(err);  
  40.             }  
  41.             callback(null, result);  
  42.         })  
  43.     })  
  44. }  
  45.   
  46. router.post('/'function (req, res, next) {  
  47.     //如果不登录是不能发表的(这个是为了防止登录过期,但是页面还保持在登录时的情况)  
  48.     if (!req.session.user) {  
  49.         req.session.err = "请登录";        //因为这里,需要修改layout.jade  
  50.         return res.redirect('/login');  
  51.     }  
  52.     //console.log(req.session.user);  
  53.     //登录后值为(示例)  
  54.     //{ Id: 23,  
  55.     //    username: '1',  
  56.     //    userpassword: 'xMpCOKC5I4INzFCab3WEmw==' }  
  57.     var blog = new BlogSubmit({  
  58.         name: req.session.user.name,  
  59.         text: req.body.text  
  60.     })  
  61.     blog.save(function (err, result) {  
  62.         if (err) {  
  63.             return res.send({  
  64.                 code: 500,  
  65.                 data: "错误描述" + err  
  66.             })  
  67.         }  
  68.         return res.send({  
  69.             code: 200,  
  70.             data: "成功发表!"  
  71.         })  
  72.     })  
  73. })  
  74. module.exports = router;  

 

 

 

这部分代码干了以下几件事:

①给Date()添加了一个方法,用于格式化日期;

②创建了一个BlogSubmit的类,传递的参数是一个对象,他有name属性和text属性,会将其值传递给这个类的实例的user属性和text属性中。同时,会在创建这个类的时候获取当前时间。于是,text表的三项内容有了;

③给BlogSubmit类添加了一个方法,这个方法的作用是将这个类的实例的属性,存储在MySQL之中;

④由于表限制text中必须保存内容,因此假如用户通过其他方法直接发起一个post请求,也是不可能成功的(会报错);

⑤保存的前提是用户已登录,未登录的时候发表(比如登录过期)会提示错误。

⑥无论成功或者失败,都会给用户返回一个对象,成功的code属性为200,失败的为500;

 

 

最后不要忘记在app.js里添加路由:

[javascript] view plain copy  在CODE上查看代码片派生到我的代码片

  1. var postBlog = require('./routes/post');  
  2. app.use('/post', postBlog);  //提交博客  

 

此时发表博客,会提示:


 

 

 

 

查看数据库,数据库对应的表是有内容的,说明成功发表了。

 

 

添加微信交流群(需加微信拉您进入)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值