前言:原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 运行工程…(其实我大部分的时间还是花在这个渣渣的前端页面编写上)
跑起来的工程大概就是这样了——
在浏览器输入localhost:3000/HomePage.html,前端显示如下——
至此,原生的博客网站搭建完成了,勉强完成以上的需求(页面有点丑…勿喷~)
——————————————————————我是分割线—————————————————————————
经过一番koa框架的学习,终于我要迈出从渣渣原生到美腻的框架实现的蜕变了!
学习资料参考:
1.koa官网:https://koa.bootcss.com/
2.ejs模板引擎学习
开始之前重新澄清一下需求,这一次,我们的需求更新了一些细节上的东西
目标功能
(包含之前的五个功能)
6.增加登录、注册功能
7.增加登录后的角色权限,并分配给管理员角色操作留言栏的功能
8.在页面中添加显示登录的角色的用户名
9.网站的样式定时更换
10.优化后台的数据持久化方式(使用连接池)
——————————————————————coding———————————————————————————
一、项目创建
选定文件夹作为工程存放位置,之后还是在cmd环境下使用
npm init
先对工程进行初始化,之后使用以下指令创建koa工程结构
npm koa
好了,接下来还是需要我们手动创建一个index.js文件,开始敲我们的代码——
二、工程目录结构
三、开始书写我们的主代码:
主目录下的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 = ''+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++;
}
}
messageShow = messageShow +'
';
}
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
欢迎来到IronMan的个站
登陆
账号:
密码:
注册
请填写以下信息并完成注册:
账号(邮箱):
用户名:
密码:
修改密码
账号
请输入旧密码:
新密码:
确认密码:
登录页面是常规的操作了,很简单,重点在于我们的主页,使用模板引擎去把session的内容展示出来
主页 homePage.ejs
个人站点主页
IronMan
点我刷新服务器时间
您好,
<% 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)的协助下托尼造出了防止弹片侵入心脏的方舟反应炉从而逃过一劫,后又用方舟反应炉作为能量运转的来源,
暗中制造了一套高科技战衣杀出重围后逃脱,后参与创立复仇者联盟。
点我显示留言栏
点我显示当前访问的客户端信息为:
点我显示访问量