基于express,MySQL,ejs实现的一个简单基本的网站后台管理应用

git地址:https://github.com/Nealyang/ejs-express-mysql

喜欢的朋友方便的话可以给个star (^-^)V

顺便推广一波nodejs技术交流群,群号:209530601

ejs mysql nodejs express express-router..

效果图

一直上传不成功,我放弃了,具体的可以去git上看哈~

https://github.com/Nealyang/ejs-express-mysql

整体架构

项目重点在后端开发中,web端页面并没有涉及到,后端管理流程大致如下:

  • 路由控制分为admin,web,还是那句话,我们操作全部在admin中
  • 跳转到admin拦截所有的请求,判断用户是否登录
  • 未登录则重定向到登录,登陆成功后设置session。不懂session?点击这里
  • 登录后则可进行相关的操作,数据的增删改查等功能。

后端开发

后台基本架构、路由设置

const express = require('express');
const expressStatic = require('express-static');
const bodyParser = require('body-parser');
const multer = require('multer');
const multerObj = multer({dest:'./static/upload'});
const cookieParser = require('cookie-parser');
const cookieSession = require('cookie-session');
const consolidate = require('consolidate');
const ejs = require('ejs');

//创建服务器
var server = express();
server.listen(8080);

//解析请求数据

server.use(bodyParser({
    extended:false
}));
server.use(multerObj.any());

//设置cookie,session
server.use(cookieParser('Neal_signed'));
(function () {
    var arr = [];
    for(var i = 0;i<10000;i++){
        arr.push('keys_'+Math.random());
    }
    server.use(cookieSession({
        name:'session_id',
        keys:arr,
        maxAge:20*60*1000//一般我会设置20分钟,这里是为了感受session过期~~带来的快感~?(●´∀`●)ノ
    }))
})();

//设置模板
server.set('view engine','html');
server.set('views','./views');
server.engine('html',consolidate.ejs);
//设置路由
server.use('/admin',require('./router/admin/index')());
server.use('/',require('./router/web/index')());


//静态文件的请求
server.use('/files',expressStatic('./static'));

我的基本架构如下,关于每一部分的功能,都已经标注。关于路由的控制在admin/index.js跟server.js大同小异,我想大家也都应该知道了。

  • 登录功能

    登录功能这里主要说两点

  • 密码的md5签名(当然,大多数人说是md5加密)
  • session的应用 在lib中存放着自己写的一些方法,作为一个库,admin初始化有三个用户,包括u/p:root,neal,Nealyang 关于密码的签名方法主要如下: var crypto = require('crypto');

module.exports = {
    MD5_SUFFIX : 'JDSAIOEUQOIoieuoiqv#$%^&dhfja)(* %^&FGHJfyuieyfhfhak(^.^)YYa!!\(^o^)/Y(^o^)Y(*^__^*)ヘ|・∀・|ノ*~●',
    md5:function (pwd) {
        var md5 = crypto.createHash('md5');
        return md5.update(pwd).digest('hex');
    }
};

MD5_SUFFIX是加密字符串,用法如路由login.js:

router.post('/',function (req,res) {
        var username = req.body.username;
        var password = common.md5(req.body.password+common.MD5_SUFFIX);
        if(username && password){
            db.query('SELECT * FROM admin_table WHERE username="'+username+'"',function (err,userData) {
                if(err){
                    console.error(err);
                    res.status(500).send({code:500,data:[],msg:'database error'});
                }else if(userData.length == 0){
                    res.status(400).send({code:400,data:[],msg:'parameters error'});
                }else{
                    if(userData[0].password != password){
                        res.status(400).send({code:400,data:[],msg:'username or password error'});
                    }else{
                        req.session['user_id'] = userData[0].ID;//注意这里是在req上面
                        res.status(200).send({code:200,data:[],msg:'success'});
                    }
                }
            })
        }else{
            res.status(400).send({code:400,data:[],msg:'parameters error'});
        }
    });

从上面的代码中也展现了什么时候设置session,并且值得提一下的是这里提供给前端页面的是接口,这样的话很多逻辑都放到了前端,后面我们都是通过页面渲染来输出的了。下面是所有请求的拦截判断:

