【nodeJS】从nodejs原生的博客网站搭建到 koa框架实现个人博客网站搭建

nodejs实现搭建博客网站

前言:原java后端渣渣一枚,因项目需要转学了nodejs进行开发,正式进行项目开发之前,师傅安排了一些项目训练,先熟悉js语法,然后熟悉nodejs,再慢慢重构向框架的使用。

    写这一篇文章的目的在于记录自己学习的历程,同时也是给其他的nodejs学习者一点参考,如果有修正之处,请给予意见,感谢各位,希望对你有用~


开发环境

系统:可以的话建议使用Linux系统,推荐Ubuntu,笔者习惯了Windows开发,暂时使用的是win7

开发工具:Visual Studio Code

语言:node 5.6.0

npm 5.6.0(其他的依赖模块靠npm去安装)


语法基础

1.熟悉js的基本语法,把nodejs的req、res以及其他相关的api熟悉起来;

2.mysql的使用,熟悉的sql脚本的编写


项目开发

目标功能(主要功能还是比较简单的几个需求,要求一天内完成)

1.网站显示图片、文字、有简洁的样式

2.网站返回服务器时间

3.网站支持用户留言以及显示当前最新10条留言

4.网站显示当前访问的客户的客户端信息

5.底部显示当前网站的访问量(日访问量以及总访问量)


——————————————————————coding———————————————————————————

选定某个文件夹路径,先新建一个index.js文件,作为项目入口,然后,在命令提示符环境下运行以下命令

npm init

之后就按照提示,输入对应的项目信息,初始化项目工程,完成之后,就可以开始coding啦~

打开index.js文件,先把对应模块引入(经由师傅提醒,为项目导入的时候应同时建立依赖文件的记录,这样能保证工程在其他机器运行的前能够安装所有的依赖模块)

npm install 模块名 --save

好了,这么一来,依赖的模块也搞定了,剩下的只剩下博客的后台处理逻辑以及前端展示的页面了。

var http = require('http');
var util = require('util');
var url = require('url');
var fs = require('fs');
var path = require('path');
var querystring = require('querystring');

目前模块引入如上,关于搭建服务器的基本语法,菜鸟教程上讲的非常详细,我们就不赘述了

在入口index.js文件里面,我们搭建了服务器,监听对应的端口,并对相应的接口地址进行逻辑处理:

const http = require('http');
const util = require('util');
const url = require('url');
const fs = require('fs');
const path = require('path');
const querystring = require('querystring');
//连接数据库
const mysql = require('mysql');
const connection = mysql.createConnection({     
  host     : 'localhost',       
  user     : 'root',              
  password : '1234',       
  port: '3306',                   
  database: 'mysql', 
}); 
connection.connect();


//留言功能
let message = [];
let mesLength = 0;
let messageShow = '';

let sql = 'SELECT message FROM MyOwnPage_messageBox';

const uriName = ['html','css','jsp','jpg','png'];

console.log("项目已经启动……127.0.0.1:3000监听当中")

