本人于今年1月开始接触node js,接触的时间不长,所以技术水平还有待提高,希望这篇简单的文章能够对大家学习nodejs有所帮助。
一、项目描述
1、项目概要:
主要是使用nodejs实现一个基本的demo的增删改查的功能为在nodejs技术上项目的扩展开发做准备。开发环境主要使用了webstorm,数据库使用了mysql,使用nodejs的express。调用了nodejs的mysql扩展包。调用了nodejs的node-uuid扩展包,主要用于uuid 的创建上。调用了nodejs的async扩展包,主要用于对于回调函数的排序处理。
页面模版主要使用了ejs,但在具体使用上将后缀名改为了html。
2、项目结构:
Bin目录:www.js文件。创建项目时开发环境自动创建,可以用来修改默认访问地址。
Dao目录:关于数据库相关操作的工具集合。
Config.js:数据库注册信息。
Daobase:数据库基本dql和dml操作的工具。
Userdao:对于tb_test_user_表的增删改查的函数的工具类。
Model目录:项目数据模型。
User:数据模型。
Node_modules:开发环境添加的项目相关的库文件。
Public:(未用到)
Routes:nodejs中express包的控制层,路由选择器的功能。用于分配访问路径。
Index.js:基本的页面后台逻辑函数的实现
users.js:(未用到)
Test:自定义测试包
asyncTest.js:用于测试async组件(测试函数可用但是实际函数有问题,留作以后研究点)
Test.js:对于daobase.js功能的测试。
TestDao.js:用于userdao.js功能的测试。
Views:ejs模版页面集合
Error.html:用于显示错误信息
Index.html:登录页面
Insert.html:添加页面
Success.html:用于显示所有数据,功能成功后的跳转页面
Update.html:修改页面
App.js:路由映射表(routes下主要用于跳转逻辑,此文件主要用于映射选择)
Package.json:类似与mavon的pom.xml,主要用于依赖包的版本注册
3、数据库设计细节
数据库名:db_test_
表名:tb_test_user_
列名:id 数据类型:varchar 默认长度:50 其他:主键
列名:name 数据类型:varchar 默认长度:50 其他:无
列名:password 数据类型:varchar 默认长度:50 其他:无
二、详细实现
1、www.js文件
//引入app.js文件
var app = require('../app');
2、Config.js文件
module.exports={
host:'localhost',
user:'root',
password:'root',
database:'db_test_',
ports:3306
};
详解:
Module.exports指将文件暴露给module空间,而文件所代表的为当前函数。(此文件函数为json格式)。 host:为mysql的url,user,password,database,ports分别为:需要用的数据库的帐号、密码、数据库名和端口号。
3、Daobase.js文件
var config=require('./config.js');//引入配置文件
var mysql=require('mysql');//引入mysql驱动
var pool=mysql.createPool(config);//创建数据库连接池
//dql函数(用于查询的函数)
exports.executeQuery=function(sql, data, callback){
//创建连接,并在其回调函数内设置相关操作(nodejs的相关操作都在回调函数里,问题较大,加大了代码复杂度)
pool.getConnection(function(err,conn){
if(err){
callback(err,null,null);
}else{
/*与dml的回调函数参数表不同,其他类似
Qerr:当正确时返回null,错误时返回有用值
Vals:用于存储返回值信息,返回的为一个对象数组,需用下表加表的列名取得值,示例如下:vals[0].name
Fields:返回表结构的相关信息。(本人未如何使用)
*/
conn.query(sql,data,function(qerr, vals, fields){
//重置数据库连接
conn.release();
//用于传入回调函数,处理数据
callback(qerr,vals,fields);
});
}
});
};
//dml函数(用于增加、修改、删除的函数)
exports.executeUpdate = function(sql, data, callback) {
pool.getConnection(function(err, conn) {
if (err) {
callback(err, null, null);
} else {
//data用于占位符的数据传入,一些意外的情况本人还未测试
conn.query(sql, data, function(qerr, result) {
//释放连接
conn.release();
//事件驱动回调
callback(qerr, result);
});
}
});
};
4、userdao.js文件
var DaoBase = require('./DaoBase');//引入dbutil
var UUID = require('node-uuid');//引入uuid模块
var user_DB = require('../model/User');//引入模型模块
var userDB = new user_DB();//实例化模型模块
//添加用户
exports.InsertUser=function(params, callback){
var data=[];//写差了,应该用实例化后的模型,但是不影响使用
var sql='INSERT INTO tb_test_user_ (id,NAME,PASSWORD) VALUES(?,?,?)';
var id=UUID.v4();
data.push(id);//按占位符顺序加入数组
data.push(params.name);
data.push(params.password);
DaoBase.executeUpdate(sql,data,callback);
};
//修改用户
exports.UpdateUser=function(params,id, callback){
var sql='UPDATE tb_test_user_ SET NAME=?,PASSWORD=? WHERE id=?';
var data=[];
data.push(params.name);
data.push(params.password);
data.push(id);
DaoBase.executeUpdate(sql,data,callback);
};
//删除用户
exports.DeleteUser=function(id,callback){
var sql='delete from tb_test_user_ where id=?';
var data=[];
data.push(id);
DaoBase.executeUpdate(sql,data,callback);
};
//查询所有用户
exports.FindAll=function(callback){
var sql='select * from tb_test_user_';
DaoBase.executeQuery(sql,callback);
};
//根据id查询
exports.FindById=function(id,callback){
var sql='select * from tb_test_user_ where id=? ';
var data=[];
data.push(id);
DaoBase.executeQuery(sql,data,callback);
};
//登陆验证
exports.UserLogin=function(params,callback){
var sql='select * from tb_test_user_ where name=? and password=?';
var data=[];
data.push(params.name);
data.push(params.password);
DaoBase.executeQuery(sql,data,callback);
}
5、user.js文件
module.exports=User;
function User(){
this.tableName='tb_test_user_';
this.id='id';
this.name='name';
this.password='password';
}
6、index.js文件
var express = require('express');//引入express包
var userDao=require('../dao/UserDao.js');//引入userdao文件
var user_DB = require('../model/User');//引入user实体
//修改初始化
exports.update=function(req,res){
var id=req.query.id;//nodejs 的get获取传参办法使用req.query.参数名
console.log(id);
userDao.FindById(id,function(qerr, vals,fields){
if(!(qerr==null)){
//选择渲染ejs模版并跳转传参
res.render('error',{
message:'寻找修改元素失败'
});
}
//获得数据库里面的信息并传到页面(请注意这里用到了多重回调函数嵌套)
var username=vals[0].name;
var password=vals[0].password;
var id=vals[0].id;
res.render('update',{
title:'修改数据',
username:username,
password:password,
id:id
});
});
};
//修改(对于修改页面的初始化工作)
exports.doUpdate=function(req,res){
var id=req.body.id;//post传值的接收方式req.Body.参数名(post传值需要)
var name=req.body.username;
var password=req.body.password;
var user=new user_DB();
user.name=name;
user.password=password;
userDao.UpdateUser(user,id,function(qerr, result){
if(!(qerr==null)){
res.render('error',{
message:'修改失败'
});
}
//用影响行数去判定dml操作是否成功(请注意即使影响行数为0,函数依然不返回错误)
if(result.affectedRows>0){
console.log('success');
userDao.FindAll(function(qerr,vals,fields){
var result=vals;
res.render('success',{
title:'修改成功',
result:result
});
});
}
});
};
//删除
exports.delete=function(req,res){
var id=req.query.id;
userDao.DeleteUser(id,function(qerr, result){
if(!(qerr==null)){
console.log('error');
res.render('error',{
message:'删除失败'
});
}
if(result.affectedRows>0){
console.log('success');
userDao.FindAll(function(qerr,vals,fields){
var result=vals;
res.render('success',{
title:'删除成功',
result:result
});
});
}
});
};
//添加初始化
exports.insert=function(req,res){
res.render('Insert',{
title:'添加数据'
});
};
//添加
exports.doInsert=function(req,res){
var username=req.body.username;//我觉得这里写的有点重复,可以直接将值赋给实体
var password=req.body.password;
var user=new user_DB();
user.name=username;
user.password=password;
userDao.InsertUser(user,function(qerr, result){
if(!(qerr==null)){
res.render('error',{
message:'添加失败'
});
}
if(result.affectedRows>0){
userDao.FindAll(function(qerr,vals,fields){
var result=vals;//直接将结果集封装到json里传给页面,由页面去解封处理
res.render('success',{
title:'添加成功',
result:result
});
});
}
});
};
//登录初始化
exports.login=function(req,res){
res.render('index',{
title:'你好'
});
};
//登录验证
exports.doLogin=function(req,res){
var username=req.body.username;
var password=req.body.password;
var user=new user_DB();
user.name=username;
user.password=password;
userDao.UserLogin(user,function(qerr, vals,fields){
if((!(qerr==null))||(vals.length<1)){
res.render('error',{
message:'登录失败'
});
}else{
userDao.FindAll(function(qerr,vals,fields){
var result=vals;
res.render('success',{
title:'你好',
result:result
});
});
}
});
};
7、 Error.html
<h1><%= message %>h1>//只用于显示结果
8、 Index.html
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><%= title %>title>
<link rel='stylesheet' href='/stylesheets/style.css' />
head>
<body>
<h1><%= title %>h1>
<p>Welcome to <%= title %>p>
<form method="post" > //表单可不命名(多表单的页面需要进一步尝试),由于get用于初始化了,所以只能用post提交(node js 有use、get、post三个函数用于处理请求,这只是我所知的,如果有其他方式应可以解决只能post提交问题,当然如果使用中间件应该也可以,我刚刚接触对于中间件还不是很熟悉,当然对于进阶中间件还是很重要的)
<input type="text" name="username" > //name属性用于服务器端接收
<input type="password" name="password" > //
<input type="submit" value="submit">
form>
body>
html>
9、 Insert.html //与登录功能相似所以不再赘述
html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title><%= title %>title>
<link rel='stylesheet' href='/stylesheets/style.css' />
head>
<body>
<h1><%= title %>h1>
<form method="post">
<p>账号名:<input type="text" name="username">p>
<p>密码:<input type="text" name="password">p>
<p><input type="submit" value="submit">p>
form>
body>
html>
10、 Success.html
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><%= title %>title>
<link rel='stylesheet' href='/stylesheets/style.css' />
head>
<body>
<h1><%= title %>h1>
<table>
<% result.forEach(function(name){ %>//迭代遍历取出后台传过来的result值,
函数中的name为result中依次各个元素的别名,用name取出每个数据库中列名的值
<tr>
<td><%= name.name %>td>
<td><%= name.password %>td>
<td>td>
<td><a href="/delete?id=<%= name.id %>">删除a>td>//超链接并用get方式传值
<td><a href="/update?id=<%= name.id %>">修改a>td>
tr>
<% }) %>
table>
<p><a href="/Insert">添加a>p>
body>
html>
11、update.html //只是学习用的示例所以修改页面写的有些简略
html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title><%= title %>title>
<link rel='stylesheet' href='/stylesheets/style.css' />
head>
<body>
<h1><%= title %>h1>
<form method="post">
<p><%= id %>p>
<p><input type="text" name="id" value="<%= id %>"> p>//用于向后台传id,应当设为不可见
<p><%= username %>p>
<p>账号名:<input type="text" name="username" value="<%= username %>" >p>
<p><%= password %>p>
<p>密码:<input type="text" name="password" value="<%= password %>">p>
<p><input type="submit" value="submit">p>
form>
body>
html>
12、App.js(以下只展示需要改和自己写的模块)
var routes = require('./routes/index');//引入自己写的控制逻辑文件
//post解析函数(可复用的必用代码)
app.use(bodyParser.urlencoded({
extended: true
}))
//自定义路由(在app.use的第二个参数中写工厂函数,若是可行则可以实现特殊功能)
app.use('/delete',routes.delete);//将删除逻辑与删除页面路径对应(访问nodejs时,系统先在app.js文件中寻找路径对应处理函数)
app.get('/update',routes.update);//修改初始化
app.post('/update',routes.doUpdate);//修改页面的post处理
app.get('/Insert',routes.insert);//添加初始化
app.post('/Insert',routes.doInsert);//添加处理函数
//初始路径请放到最下面
app.get('/',routes.login);//登录页面初始化
app.post('/',routes.doLogin);//登录验证
// view engine setup //用于将ejs模版的后缀改为html(据说是3版本后的写法)
app.set('views', path.join(__dirname, 'views'));//确定模版所在文件夹
app.engine('.html', require('ejs').renderFile);
app.set('view engine', 'html');
13、package.Json
{
"name": "LoginTest0",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"async": "~1.4.2", //自己添加
"node-uuid": ">= 1.4.1",//自己添加
"mysql": "~2.9.0", //自己添加
"body-parser": "~1.13.2",
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"ejs": "~2.3.3",
"express": "~4.13.1",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0"
}
}
三、总结
本人新接触nodejs有很多地方都是第一次接触,所以写的这个例子相对来讲只是入门级别,如果有问题希望能给于指正,有问题希望能和大家一起探讨。在写这个例子的过程中,用到了很多回调函数嵌套的情况,一般说来这是一个无可奈何的事情,但是这样加大了程序的阅读难度。我希望能在之后的学习中找到一种能够提高代码可读性的办法。在这个例子中有很多回调函数实际是重复的,这实际上是一种冗余。希望大家能够借鉴我好的方面,摒除我坏的方面。