router.use(function (req,res,next) {
        if(!req.session['user_id'] && req.url != '/login'){
            res.redirect('/admin/login');
        }else{
            next();
        }
    });

ejs前端页面的重点代码讲解

公共头部的引入:

<% include common/top.ejs %>

查询数据库的前端展示:

 <% for(var i = 0;i<formData.length;i++){%>
        <tr>
            <td><%=formData[i].ID%></td>
            <td><%=formData[i].title%></td>
            <td><%=formData[i].author%></td>
            <td><%=formData[i].summary%></td>
            <td><%=formData[i].href%></td>
            <td>
                <a href="?action=del&id=<%=formData[i].ID%>" onclick="return confirm('确定删除?')">
                    <button>删除</button>
                </a>
                <a href="?action=mod&id=<%=formData[i].ID%>">
                    <button>修改</button>
                </a>
            </td>
        </tr>
        <%}%>

修改后我们通过页面给的标识来知道是否为修改的提交,毕竟这里我们没有前端逻辑的js

<%if(typeof modData != 'undefined'){%>
    <div class="add_form" style="display: flex">
        <img src="/files/admin/img/add.png">
        <div class="form_bg"></div>
        <form action="?" method="post">
            <input type="hidden" name="modified" value="<%= modData[0].ID %>">
            标题: <input type="text" name="title" autofocus="autofocus" value="<%=modData[0].title%>"><br>
            作者: <input type="text" name="author" value="<%=modData[0].author%>"><br>
            摘要: <textarea name="summary"><%=modData[0].summary%></textarea><br>
            链接: <input type="text" name="href" value="<%=modData[0].href%>"><br>
            <input type="submit" value="确认修改">
        </form>
    </div>
    <%}%>

博文管理

还是那句话,如果是仅仅提供前端的接口的话,这里会方便很多,然后我们用的是ejs,所以很多的逻辑都放在了后端,在get/post到请求的时候需要做很多判断 get方法请求如下:

 router.get('/', function (req, res) {
            switch (req.query.action) {
                case 'del':
                    //删除操作
                    db.query('DELETE FROM blog_list_table WHERE id="'+req.query.id+'"',function (err,resultData) {
                        if(err){
                            console.error(err);
                            res.status(500).send({code:500,msg:'database error'});
                        }else{
                            res.redirect('/admin/blog');
                        }
                    });
                    break;
                case 'mod':
                    //修改操作
                    db.query('SELECT * FROM blog_list_table WHERE id="'+req.query.id+'"',function (err,modData) {
                        if(err){
                            console.error(err);
                            res.status(500).send({code:500,msg:'database error'});
                        }else if(modData.length == 0){
                            res.status(400).send({code:400,msg:'parameters error'});
                        }else{
                            db.query('SELECT * FROM blog_list_table',function (err,allData) {
                                if(err){
                                    console.error(err);
                                    res.status(500).send({code:500,msg:'database error'});
                                }else{
                                    res.render('admin/blog.ejs',{formData:allData,modData:modData});
                                }
                            });
                        }
                    });
                    break;
                default:
                    db.query('SELECT * FROM blog_list_table', function (err, resultData) {
                        if (err) {
                            console.error(err);
                            res.status(500).send({code: 500, msg: 'database error'}).end();
                        } else {
                            res.render('admin/blog.ejs', {formData: resultData});
                        }
                    });
            }

        });

这里的switch,主要是分为,查询,删除,和修改 都是些简单的CRUD操作,这里就不多细说了。不熟悉的兄弟们可以看一看,写的不好,多多提意见。

同理,post的请求,主要就是分为,文章列表的添加,和修改的两个post,代码如下:

router.post('/', function (req, res) {
            //此处验证应该更加严格,比如正则
            var title = req.body.title.trim();
            var author = req.body.author.trim();
            var summary = req.body.summary.trim();
            var href = req.body.href.trim();

            if (title && author && summary && href) {
                if(req.body.modified){
                    db.query('UPDATE blog_list_table SET title="'+title+'",author="'+author+'",summary="'+summary+'",href="'+href+'" WHERE ID="'+req.body.modified+'"',function (err,resultData) {
                        if(err){
                            console.error(err);
                            res.status(500).send({code:500,msg:'database error'});
                        }else{
                            res.redirect('/admin/blog');
                        }
                    })
                }else{
                    db.query('INSERT INTO blog_list_table (title,author,summary,href) VALUE("' + title + '","' + author + '","' + summary + '","' + href + '")', function (err, data) {
                        if (err) {
                            console.error(err);
                            res.status(500).send({code: 500, msg: 'database error'}).end();
                        } else {
                            res.redirect('/admin/blog');
                        }
                    });
                }
            } else {
                res.status(400).send({code: 400, msg: 'parameters error'}).end();
            }

        });

用户管理

后台的管理大概也即是这么多,用户管理和博文管理基本都是差不多的,这里重点是说下,这里用到的图片上传。 图片上传我用的是multer中间件,不知道的可以查下,注意用这个中间件接受图片上传时form表单的enctype必须要设置为multipart/form-data

关于图片上传后,默认是不包括后缀名的,所以这里我们需要用到fs模块的重命名操作,代码如下:

fs.rename(req.files[0].path, req.files[0].path + ext, function (err) {
                    if (err) {
                        console.error(err);
                        res.status(500).send({code: 500, msg: 'data error'});
                    } else {
                        db.query('INSERT INTO user_table (username,email,pic_header) VALUE("' + username + '","' +
                            email + '","' + pic_header + '")', function (err, resultData) {
                            if (err) {
                                console.error(err);
                                res.status(500).send({code: 500, msg: 'database error'});
                            } else {
                                res.redirect('/admin/users');
                            }
                        });
                    }
                });

前面的变量定义主要如下:

 var username = req.body.username;
            var email = req.body.email;
            if(req.files.length>0){
                var ext = pathLib.parse(req.files[0].originalname).ext;
                var pic_header = '/files/upload/' + req.files[0].filename + ext;
            }

用到path模块对路径的解析。为了获取后缀名~

说到这,对于修改也就很简单了,就是删除原先有的那个图片,然后换上现在有的图片,具体代码如下:

 //需要进行一些校验,这里就忽略了
            if(req.body.modified){//修改
                //查看有没有新传来的头像,如果有,则删除,新建,如果没有,直接更新需要更新的内容
                if(req.files.length>0){
                    //有修改头像,则进行原来头像的删除,再上传
                    db.query('SELECT * FROM user_table WHERE ID="'+req.body.modified+'"',function (err,modData) {
                        if (err) {
                            console.error(err);
                            res.status(500).send({code: 500, msg: 'database error'});
                        }else if(modData.length == 0){
                            res.status(400).send({code: 400, msg: 'parameters error'});
                        }else{
                            fs.unlink(modData[0].pic_header.replace('\/files','static'),function (err) {
                                if(err){
                                    console.error(err);
                                    res.status(500).send({code:500,msg:'operate error'});
                                }else{
                                    //删除成功,开始对新的文件进行重命名
                                    fs.rename(req.files[0].path, req.files[0].path + ext, function (err) {
                                        if (err) {
                                            console.error(err);
                                            res.status(500).send({code: 500, msg: 'operate error'});
                                        } else {
                                            db.query('UPDATE user_table SET username="'+
                                                username+'",email="' + email + '",pic_header="' +
                                                pic_header + '" WHERE ID="'+req.body.modified+'"',function (err,data) {
                                                if (err) {
                                                    console.error(err);
                                                    res.status(500).send({code: 500, msg: 'database error'});
                                                }else{
                                                    res.redirect('/admin/users');
                                                }
                                            });
                                        }
                                    });
                                }
                            })
                        }
                    })
                }else{
                    db.query('UPDATE user_table SET username="'+username+'",email="' + email + '" WHERE ID="'+
                        req.body.modified+'"',function (err,data) {
                        if (err) {
                            console.error(err);
                            res.status(500).send({code: 500, msg: 'database error'});
                        }else{
                            res.redirect('/admin/users');
                        }
                    });
                }
            }