var app = http.createServer(function (req, res) {
    let body = "";
    req.on('data', function (chunk) {
        body += chunk;
    });

    req.on('end',function(){
        body = querystring.parse(body);
        let pathName = url.parse(req.url).pathname;
        //static resource
            if(req.method == 'GET' && pathName == '/' ){
                fs.readFile(__dirname+"\\LoginPage.html", function (err, data){
                    if(err){
                        res.writeHead(404, {'Content-Type': 'text/html'});
                    }else{
                        res.writeHead(200,{'Content-Type': 'text/html'});
                        res.write(data);
                        res.end();
                    }
                })
            }
            if(req.method == 'GET' && uriName.indexOf(path.extname(pathName).substring(1)) != -1){
                if(pathName ==='/HomePage.html'){
                    let addsql = 'INSERT INTO login_log (page,ip,login_time) value (?,?,?)';
                    let ip = req.socket["remoteAddress"];
                    let date = new Date();
                    let day = date.getDate();
                    let month = date.getMonth()+1;
                    let year = date.getFullYear();
                    let login_time = year+'-'+month+'-'+day;
                    let sqlParams = [pathName,ip,login_time];
                    connection.query(addsql,sqlParams,function(err,result){
                        if(err){
                        return;
                        }        
                    })
                }
                fs.readFile(__dirname+"\\"+pathName.substring(1), function (err, data){
                    if(err){
                        res.writeHead(404, {'Content-Type': 'text/html'});
                    }else{
                        if(path.extname(pathName).substring(1) === 'css'){
                            res.writeHead(200,{'Content-Type': 'text/css'});
                        }else if(path.extname(pathName).substring(1) === 'html'){
                            res.writeHead(200,{'Content-Type': 'text/html'});
                        }else if(path.extname(pathName).substring(1) === 'jpg'){
                            res.writeHead(200,{'Content-Type': 'image/jpeg'});
                        }
                        res.write(data);
                        res.end();
                    }
                })
            }
        //获取系统时间
            if(req.method == 'GET'  && pathName == '/sysTime_wenzhiqun'){
                    let time = new Date();
                    let year = time.getFullYear();
                    let month = time.getMonth()+1;
                    let date= time.getDate();
                    let hour = time.getHours();
                    let minutes = time.getMinutes();
                    let seconds = time.getSeconds();
                    if(month<10){
                    month = "0"+month; 
                    }
                    if(date<10){
                    date = "0"+date; 
                    }
                    if(hour<10){
                    hour = "0"+hour; 
                    }
                    if(minutes<10){
                    minutes = "0"+minutes; 
                    }
                    if(seconds<10){
                    seconds = "0"+seconds; 
                    }
                    let currentTime = "当前服务器时间为:"+year+"年"+month+"月"+date+"日"+hour+":"+minutes+":"+seconds;
                res.write(currentTime); 
                res.end();
            }
            
            //提交留言信息
            if(req.method == 'POST' && pathName == '/message'){
                // 新增留言栏中的留言
                let addsql = 'INSERT INTO MyOwnPage_messageBox (messageCount,message) value (?,?)'
                let userMes = body['message'];
                let sqlParams = [mesLength+1,userMes];
                connection.query(addsql,sqlParams,function(err,result){
                    if(err){
                    res.write("提交失败,请重试");
                    res.end();
                    return;
                    }        
                    res.write("提交成功");
                    res.end();        
                  })
            }
            //获取留言栏
            if(req.method == 'GET' && pathName == '/message/list'){
                let sql = 'SELECT message FROM MyOwnPage_messageBox';
                //查询目前的留言栏
                connection.query(sql,function (err, result) {
                        if(err){
                        return;
                        }
                    message = [];
                    mesLength = 0;
                    for(i of result){
                        message[mesLength]=i.message;
                        mesLength++;
                    } 
                });
                let messageShow = '留言栏:';
                if(message.length<=10){
                    for(let i = message.length,j=1;i>0;i--){
                        messageShow = messageShow+'
'+'('+j+')'+message[i-1]; j++; } }else if(message.length>10){ for(let i = message.length,j=1;i>(message.length-10);i--){ messageShow = messageShow+'
'+'('+j+')'+message[i-1]; j++; } } res.write(messageShow); res.end(); } //获取客户端信息 if(req.method == 'GET' && pathName == '/sysinfo'){ let ob = req.headers["user-agent"].split(" "); let system = ob[1].substring(1); let chrome = ob[9].split('/')[0]; let ip = req.socket["remoteAddress"]; let iptype = req.socket["remoteFamily"]; res.write("操作系统:"+system+" 浏览器: "+chrome+"ip地址: "+ip+" ip类型: "+iptype); res.end(); } //获取访客量 if(req.method == 'GET' && pathName == '/visit'){ let sql = 'SELECT login_time FROM login_log'; //查询目前的总访客量和日访客量 connection.query(sql,function (err, result) { if(err){ return; } let arr = []; //日访问量 let date = new Date(); let day = date.getDate(); let month = date.getMonth()+1; let year = date.getFullYear(); let login_date = year+'-'+month+'-'+day; let day_vi = 0; for(i of result){ arr.push(i.login_time); } // 访问总量 let vi = arr.length; for (let i = 0; i

以上是一个非常简单的实现,由于是很久很久以前写的了,所以会有很多不足的地方,包括:

1)数据库没有使用连接池去管理,没有考虑每条数据库连接之后的处理

2)有较多的模块没有抽象出来写成公共的工具类(其实也是想要一次性完成,毕竟只用一天的时间……比较粗糙)

3)听说没有error处理的代码不是好代码

另外关于具体的代码实现有几点是需要补充说明的:

1)读取静态文件,像html、css、jpg等文件时,需要判断请求文件的类型,并对其做出相应的处理,而不只是单纯的针对html后缀的文件进行返回(毕竟也是自己踩过的坑,对于html的基础没有学好,导致漏处理)

2)熟练的应用node的原生模块,可以使用querysthing、url等模块对http请求进行解析,以便于我们对于访问资源的处理以及请求的响应

3)依据restful风格,get请求与post请求归类之后针对其uri进行操作

4)由于当时对于整块html不算熟悉,用了最原生态的返回html源码去写页面,⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄

主要的代码如上,其实逻辑很简单,API也很简单,完成之后点击一波F5,或者在命令提示符窗口里,输入node index.js 运行工程…(其实我大部分的时间还是花在这个渣渣的前端页面编写上)

跑起来的工程大概就是这样了——

【nodeJS】从nodejs原生的博客网站搭建到 koa框架实现个人博客网站搭建_第1张图片

在浏览器输入localhost:3000/HomePage.html,前端显示如下——

【nodeJS】从nodejs原生的博客网站搭建到 koa框架实现个人博客网站搭建_第2张图片

至此,原生的博客网站搭建完成了,勉强完成以上的需求(页面有点丑…勿喷~)

——————————————————————我是分割线—————————————————————————

经过一番koa框架的学习,终于我要迈出从渣渣原生到美腻的框架实现的蜕变了!

学习资料参考:

1.koa官网:https://koa.bootcss.com/

2.ejs模板引擎学习

开始之前重新澄清一下需求,这一次,我们的需求更新了一些细节上的东西

目标功能

包含之前的五个功能

6.增加登录、注册功能

7.增加登录后的角色权限,并分配给管理员角色操作留言栏的功能

8.在页面中添加显示登录的角色的用户名

9.网站的样式定时更换

10.优化后台的数据持久化方式(使用连接池)


——————————————————————coding———————————————————————————

一、项目创建

选定文件夹作为工程存放位置,之后还是在cmd环境下使用

npm init
先对工程进行初始化,之后使用以下指令创建koa工程结构
npm koa

好了,接下来还是需要我们手动创建一个index.js文件,开始敲我们的代码——

二、工程目录结构

【nodeJS】从nodejs原生的博客网站搭建到 koa框架实现个人博客网站搭建_第3张图片

三、开始书写我们的主代码:

主目录下的index.js(入口文件)

const koa = require('koa');
const app = new koa();
const static = require('koa-static');
const index = require('./routes/index');
const views = require('koa-views');
const bodyParser = require('koa-bodyparser');
var MysqlStore = require('koa-mysql-session');
const session = require('koa-session-minimal');
const config = require('./config/default.js');
const ejs = require('ejs');
const path = require('path');

//静态资源加载
app.use(static(__dirname+'/public'));
//视图调用
// app.use(views(__dirname+'/views'));
app.use(views(path.join(__dirname, './views'), {
    extension: 'ejs'
}))

