{"id":15479656,"url":"https://github.com/nealyang/ejs-express-mysql","last_synced_at":"2025-04-22T15:22:01.826Z","repository":{"id":89426995,"uuid":"82893405","full_name":"Nealyang/ejs-express-mysql","owner":"Nealyang","description":":sweat_drops: ejs+express+mysql实现基本的CRUD后台管理应用 :sweat_drops:","archived":false,"fork":false,"pushed_at":"2017-02-27T02:54:35.000Z","size":10628,"stargazers_count":91,"open_issues_count":1,"forks_count":26,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-19T04:11:23.103Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Nealyang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-02-23T06:35:48.000Z","updated_at":"2024-02-28T03:12:30.000Z","dependencies_parsed_at":null,"dependency_job_id":"1c5a0117-aea7-4ffd-abbb-604de3d588cd","html_url":"https://github.com/Nealyang/ejs-express-mysql","commit_stats":{"total_commits":16,"total_committers":2,"mean_commits":8.0,"dds":0.0625,"last_synced_commit":"0e1fc42c660794b0ff3e33ffb955c4576b825680"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nealyang%2Fejs-express-mysql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nealyang%2Fejs-express-mysql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nealyang%2Fejs-express-mysql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nealyang%2Fejs-express-mysql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Nealyang","download_url":"https://codeload.github.com/Nealyang/ejs-express-mysql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250264973,"owners_count":21402013,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T04:22:22.347Z","updated_at":"2025-04-22T15:22:01.797Z","avatar_url":"https://github.com/Nealyang.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"ejs-express-mysql\n==\n\n###[基于express，MySQL，ejs实现的一个简单基本的网站后台管理应用](https://github.com/Nealyang/ejs-express-mysql)\n\n##前言\n也是这两周才正式的接触node，虽然在前端开发中我们常常说前后端分离，但是在学习过程中，个人感觉还是要刁难刁难自己的。因为用ejs来写前端页面。\n项目主要实现用户的登录，session的存储和加密（准确的说是签名），数据库的CRUD，包括图片的上传，删除和修改等基本功能。\n关于登录，查询等操作本应该更加的严谨，这里只做简单演示。包括一些配置文件的编写。\n    \n---\n喜欢的朋友方便的话可以给个star  (＾－＾)V\n\n顺便推广一波nodejs技术交流群，群号:209530601\n***\n\n\u003eejs mysql nodejs express express-router...\n\n##效果图\n\n登录页\n![登录页](./resources/login.png)\n\n整体操作流程图\n![操作图](./resources/show.gif)\n\n**GIF Brewery转gif真的有点。。。好吧，不吐槽了，后面会分开讲解每一步，好在基本操作还能看得清~**\n\n\n后台管理首页\n![后台管理首页](./resources/index.png)\n\n博文管理\n![博文管理](./resources/blog.png)\n\n用户管理页\n![用户管理](./resources/user.png)\n\n操作\n![操作](./resources/blog_o.png)\n\n\n##开发准备\n关于开发前期的准备，这里就不多说了，说实话，自己也没有准备啥，关于nodejs环境，MySQL配置啥的就多少了，关于本项目的[数据字典](./数据字典.txt)，还有[SQL文件](./resources/blog.sql)已经在目录里了，这里主要说下后端开发的每一个步骤\n##项目目录\n项目目录\n![项目目录](./resources/project_str.png)\n##整体架构\n项目重点在后端开发中，web端页面并没有涉及到，后端管理流程大致如下:\n* 路由控制分为admin，web，还是那句话，我们操作全部在admin中\n* 跳转到admin拦截所有的请求，判断用户是否登录\n* 未登录则重定向到登录，登陆成功后设置session。[不懂session？点击这里](https://my.oschina.net/Nealyang/blog/844049)\n* 登录后则可进行相关的操作，数据的增删改查等功能。\n##后端开发\n###后台基本架构、路由设置\n        const express = require('express');\n        const expressStatic = require('express-static');\n        const bodyParser = require('body-parser');\n        const multer = require('multer');\n        const multerObj = multer({dest:'./static/upload'});\n        const cookieParser = require('cookie-parser');\n        const cookieSession = require('cookie-session');\n        const consolidate = require('consolidate');\n        const ejs = require('ejs');\n        \n        //创建服务器\n        var server = express();\n        server.listen(8080);\n        \n        //解析请求数据\n        \n        server.use(bodyParser({\n            extended:false\n        }));\n        server.use(multerObj.any());\n        \n        //设置cookie，session\n        server.use(cookieParser('Neal_signed'));\n        (function () {\n            var arr = [];\n            for(var i = 0;i\u003c10000;i++){\n                arr.push('keys_'+Math.random());\n            }\n            server.use(cookieSession({\n                name:'session_id',\n                keys:arr,\n                maxAge:20*60*1000//一般我会设置20分钟，这里是为了感受session过期~~带来的快感~?(●´∀｀●)ﾉ\n            }))\n        })();\n        \n        //设置模板\n        server.set('view engine','html');\n        server.set('views','./views');\n        server.engine('html',consolidate.ejs);\n        //设置路由\n        server.use('/admin',require('./router/admin/index')());\n        server.use('/',require('./router/web/index')());\n        \n        \n        //静态文件的请求\n        server.use('/files',expressStatic('./static'));\n我的基本架构如下，关于每一部分的功能，都已经标注。关于路由的控制在admin/index.js跟server.js大同小异，我想大家也都应该知道了。\n###登录功能\n登录功能这里主要说两点\n* 密码的md5签名（当然，大多数人说是md5加密）\n* session的应用\n在lib中存放着自己写的一些方法，作为一个库，admin初始化有三个用户，包括u/p：root,neal,Nealyang\n关于密码的签名方法主要如下：\n        var crypto = require('crypto');\n        \n        module.exports = {\n            MD5_SUFFIX : 'JDSAIOEUQOIoieuoiqv#$%^\u0026dhfja)(* %^\u0026FGHJfyuieyfhfhak(^.^)YYa!!\\(^o^)/Y(^o^)Y(*^__^*)ﾍ|･∀･|ﾉ*~●',\n            md5:function (pwd) {\n                var md5 = crypto.createHash('md5');\n                return md5.update(pwd).digest('hex');\n            }\n        };\nMD5_SUFFIX是加密字符串，用法如路由login.js：\n\n        \n            router.post('/',function (req,res) {\n                var username = req.body.username;\n                var password = common.md5(req.body.password+common.MD5_SUFFIX);\n                if(username \u0026\u0026 password){\n                    db.query('SELECT * FROM admin_table WHERE username=\"'+username+'\"',function (err,userData) {\n                        if(err){\n                            console.error(err);\n                            res.status(500).send({code:500,data:[],msg:'database error'});\n                        }else if(userData.length == 0){\n                            res.status(400).send({code:400,data:[],msg:'parameters error'});\n                        }else{\n                            if(userData[0].password != password){\n                                res.status(400).send({code:400,data:[],msg:'username or password error'});\n                            }else{\n                                req.session['user_id'] = userData[0].ID;//注意这里是在req上面\n                                res.status(200).send({code:200,data:[],msg:'success'});\n                            }\n                        }\n                    })\n                }else{\n                    res.status(400).send({code:400,data:[],msg:'parameters error'});\n                }\n            });\n从上面的代码中也展现了什么时候设置session，并且值得提一下的是这里提供给前端页面的是接口，这样的话很多逻辑都放到了前端，后面我们都是通过页面渲染来输出的了。下面是所有请求的拦截判断：\n\n        router.use(function (req,res,next) {\n                if(!req.session['user_id'] \u0026\u0026 req.url != '/login'){\n                    res.redirect('/admin/login');\n                }else{\n                    next();\n                }\n            });\n\n##ejs前端页面的重点代码讲解\n公共头部的引入：\n\n        \u003c% include common/top.ejs %\u003e\n        \n查询数据库的前端展示：\n\n        \u003c% for(var i = 0;i\u003cformData.length;i++){%\u003e\n            \u003ctr\u003e\n                \u003ctd\u003e\u003c%=formData[i].ID%\u003e\u003c/td\u003e\n                \u003ctd\u003e\u003c%=formData[i].title%\u003e\u003c/td\u003e\n                \u003ctd\u003e\u003c%=formData[i].author%\u003e\u003c/td\u003e\n                \u003ctd\u003e\u003c%=formData[i].summary%\u003e\u003c/td\u003e\n                \u003ctd\u003e\u003c%=formData[i].href%\u003e\u003c/td\u003e\n                \u003ctd\u003e\n                    \u003ca href=\"?action=del\u0026id=\u003c%=formData[i].ID%\u003e\" onclick=\"return confirm('确定删除？')\"\u003e\n                        \u003cbutton\u003e删除\u003c/button\u003e\n                    \u003c/a\u003e\n                    \u003ca href=\"?action=mod\u0026id=\u003c%=formData[i].ID%\u003e\"\u003e\n                        \u003cbutton\u003e修改\u003c/button\u003e\n                    \u003c/a\u003e\n                \u003c/td\u003e\n            \u003c/tr\u003e\n            \u003c%}%\u003e\n\n修改后我们通过页面给的标识来知道是否为修改的提交，毕竟这里我们没有前端逻辑的js\n\n        \u003c%if(typeof modData != 'undefined'){%\u003e\n        \u003cdiv class=\"add_form\" style=\"display: flex\"\u003e\n            \u003cimg src=\"/files/admin/img/add.png\"\u003e\n            \u003cdiv class=\"form_bg\"\u003e\u003c/div\u003e\n            \u003cform action=\"?\" method=\"post\"\u003e\n                \u003cinput type=\"hidden\" name=\"modified\" value=\"\u003c%= modData[0].ID %\u003e\"\u003e\n                标题: \u003cinput type=\"text\" name=\"title\" autofocus=\"autofocus\" value=\"\u003c%=modData[0].title%\u003e\"\u003e\u003cbr\u003e\n                作者: \u003cinput type=\"text\" name=\"author\" value=\"\u003c%=modData[0].author%\u003e\"\u003e\u003cbr\u003e\n                摘要: \u003ctextarea name=\"summary\"\u003e\u003c%=modData[0].summary%\u003e\u003c/textarea\u003e\u003cbr\u003e\n                链接: \u003cinput type=\"text\" name=\"href\" value=\"\u003c%=modData[0].href%\u003e\"\u003e\u003cbr\u003e\n                \u003cinput type=\"submit\" value=\"确认修改\"\u003e\n            \u003c/form\u003e\n        \u003c/div\u003e\n        \u003c%}%\u003e\n\n##博文管理\n还是那句话，如果是仅仅提供前端的接口的话，这里会方便很多，然后我们用的是ejs，所以很多的逻辑都放在了后端，在get/post到请求的时候需要做很多判断\nget方法请求如下：\n\n        router.get('/', function (req, res) {\n                switch (req.query.action) {\n                    case 'del':\n                        //删除操作\n                        db.query('DELETE FROM blog_list_table WHERE id=\"'+req.query.id+'\"',function (err,resultData) {\n                            if(err){\n                                console.error(err);\n                                res.status(500).send({code:500,msg:'database error'});\n                            }else{\n                                res.redirect('/admin/blog');\n                            }\n                        });\n                        break;\n                    case 'mod':\n                        //修改操作\n                        db.query('SELECT * FROM blog_list_table WHERE id=\"'+req.query.id+'\"',function (err,modData) {\n                            if(err){\n                                console.error(err);\n                                res.status(500).send({code:500,msg:'database error'});\n                            }else if(modData.length == 0){\n                                res.status(400).send({code:400,msg:'parameters error'});\n                            }else{\n                                db.query('SELECT * FROM blog_list_table',function (err,allData) {\n                                    if(err){\n                                        console.error(err);\n                                        res.status(500).send({code:500,msg:'database error'});\n                                    }else{\n                                        res.render('admin/blog.ejs',{formData:allData,modData:modData});\n                                    }\n                                });\n                            }\n                        });\n                        break;\n                    default:\n                        db.query('SELECT * FROM blog_list_table', function (err, resultData) {\n                            if (err) {\n                                console.error(err);\n                                res.status(500).send({code: 500, msg: 'database error'}).end();\n                            } else {\n                                res.render('admin/blog.ejs', {formData: resultData});\n                            }\n                        });\n                }\n        \n            });\n这里的switch，主要是分为，查询，删除，和修改\n都是些简单的CRUD操作，这里就不多细说了。不熟悉的兄弟们可以看一看，写的不好，多多提意见。\n\n同理，post的请求，主要就是分为，文章列表的添加，和修改的两个post，代码如下：\n\n        router.post('/', function (req, res) {\n                //此处验证应该更加严格，比如正则\n                var title = req.body.title.trim();\n                var author = req.body.author.trim();\n                var summary = req.body.summary.trim();\n                var href = req.body.href.trim();\n        \n                if (title \u0026\u0026 author \u0026\u0026 summary \u0026\u0026 href) {\n                    if(req.body.modified){\n                        db.query('UPDATE blog_list_table SET title=\"'+title+'\",author=\"'+author+'\",summary=\"'+summary+'\",href=\"'+href+'\" WHERE ID=\"'+req.body.modified+'\"',function (err,resultData) {\n                            if(err){\n                                console.error(err);\n                                res.status(500).send({code:500,msg:'database error'});\n                            }else{\n                                res.redirect('/admin/blog');\n                            }\n                        })\n                    }else{\n                        db.query('INSERT INTO blog_list_table (title,author,summary,href) VALUE(\"' + title + '\",\"' + author + '\",\"' + summary + '\",\"' + href + '\")', function (err, data) {\n                            if (err) {\n                                console.error(err);\n                                res.status(500).send({code: 500, msg: 'database error'}).end();\n                            } else {\n                                res.redirect('/admin/blog');\n                            }\n                        });\n                    }\n                } else {\n                    res.status(400).send({code: 400, msg: 'parameters error'}).end();\n                }\n        \n            });\n\n\n##用户管理\n后台的管理大概也即是这么多，用户管理和博文管理基本都是差不多的，这里重点是说下，这里用到的图片上传。\n图片上传我用的是**multer**中间件，不知道的可以查下，注意用这个中间件接受图片上传时form表单的**enctype**必须要设置为multipart/form-data\n\n关于图片上传后，默认是不包括后缀名的，所以这里我们需要用到fs模块的重命名操作，代码如下：\n\n        fs.rename(req.files[0].path, req.files[0].path + ext, function (err) {\n                        if (err) {\n                            console.error(err);\n                            res.status(500).send({code: 500, msg: 'data error'});\n                        } else {\n                            db.query('INSERT INTO user_table (username,email,pic_header) VALUE(\"' + username + '\",\"' +\n                                email + '\",\"' + pic_header + '\")', function (err, resultData) {\n                                if (err) {\n                                    console.error(err);\n                                    res.status(500).send({code: 500, msg: 'database error'});\n                                } else {\n                                    res.redirect('/admin/users');\n                                }\n                            });\n                        }\n                    });\n前面的变量定义主要如下：\n\n        var username = req.body.username;\n                var email = req.body.email;\n                if(req.files.length\u003e0){\n                    var ext = pathLib.parse(req.files[0].originalname).ext;\n                    var pic_header = '/files/upload/' + req.files[0].filename + ext;\n                }\n用到path模块对路径的解析。为了获取后缀名~\n\n说到这，对于修改也就很简单了，就是删除原先有的那个图片，然后换上现在有的图片，具体代码如下：\n\n        //需要进行一些校验，这里就忽略了\n                if(req.body.modified){//修改\n                    //查看有没有新传来的头像，如果有，则删除，新建，如果没有，直接更新需要更新的内容\n                    if(req.files.length\u003e0){\n                        //有修改头像，则进行原来头像的删除，再上传\n                        db.query('SELECT * FROM user_table WHERE ID=\"'+req.body.modified+'\"',function (err,modData) {\n                            if (err) {\n                                console.error(err);\n                                res.status(500).send({code: 500, msg: 'database error'});\n                            }else if(modData.length == 0){\n                                res.status(400).send({code: 400, msg: 'parameters error'});\n                            }else{\n                                fs.unlink(modData[0].pic_header.replace('\\/files','static'),function (err) {\n                                    if(err){\n                                        console.error(err);\n                                        res.status(500).send({code:500,msg:'operate error'});\n                                    }else{\n                                        //删除成功，开始对新的文件进行重命名\n                                        fs.rename(req.files[0].path, req.files[0].path + ext, function (err) {\n                                            if (err) {\n                                                console.error(err);\n                                                res.status(500).send({code: 500, msg: 'operate error'});\n                                            } else {\n                                                db.query('UPDATE user_table SET username=\"'+\n                                                    username+'\",email=\"' + email + '\",pic_header=\"' +\n                                                    pic_header + '\" WHERE ID=\"'+req.body.modified+'\"',function (err,data) {\n                                                    if (err) {\n                                                        console.error(err);\n                                                        res.status(500).send({code: 500, msg: 'database error'});\n                                                    }else{\n                                                        res.redirect('/admin/users');\n                                                    }\n                                                });\n                                            }\n                                        });\n                                    }\n                                })\n                            }\n                        })\n                    }else{\n                        db.query('UPDATE user_table SET username=\"'+username+'\",email=\"' + email + '\" WHERE ID=\"'+\n                            req.body.modified+'\"',function (err,data) {\n                            if (err) {\n                                console.error(err);\n                                res.status(500).send({code: 500, msg: 'database error'});\n                            }else{\n                                res.redirect('/admin/users');\n                            }\n                        });\n                    }\n                }\n\n看到这个代码是不是感觉想死的心都有了？？？的确，多少篇文章都说到了关于nodejs的回调地狱，但是Node 7.6 发布了，支持了async函数，JavaScript异步的写法彻底改变了。所以这个大可不必太过于担心\n况且Koa不就这么的出来和投入大范围的使用了嘛，这里我们大可用express，去尽情的感受Node的魅力。\n\n##结束语\n说到这，基本的一个小小后台管理应用就完事了，是不是感觉没有想象中的那么难？写的不好，欢迎大家吐槽指教~~~\n最后，欢迎愿意一起学习nodejs的朋友加入，Nodejs技术群:209530601\n~~","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnealyang%2Fejs-express-mysql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnealyang%2Fejs-express-mysql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnealyang%2Fejs-express-mysql/lists"}