看到这个代码是不是感觉想死的心都有了???的确,多少篇文章都说到了关于nodejs的回调地狱,但是Node 7.6 发布了,支持了async函数,JavaScript异步的写法彻底改变了。所以这个大可不必太过于担心 况且Koa不就这么的出来和投入大范围的使用了嘛,这里我们大可用express,去尽情的感受Node的魅力。

结束语

说到这,基本的一个小小后台管理应用就完事了,是不是感觉没有想象中的那么难?写的不好,欢迎大家吐槽指教~~~ 最后,欢迎愿意一起学习nodejs的朋友加入,Nodejs技术群:209530601 ~~

转载于:https://my.oschina.net/Nealyang/blog/846319

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个系统其实是出于学习nodejs的目的而改写的系统。 原来的系统前端使用了extjs4.2.1,后端使用了PHP5.4和ZEND框架开发,后台数据库是用mongodb2.2.2。 我抽离出了原来系统中的账户管理,角色管理,菜单管理,权限管理这4个部分, 我想这4个部分,基本上所有的系统都会用到。具有一定的普遍性。所以将这4个部分用nodejs重新改写了。 该系统目前使用模块有express,ejs,connect-mongo,mongodb,express-partials,connect-flash,fibers,wind等 其实wind模块这次系统中没有使用。可以将它排除出去。我是出于学习wind的目的,才加入这个模块的。 本来准备使用wind模块,是为了实现同步的目的,由于后来改用了fibers模块之后,就没有使用它。这里说明一下 不是fibers要比wind好,而是我暂时不能理解wind,或则是说对wind的研究不够吧。 众所周知nodejs是推崇异步模式。但是这个系统是从php过来的,而php的代码是同步模式的写法,所以为了在改写的过程中 希望 1是代码改动最少 2是同步写法更加适合思维习惯。而且代码可读性高的目的,用到了fibers。 这个系统的源代码中有些js文件里保留了一些原来的PHP代码,这是出于代码对比的目的。 是让大家了解原来的php代码是怎么实现的,用nodejs之后是如何改写的。通过对比,大家会发现 其实通过使用fibers之后,几乎两者是一模一样的。 还有源代码中还保留了一些被注释掉的函数,有些是用到了wind,有些是用到了fibers,有些是直接异步的写法。 这些内容都是在开发过程中我不断尝试后的产物。我花了1周的时间才实现一个递归的调用,而且还是同步的方式。 到目前为止,我还不能理解在异步模式下实现递归调用函数。比如说源代码中有个函数getMenuTree,菜单下面可能有子菜单, 子菜单的下面可能还有菜单。所以是一个递归的过程。我现在是同步的写法实现了这个函数,如果有人能够提供异步写法实现的递归函数并 emai给我,我不胜荣幸。 在使用本系统之前,必须要安装nodejs 0.10.10,mongodb2.2.2,python2.7.5至于安装的方法请googel解决。 将源代码下载之后,解压到某个目录下,比如说d:\nodejs\umav4simple目录。 进入到那个目录, a)运行以下命令 npm install express npm install ejs npm install connect-mongo npm install mongodb npm install express-partials npm install connect-flash npm install fibers npm install wind 尽管在源代码中已包含了这些模块,但是最好还是要重新运行一遍。 因为有些模块可能需要重新的编译。 比如说fibers模块,我在window下运行npm install fibers的时候编译了一个win32-ia32-v8-3.14 而在linux下重新编译了linux-ia32-v8-3.14。所以说根据操作系统的不同,可能会有一些不同。 以免造成想不到的错误。 b)打开settings.js,并且将你的mongodb的设置改写并保存。 c)运行node app.js或则node cluster.js 如果没有提示错误的话,那么就说明环境配置成功了。 d)通过以下的URL可以在mongodb中追加一些数据,不过只能运行一次。否则会重复追加数据。 浏览器上输入 http://localhost:3000/admin/index/install 做完之后, 浏览器上输入http://localhost:3000/ 就通过用户名admin 密码adminadmin进行登录,并使用这个系统了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值