// session存储配置
const sessionMysqlConfig= {
    user: config.database.USERNAME,
    password: config.database.PASSWORD,
    database: config.database.DATABASE,
    host: config.database.HOST,
}
  
  // 配置session中间件
app.use(session({
    key: 'USER_SID',
    store: new MysqlStore(sessionMysqlConfig)
}))

app.use(bodyParser());

//这里的 allowedMethods 用于校验请求的方法,如果用 post 请求访问 get 接口,就会直接返回失败
app.use(index.routes(),index.allowedMethods());


app.listen(3000,function(){
    console.log("项目已经启动");
});

这里面,我们引入了本次工程依赖的模块,和原生的工程不同,我们这次加入了很多的“新面孔”,包括有处理静态资源的public、views模块,session模块(对于角色的控制)、ejs模块(模板引擎)以及数据库连接池配置等等

关于koa我有几点想说的:

1)koa框架将req和res封装到了ctx当中,方便我们直接使用ctx调用原来res和req的方法

2)koa中引入了中间件的概念,我们使用.use()的方法去调用我们需要使用的中间件,按照从上往下的顺序,中间件的使用是有顺序区分的

3)采用MVC的思想,我们会将整个工程目录去做分层,如同工程结构截图所示,views里存放的是我们返回的视图,routes存放的是控制器和我们的业务处理(核心所在)…

routes目录下的index.js

const fs = require('fs');
const router = require('koa-router')();
const timeGetter = require('../utils/timeGetter');
const sqlUser = require('../lib/mysql');
const session = require('koa-session-minimal');
let mesLength2 ;

router.get('/HomePage',async (ctx)=>{
    //记录访客量
    let ip = ctx.request.socket["remoteAddress"];
    let currentTime = timeGetter.getServerDate();
    let path = '/HomePage.html' ;
    let value =[path,ip,currentTime]; 
    if(ctx.session){
        await  ctx.render('homePage',{
            session : ctx.session
        })
    }else{
        await ctx.render('homePage',{
            session : null
        })
    }
    sqlUser.addVisit(value);
    sqlUser.getAllMesCount().then(result=>{
        mesLength2 = result['count(message)'];
    })
})

//获取服务器时间
router.get('/sysTime_wenzhiqun', ctx => {
    ctx.body = (timeGetter.getServerTime()).toString();
})

//获取留言栏
router.get('/message/list', async(ctx) => {
    let mesLength = 0;
    let message = [];
    await sqlUser.getAllMes().then(result =>{
        for(i of result){
            message[mesLength]=i.message;
            mesLength++;
        }
    })
    let messageShow = '留言栏:';
    //普通用户
    if(ctx.session.role != 'admin'){
        if(message.length<=10){
            for(let i = message.length,j=1;i>0;i--){
                messageShow = messageShow+'
'+'('+j+')'+message[i-1]; j++; } }else if(message.length>10){ for(let i = message.length,j=1;i>(message.length-10);i--){ messageShow = messageShow+'
'+'('+j+')'+message[i-1]; j++; } } }else{ if(message.length<=10){ for(let i = message.length,j=1;i>0;i--){ messageShow = ''+''; j++; } }else if(message.length>10){ for(let i = message.length,j=1;i>(message.length-10);i--){ messageShow = '
'+messageShow+'
('+j+')

'+message[i-1]+'

'+''; j++; } } messageShow = messageShow +'
'+messageShow+'
('+j+')

'+message[i-1]+'

'; } mesLength2 = mesLength; ctx.body = messageShow; }) //获取客户端信息 router.get('/sysinfo', ctx => { let ob = ctx.request.headers["user-agent"].split(" "); let system = ob[1].substring(1); let chrome = ob[9].split('/')[0]; let ip = ctx.request.socket["remoteAddress"]; let iptype = ctx.request.socket["remoteFamily"]; ctx.body = "操作系统:"+system+" 浏览器: "+chrome+"ip地址: "+ip+" ip类型: "+iptype; }) //获取访问量 router.get('/visit',async (ctx) => { await sqlUser.getVisitCount() .then(result =>{ let arr = []; let day_vi = 0; let today_date = timeGetter.getServerDate(); for(i of result){ arr.push(i.login_time); } // 访问总量 let vi = arr.length; for (let i = 0; i{ let userMes = ctx.request.body.message; let sqlParams = [mesLength2+1,userMes]; await sqlUser.insertMes(sqlParams).then(result=>{ ctx.body = "成功了!"; }); }); //删除留言 router.post('/deleteMes',async (ctx) =>{ let userMes = ctx.request.body.message; let sqlParams = [userMes]; await sqlUser.deleteMes(sqlParams).then(result=>{ ctx.body = "删除成功!"; }); }); //登陆 router.post('/login',async (ctx) =>{ let username = ctx.request.body.profile; let pwd = ctx.request.body.pwd; let user = {'username':username}; await sqlUser.findDataByUser(username,pwd).then( result=>{ if(result[0]){ user['role'] = result[0].role; ctx.session = user; ctx.body = '2'; }else{ ctx.body = '1' ; } }) }) //登录引导页 router.get('/',async ctx =>{ if(JSON.stringify(ctx.session) =='{}'){ await ctx.render('login'); }else{ await ctx.render('homePage',{ session : ctx.session }); } }) //登出 router.get('/clearSession',async (ctx) =>{ let clear = function(){ ctx.session = null; } clear(); await ctx.render('login'); }) //注册 router.post('/register',async(ctx) =>{ let profile = ctx.request.body.profile; let pwd = ctx.request.body.pwd; let username = ctx.request.body.username; let create_time = timeGetter.getServerDate(); let role = 'normal_user'; let value = [profile,pwd,username,role,create_time]; await sqlUser.insertUser(value).then(result=>{ if(result){ let user = {username:username,role:role}; ctx.session = user; ctx.body = "1"; }else{ ctx.body = '2'; } }) }) module.exports = router;

值得注意的是,我们在这里面开始应用上ES6、7标准的语法,async/await函数以及promise的语法:

1)在routes中的index文件,我们应该在装有异步的事件中使用async函数,部分api是不需要使用到async函数,因为不存在异步回调的使用,如返回服务器时间、获取客户端系统信息等

2)使用了async函数,参数列表是否使用next取决于函数的内容,next()会直接进入下一个中间件,等待中间件处理完毕之后再回到当前函数继续执行

3)我们在数据库操作完成返回了一个promise对象,可以利用promise对象的.then()对await 里获得的返回对象进行下一步的操作,并且建议await后面的语句是一个异步的事件,当然如果不是也没关系,await会按照正常的语句去处理,相当于同步处理

关于使用的几大模块:

1.session:直接对session的操作,依赖koa-session-minimal我们实现了对ctx.session非常简便的操作,进行用户角色和权限的控制

2.sql:将sql的管理控制抽离出来,在工程结构里集成管理


sql数据持久化操作

mysql.js

var mysql = require('mysql');
var config = require('../config/default.js')

var pool  = mysql.createPool({
  host     : config.database.HOST,
  user     : config.database.USERNAME,
  password : config.database.PASSWORD,
  database : config.database.DATABASE
});

let query = function( sql, values ) {

  return new Promise(( resolve, reject ) => {
    pool.getConnection(function(err, connection) {
      if (err) {
        resolve( err )
      } else {
        connection.query(sql, values, ( err, rows) => {

          if ( err ) {
            reject( err )
          } else {
            resolve( rows )
          }
          connection.release()
        })
      }
    })
  })

}

// 提交留言
let insertMes = function(value) {
  let _sql = "INSERT INTO MyOwnPage_messageBox (messageCount,message) value (?,?) "
  return query( _sql, value )
}
// 删除留言
let deleteMes = function(value) {
  let _sql = "DELETE FROM MyOwnPage_messageBox WHERE message = ?"
  return query( _sql, value )
}

// 获取所有的留言总量
let getAllMesCount = function() {
  let _sql = "SELECT count(message) FROM MyOwnPage_messageBox"
  return query( _sql)
}

// 获取所有的留言
let getAllMes = function() {
  let _sql = "SELECT message FROM MyOwnPage_messageBox"
  return query( _sql)
}

// 查询访客量
let getVisitCount = function() {
  let _sql = "SELECT login_time FROM login_log"
  return query( _sql)
}

//增加访客量
let addVisit = function(value) {
  let _sql = "INSERT INTO login_log (page,ip,login_time) value (?,?,?) ";
  return query( _sql, value);
}

//通过账户名密码查询登陆id及角色
let findDataByUser = function (profile,pwd) {
  let _sql = `SELECT username, role FROM USER_PROFILE WHERE email = "${profile}"`
  return query( _sql)
}

//新增(注册)用户
let insertUser = function(value) {
  let _sql = "INSERT INTO user_profile (email,pwd,username,role,create_time) value (?,?,?,?,?) ";
  return query( _sql, value );
}

module.exports={
    insertMes,
    deleteMes,
    getAllMes,
    findDataByUser,
    getVisitCount,
    addVisit,
    getAllMesCount,
    insertUser
}

登录页面

login.ejs




    
    IronMan 登陆页面
    
    
    


        
        

欢迎来到IronMan的个站

登陆

账号:
密码:

注册

请填写以下信息并完成注册:
账号(邮箱):
用户名:
密码:

修改密码

账号
请输入旧密码:
新密码:
确认密码:

登录页面是常规的操作了,很简单,重点在于我们的主页,使用模板引擎去把session的内容展示出来

主页 homePage.ejs




    
    
    
    个人站点主页
    



    
    

点我刷新服务器时间

您好, <% if(session.username){ %> <%= session.username %> 退出登录 <% } %> <% if(!session.username){ %> 您还未登录 <% } %>

个人介绍

IronMan

诞生地:Marvel

详细介绍:

托尼·史塔克(Tony Stark)即钢铁侠(Iron Man), 是美国漫威漫画旗下超级英雄,初次登场于《悬疑故事》(Tales of Suspense)第39期(1963年3月), 由斯坦·李、赖瑞·理柏、唐·赫克以及杰克·科比联合创造。全名安东尼·爱德华·“托尼”·斯塔克(Anthony Edward “Tony” Stark), 是斯塔克工业(STARK INDUSTRIES)的董事长,因于一场阴谋绑架中,胸部遭弹片穿入,生命危在旦夕,为了挽救自己的生命, 在同被绑架的物理学家殷森(Yin Sen)的协助下托尼造出了防止弹片侵入心脏的方舟反应炉从而逃过一劫,后又用方舟反应炉作为能量运转的来源, 暗中制造了一套高科技战衣杀出重围后逃脱,后参与创立复仇者联盟。

提交

点我显示留言栏

点我显示当前访问的客户端信息为:

实现后,管理员登录的页面如下图:

1)在服务器时间下多了登录的用户名显示

2)支持管理对留言栏的删除处理

3)其实还有一个隐藏的页面背景色的定时变化,截图所以看不出来~但是代码里面是可以发现的

【nodeJS】从nodejs原生的博客网站搭建到 koa框架实现个人博客网站搭建_第4张图片

结束语

到这里,基本上整个网站就已经完成了~

这个博客网站比较简单,代码有很多改进的地方,希望对你们有帮助,如果有什么建议可以和我联系,qq:657897294,有什么不懂得也可以问我,有空就会回复啦!

git地址:https://github.com/VeniesLoveJava/nodejs_Koa_BlogWeb.git

非常感谢你读完这篇这么长的文章!

你可能感兴趣的:(日记帖/成长帖)