├── .gitignore ├── resources ├── blog.png ├── show.gif ├── user.png ├── blog_o.png ├── index.png ├── login.png ├── project_str.png └── blog.sql ├── static ├── admin │ ├── img │ │ ├── add.png │ │ ├── sign_bg.png │ │ ├── login_bg.png │ │ ├── user_icon.png │ │ ├── login_button.png │ │ └── password_icon.png │ ├── js │ │ ├── blog.js │ │ ├── users.js │ │ ├── login.js │ │ └── top.js │ └── css │ │ ├── index.css │ │ ├── login.css │ │ ├── top.css │ │ ├── blog.css │ │ ├── users.css │ │ └── reset.css └── upload │ ├── 68efe455c4ecda9517d644751c749849.jpg │ ├── 872ce9c96a353d082f20f20575af158f.jpg │ └── c630494b49e1e54a713f2e4f275edd2e.jpg ├── .idea └── vcs.xml ├── router ├── web │ └── index.js └── admin │ ├── index.js │ ├── login.js │ ├── blog.js │ └── users.js ├── lib └── common.js ├── 数据字典.txt ├── views └── admin │ ├── index.ejs │ ├── common │ └── top.ejs │ ├── login.ejs │ ├── users.ejs │ └── blog.ejs ├── package.json ├── server.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ -------------------------------------------------------------------------------- /resources/blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/resources/blog.png -------------------------------------------------------------------------------- /resources/show.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/resources/show.gif -------------------------------------------------------------------------------- /resources/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/resources/user.png -------------------------------------------------------------------------------- /resources/blog_o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/resources/blog_o.png -------------------------------------------------------------------------------- /resources/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/resources/index.png -------------------------------------------------------------------------------- /resources/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/resources/login.png -------------------------------------------------------------------------------- /resources/project_str.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/resources/project_str.png -------------------------------------------------------------------------------- /static/admin/img/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/static/admin/img/add.png -------------------------------------------------------------------------------- /static/admin/img/sign_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/static/admin/img/sign_bg.png -------------------------------------------------------------------------------- /static/admin/img/login_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/static/admin/img/login_bg.png -------------------------------------------------------------------------------- /static/admin/img/user_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/static/admin/img/user_icon.png -------------------------------------------------------------------------------- /static/admin/img/login_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/static/admin/img/login_button.png -------------------------------------------------------------------------------- /static/admin/img/password_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/static/admin/img/password_icon.png -------------------------------------------------------------------------------- /static/upload/68efe455c4ecda9517d644751c749849.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/static/upload/68efe455c4ecda9517d644751c749849.jpg -------------------------------------------------------------------------------- /static/upload/872ce9c96a353d082f20f20575af158f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/static/upload/872ce9c96a353d082f20f20575af158f.jpg -------------------------------------------------------------------------------- /static/upload/c630494b49e1e54a713f2e4f275edd2e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/ejs-express-mysql/HEAD/static/upload/c630494b49e1e54a713f2e4f275edd2e.jpg -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /router/web/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/25. 3 | */ 4 | const express = require('express'); 5 | 6 | module.exports = function () { 7 | var router = express.Router(); 8 | router.get('/',function (req,res) { 9 | res.send('这是前端页面'); 10 | }); 11 | return router; 12 | }; -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/25. 3 | * 自己的库 4 | */ 5 | var crypto = require('crypto'); 6 | 7 | module.exports = { 8 | MD5_SUFFIX : 'JDSAIOEUQOIoieuoiqv#$%^&dhfja)(* %^&FGHJfyuieyfhfhak(^.^)YYa!!\(^o^)/Y(^o^)Y(*^__^*)ヘ|・∀・|ノ*~●', 9 | md5:function (pwd) { 10 | var md5 = crypto.createHash('md5'); 11 | return md5.update(pwd).digest('hex'); 12 | } 13 | }; -------------------------------------------------------------------------------- /数据字典.txt: -------------------------------------------------------------------------------- 1 | 1、管理员 admin_table 2 | ID int 3 | username varchar(32) 4 | password varchar(32) 5 | 6 | 2、用户 user_table 7 | ID int 8 | username varchar(32) 9 | email varchar(32) 10 | pic_header varchar(300) 11 | 12 | 3、博客 blog_table 13 | ID int 14 | title varchar(200) 15 | summary varchar(500) 16 | author varchar(32) 17 | href varchar(300) -------------------------------------------------------------------------------- /views/admin/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台管理-首页 6 | 7 | 8 | <% include common/top.ejs%> 9 |
10 |

11 | 后台管理主要实现的是:博客的删除修改和新建(涉及到数据的CRUD),以及用户管理(涉及到图片上传). 12 | 对于前后端分离,前面登录就已经给出了实例~ 13 |

14 |
15 |

16 | 喜欢的朋友可以star下,初涉Node,深知代码烂如麻,求指点,求轻喷~ 17 |

18 |
19 |

20 | Nodejs技术交流群:209530601 21 |

22 |
23 | 24 | -------------------------------------------------------------------------------- /router/admin/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/25. 3 | */ 4 | const express = require('express'); 5 | 6 | module.exports = function () { 7 | var router = express.Router(); 8 | router.use(function (req,res,next) { 9 | if(!req.session['user_id'] && req.url != '/login'){ 10 | res.redirect('/admin/login'); 11 | }else{ 12 | next(); 13 | } 14 | }); 15 | router.use('/login',require('./login')()); 16 | router.use('/blog',require('./blog')()); 17 | router.use('/users',require('./users')()); 18 | router.get('/',function (req,res) { 19 | res.render('admin/index.ejs'); 20 | }); 21 | return router; 22 | }; -------------------------------------------------------------------------------- /views/admin/common/top.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台管理-首页 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 | Nealyang后台管理应用 15 |

16 | 27 |
28 | 29 | -------------------------------------------------------------------------------- /views/admin/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台管理员登录 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /static/admin/js/blog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/24. 3 | */ 4 | $(document).ready(function () { 5 | var add_button = $('.add_button'); 6 | var scroll_img = $('.add_button img'); 7 | var add_form_container = $('.add_form'); 8 | add_button.mouseenter(function () { 9 | $(this).css({ 10 | 'left':'0' 11 | }); 12 | scroll_img.css({ 13 | 'right':'30px', 14 | 'transform':'rotate(-90deg)' 15 | }); 16 | }); 17 | 18 | add_button.mouseleave(function () { 19 | $(this).css({ 20 | 'left':'-50px' 21 | }); 22 | scroll_img.css({ 23 | 'right':'0', 24 | 'transform':'rotate(0deg)' 25 | }); 26 | }); 27 | 28 | add_button.click(function () { 29 | add_form_container.css({ 30 | 'display':'flex' 31 | }); 32 | }); 33 | $('.add_form img').click(function () { 34 | add_form_container.css({ 35 | 'display':'none' 36 | }); 37 | }) 38 | 39 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ejs-express-mysql", 3 | "version": "1.0.0", 4 | "description": "ejs+express+mysql实现的后台管理", 5 | "main": "server", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Nealyang/ejs-express-mysql.git" 12 | }, 13 | "keywords": [ 14 | "node", 15 | "express", 16 | "ejs", 17 | "mysql" 18 | ], 19 | "author": "Nealyang", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/Nealyang/ejs-express-mysql/issues" 23 | }, 24 | "homepage": "https://github.com/Nealyang/ejs-express-mysql#readme", 25 | "devDependencies": { 26 | "body-parser": "^1.16.1", 27 | "consolidate": "^0.14.5", 28 | "cookie-parser": "^1.4.3", 29 | "cookie-session": "^2.0.0-beta.1", 30 | "ejs": "^2.5.6", 31 | "express": "^4.14.1", 32 | "express-router": "0.0.1", 33 | "express-static": "^1.0.3", 34 | "multer": "^1.3.0", 35 | "mysql": "^2.13.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /static/admin/js/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/25. 3 | */ 4 | /** 5 | * Created by Nealyang on 17/2/24. 6 | */ 7 | $(document).ready(function () { 8 | var add_button = $('.add_button'); 9 | var scroll_img = $('.add_button img'); 10 | var add_form_container = $('.add_form'); 11 | add_button.mouseenter(function () { 12 | $(this).css({ 13 | 'left':'0' 14 | }); 15 | scroll_img.css({ 16 | 'right':'30px', 17 | 'transform':'rotate(-90deg)' 18 | }); 19 | }); 20 | 21 | add_button.mouseleave(function () { 22 | $(this).css({ 23 | 'left':'-50px' 24 | }); 25 | scroll_img.css({ 26 | 'right':'0', 27 | 'transform':'rotate(0deg)' 28 | }); 29 | }); 30 | 31 | add_button.click(function () { 32 | add_form_container.css({ 33 | 'display':'flex' 34 | }); 35 | }); 36 | $('.add_form img').click(function () { 37 | add_form_container.css({ 38 | 'display':'none' 39 | }); 40 | }) 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /static/admin/js/login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/25. 3 | */ 4 | $(document).ready(function () { 5 | //设置一屏的宽高 6 | $('body').css({ 7 | 'height': $(window).height() + 'px', 8 | 'width': $(window).width() + 'px' 9 | }); 10 | // 点击登录 11 | $('.login_button').click(function () { 12 | var username = $('.username_div input').val(), 13 | password = $('.password_div input').val(); 14 | if (username && password) { 15 | $.ajax({ 16 | type: 'POST', 17 | url: '/admin/login', 18 | data: { 19 | username: username, 20 | password: password 21 | }, 22 | success: function (response) { 23 | window.location.replace('/admin'); 24 | }, 25 | error: function (response) { 26 | if (JSON.parse(response.responseText).code == 500) { 27 | alert('服务器内部错误!'); 28 | } else { 29 | alert('用户名或密码错误'); 30 | } 31 | } 32 | }) 33 | } else { 34 | confirm('用户名和密码为必填项~'); 35 | } 36 | 37 | }); 38 | }); -------------------------------------------------------------------------------- /static/admin/js/top.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/24. 3 | */ 4 | $(document).ready(function () { 5 | var title = '首页'; 6 | if(window.location.pathname.indexOf('blog') >0 ){ 7 | title = '博文管理'; 8 | $('.menu span:contains("博文管理")').css({ 9 | 'bottom':'5px', 10 | 'box-shadow' :'4px 9px 10px #020202' 11 | }); 12 | }else if(window.location.pathname.indexOf('users') >0 ){ 13 | $('.menu span:contains("用户管理")').css({ 14 | 'bottom':'5px', 15 | 'box-shadow' :'4px 9px 10px #020202' 16 | }); 17 | title = '用户管理' 18 | }else{ 19 | $('.menu span:contains("首页")').css({ 20 | 'bottom':'5px', 21 | 'box-shadow' :'4px 9px 10px #020202' 22 | }); 23 | } 24 | $('.menu span').mouseenter(function () { 25 | $(this).css({ 26 | 'bottom':'5px', 27 | 'box-shadow' :'4px 9px 10px #020202' 28 | }); 29 | }); 30 | $('.menu span').mouseleave(function () { 31 | if($.trim($(this).text())!= title){ 32 | $(this).css({ 33 | 'bottom':'0', 34 | 'box-shadow' :'2px 2px 2px #6d5d5d' 35 | }); 36 | } 37 | 38 | }); 39 | 40 | }); -------------------------------------------------------------------------------- /static/admin/css/index.css: -------------------------------------------------------------------------------- 1 | .header{ 2 | width: 100%; 3 | height: 120px; 4 | background: url("/admin/img/login_bg.png") no-repeat center; 5 | background-size: 100%; 6 | display: flex; 7 | align-items: center; 8 | color: #fff; 9 | } 10 | .header p{ 11 | font-size: 32px; 12 | font-family: fantasy; 13 | margin-left: 5%; 14 | } 15 | .menu{ 16 | height: 80px; 17 | font-family: fantasy; 18 | display: flex; 19 | align-items: center; 20 | justify-content: space-around; 21 | width: 500px; 22 | margin-left: 30%; 23 | } 24 | 25 | .menu span{ 26 | font-size: 25px; 27 | display: inline-block; 28 | text-align: center; 29 | box-shadow: 2px 2px 2px #6d5d5d; 30 | background:-moz-linear-gradient(left,#a1655a,#2e4a53);/*火狐*/ 31 | background:-webkit-gradient(linear, 0% 0%, 0% 100%,from(#b8c4cb), to(#f6f6f8));/*谷歌*/ 32 | background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#0000ff)); /* Safari 4-5, Chrome 1-9*/ 33 | background: -webkit-linear-gradient(left, #a1655a, #2e4a53); /*Safari5.1 Chrome 10+*/ 34 | background: -o-linear-gradient(left, #a1655a, #2e4a53); /*Opera 11.10+*/ 35 | height: 40px; 36 | line-height: 46px; 37 | padding:0px 20px; 38 | -webkit-border-radius:5px; 39 | -moz-border-radius:5px; 40 | border-radius:5px; 41 | cursor: pointer; 42 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/25. 3 | */ 4 | 5 | const express = require('express'); 6 | const expressStatic = require('express-static'); 7 | const bodyParser = require('body-parser'); 8 | const multer = require('multer'); 9 | const multerObj = multer({dest:'./static/upload'}); 10 | const cookieParser = require('cookie-parser'); 11 | const cookieSession = require('cookie-session'); 12 | const consolidate = require('consolidate'); 13 | const ejs = require('ejs'); 14 | 15 | //创建服务器 16 | var server = express(); 17 | server.listen(8080); 18 | 19 | //解析请求数据 20 | 21 | server.use(bodyParser({ 22 | extended:false 23 | })); 24 | server.use(multerObj.any()); 25 | 26 | //设置cookie,session 27 | server.use(cookieParser('Neal_signed')); 28 | (function () { 29 | var arr = []; 30 | for(var i = 0;i<10000;i++){ 31 | arr.push('keys_'+Math.random()); 32 | } 33 | server.use(cookieSession({ 34 | name:'session_id', 35 | keys:arr, 36 | maxAge:20*60*1000//一般我会设置20分钟,这里是为了感受session过期~~带来的快感~?(●´∀`●)ノ 37 | })) 38 | })(); 39 | 40 | //设置模板 41 | server.set('view engine','html'); 42 | server.set('views','./views'); 43 | server.engine('html',consolidate.ejs); 44 | //设置路由 45 | server.use('/admin',require('./router/admin/index')()); 46 | server.use('/',require('./router/web/index')()); 47 | 48 | 49 | //静态文件的请求 50 | server.use('/files',expressStatic('./static')); 51 | -------------------------------------------------------------------------------- /static/admin/css/login.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url("/files/admin/img/login_bg.png") no-repeat center 0; 3 | background-size: 100% 100%; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | } 8 | 9 | .login_container { 10 | width: 475px; 11 | height: 459px; 12 | background: url("/files/admin/img/sign_bg.png") no-repeat center 0; 13 | background-size: 100% 100%; 14 | -webkit-animation:inner 1s; 15 | -o-animation:inner 1s; 16 | animation:inner 1s; 17 | } 18 | 19 | @keyframes inner { 20 | 0% { 21 | width: 0; 22 | height: 0; 23 | opacity: 0; 24 | } 25 | 100%{ 26 | width: 475px; 27 | height: 459px; 28 | opacity: 1; 29 | } 30 | } 31 | 32 | .login_input_container { 33 | width: 75%; 34 | height: 13%; 35 | border: solid 2px rgba(80, 75, 75, 0.8); 36 | box-shadow: 1px 1px 9px #544e4e inset; 37 | border-radius: 7px; 38 | display: flex; 39 | align-items: center; 40 | } 41 | 42 | .username_div { 43 | margin: 27% auto 0; 44 | } 45 | 46 | .login_input_container img { 47 | width: 10%; 48 | height: auto; 49 | margin-left: 5%; 50 | } 51 | 52 | .login_input_container input { 53 | border: none; 54 | background: transparent; 55 | height: 68%; 56 | width: 80%; 57 | margin-left: 3%; 58 | font-size: 25px; 59 | color: #fff; 60 | } 61 | 62 | .password_div { 63 | margin: 10% auto 0; 64 | } 65 | 66 | .login_button { 67 | background: url(/files/admin/img/login_button.png) no-repeat center 0; 68 | height: 13%; 69 | width: 63%; 70 | background-size: 100% 100%; 71 | margin: 8% auto 0; 72 | 73 | } -------------------------------------------------------------------------------- /router/admin/login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/24. 3 | */ 4 | const express = require('express'); 5 | const mysql = require('mysql'); 6 | const common = require('../../lib/common'); 7 | 8 | const db = mysql.createPool({host:'localhost',port:3306,user:'root',password:' neal',database:'blog'}); 9 | 10 | module.exports = function () { 11 | var router = express.Router(); 12 | router.get('/',function (req,res) { 13 | res.render('admin/login.ejs'); 14 | }); 15 | router.post('/',function (req,res) { 16 | var username = req.body.username; 17 | var password = common.md5(req.body.password+common.MD5_SUFFIX); 18 | if(username && password){ 19 | db.query('SELECT * FROM admin_table WHERE username="'+username+'"',function (err,userData) { 20 | if(err){ 21 | console.error(err); 22 | res.status(500).send({code:500,data:[],msg:'database error'}); 23 | }else if(userData.length == 0){ 24 | res.status(400).send({code:400,data:[],msg:'parameters error'}); 25 | }else{ 26 | if(userData[0].password != password){ 27 | res.status(400).send({code:400,data:[],msg:'username or password error'}); 28 | }else{ 29 | req.session['user_id'] = userData[0].ID;//注意这里是在req上面 30 | res.status(200).send({code:200,data:[],msg:'success'}); 31 | } 32 | } 33 | }) 34 | }else{ 35 | res.status(400).send({code:400,data:[],msg:'parameters error'}); 36 | } 37 | }); 38 | return router; 39 | }; 40 | -------------------------------------------------------------------------------- /static/admin/css/top.css: -------------------------------------------------------------------------------- 1 | .header{ 2 | width: 100%; 3 | height: 120px; 4 | background: url("/files/admin/img/login_bg.png") no-repeat center; 5 | background-size: 100%; 6 | display: flex; 7 | align-items: center; 8 | color: #fff; 9 | } 10 | .header p{ 11 | font-size: 32px; 12 | font-family: fantasy; 13 | margin-left: 5%; 14 | } 15 | .menu{ 16 | height: 80px; 17 | font-family: fantasy; 18 | display: flex; 19 | align-items: center; 20 | justify-content: space-around; 21 | width: 500px; 22 | margin-left: 30%; 23 | } 24 | 25 | .menu span{ 26 | color: #fff; 27 | font-size: 25px; 28 | display: inline-block; 29 | text-align: center; 30 | box-shadow: 2px 2px 2px #6d5d5d; 31 | background:-moz-linear-gradient(left,#a1655a,#2e4a53);/*火狐*/ 32 | background:-webkit-gradient(linear, 0% 0%, 0% 100%,from(#b8c4cb), to(#f6f6f8));/*谷歌*/ 33 | background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#0000ff)); /* Safari 4-5, Chrome 1-9*/ 34 | background: -webkit-linear-gradient(left, #a1655a, #2e4a53); /*Safari5.1 Chrome 10+*/ 35 | background: -o-linear-gradient(left, #a1655a, #2e4a53); /*Opera 11.10+*/ 36 | height: 40px; 37 | line-height: 46px; 38 | padding:0px 20px; 39 | -webkit-border-radius:5px; 40 | -moz-border-radius:5px; 41 | border-radius:5px; 42 | cursor: pointer; 43 | position: relative; 44 | bottom:0; 45 | -webkit-transition: bottom .3s; 46 | -moz-transition: bottom .3s; 47 | -ms-transition: bottom .3s; 48 | -o-transition: bottom .3s; 49 | transition: bottom .3s,box-shadow .3s; 50 | } 51 | .on_select{ 52 | box-shadow: 4px 9px 10px #020202; 53 | } -------------------------------------------------------------------------------- /views/admin/users.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台管理-用户管理 6 | 7 | 8 | 9 | 10 | 11 | <% include common/top.ejs%> 12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 用户名:
20 | 邮箱:
21 | 头像:
22 | 23 |
24 |
25 | <%if(typeof modData != 'undefined'){%> 26 |
27 | 28 |
29 |
30 | 31 | 用户名:
32 | 邮箱:
33 | 头像:
34 | 35 |
36 |
37 | <%}%> 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | <% for(var i = 0;i 47 | 48 | 49 | 50 | 51 | 52 | 60 | 61 | <%}%> 62 |
ID名字邮箱头像操作
<%=usersData[i].ID%><%=usersData[i].username%><%=usersData[i].email%> 53 | 54 | 55 | 56 | 57 | 58 | 59 |
63 | 64 | -------------------------------------------------------------------------------- /static/admin/css/blog.css: -------------------------------------------------------------------------------- 1 | .add_button{ 2 | height: 50px; 3 | width: 100px; 4 | background:-moz-linear-gradient(left,#a1655a,#2e4a53);/*火狐*/ 5 | background:-webkit-gradient(linear, 0% 0%, 0% 100%,from(#b8c4cb), to(#f6f6f8));/*谷歌*/ 6 | background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#0000ff)); /* Safari 4-5, Chrome 1-9*/ 7 | background: -webkit-linear-gradient(left, #a1655a, #2e4a53); /*Safari5.1 Chrome 10+*/ 8 | background: -o-linear-gradient(left, #a1655a, #2e4a53); /*Opera 11.10+*/ 9 | border-top-right-radius: 25px; 10 | border-bottom-right-radius: 25px; 11 | display: flex; 12 | align-items: center; 13 | flex-direction: row-reverse; 14 | position: fixed; 15 | left:-50px; 16 | top:300px; 17 | cursor: pointer; 18 | -webkit-transition: left .5s; 19 | -moz-transition: left .5s; 20 | -ms-transition: left .5s; 21 | -o-transition: left .5s; 22 | transition: left .5s; 23 | } 24 | .add_button img{ 25 | width: 50%; 26 | transform: rotate(0deg); 27 | position: relative; 28 | right:0; 29 | -webkit-transition: right .5s,transform,.5s; 30 | -moz-transition: right .5s,transform,.5s; 31 | -ms-transition: right .5s,transform,.5s; 32 | -o-transition: right .5s,transform,.5s; 33 | transition: right .5s,transform,.5s; 34 | } 35 | .form_bg{ 36 | background-color: #000; 37 | opacity: .5; 38 | height: 100%; 39 | width: 100%; 40 | position: absolute; 41 | top:0; 42 | } 43 | .add_form{ 44 | justify-content: center; 45 | display: none; 46 | } 47 | .add_form img{ 48 | width: 50px; 49 | height: auto; 50 | position: fixed; 51 | z-index: 2; 52 | transform: rotate(45deg); 53 | right: 37%; 54 | margin-top: 33px; 55 | } 56 | .add_form form{ 57 | position: fixed; 58 | z-index: 2; 59 | text-align: center; 60 | color: #fff; 61 | font-size: 20px; 62 | margin-top: 100px; 63 | line-height: 55px; 64 | } 65 | table{ 66 | width: 100%; 67 | } 68 | table .id{ 69 | width: 7%; 70 | } 71 | table .title{ 72 | width: 25%; 73 | } 74 | table .author{ 75 | width: 8%; 76 | } 77 | table .summary{ 78 | width: 30%; 79 | 80 | } 81 | table .href{ 82 | width: 20%; 83 | } 84 | table tr:first-child{ 85 | background-color: #ddd; 86 | height: 30px; 87 | line-height: 30px; 88 | } 89 | table tr{ 90 | text-align: center; 91 | border-bottom: solid 2px #ddd; 92 | } 93 | table td{ 94 | padding-bottom: 20px; 95 | padding-top: 20px; 96 | } -------------------------------------------------------------------------------- /views/admin/blog.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台管理-博客管理 6 | 7 | 8 | 9 | 10 | 11 | <% include common/top.ejs %> 12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 标题:
20 | 作者:
21 | 摘要:
22 | 链接:
23 | 24 |
25 |
26 | <%if(typeof modData != 'undefined'){%> 27 |
28 | 29 |
30 |
31 | 32 | 标题:
33 | 作者:
34 | 摘要:
35 | 链接:
36 | 37 |
38 |
39 | <%}%> 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | <% for(var i = 0;i 50 | 51 | 52 | 53 | 54 | 55 | 56 | 64 | 65 | <%}%> 66 |
ID标题作者摘要链接操作
<%=formData[i].ID%><%=formData[i].title%><%=formData[i].author%><%=formData[i].summary%><%=formData[i].href%> 57 | 58 | 59 | 60 | 61 | 62 | 63 |
67 | 68 | -------------------------------------------------------------------------------- /static/admin/css/users.css: -------------------------------------------------------------------------------- 1 | .add_button{ 2 | height: 50px; 3 | width: 100px; 4 | background:-moz-linear-gradient(left,#a1655a,#2e4a53);/*火狐*/ 5 | background:-webkit-gradient(linear, 0% 0%, 0% 100%,from(#b8c4cb), to(#f6f6f8));/*谷歌*/ 6 | background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#0000ff)); /* Safari 4-5, Chrome 1-9*/ 7 | background: -webkit-linear-gradient(left, #a1655a, #2e4a53); /*Safari5.1 Chrome 10+*/ 8 | background: -o-linear-gradient(left, #a1655a, #2e4a53); /*Opera 11.10+*/ 9 | border-top-right-radius: 25px; 10 | border-bottom-right-radius: 25px; 11 | display: flex; 12 | align-items: center; 13 | flex-direction: row-reverse; 14 | position: fixed; 15 | left:-50px; 16 | top:300px; 17 | cursor: pointer; 18 | -webkit-transition: left .5s; 19 | -moz-transition: left .5s; 20 | -ms-transition: left .5s; 21 | -o-transition: left .5s; 22 | transition: left .5s; 23 | } 24 | .add_button img{ 25 | width: 50%; 26 | transform: rotate(0deg); 27 | position: relative; 28 | right:0; 29 | -webkit-transition: right .5s,transform,.5s; 30 | -moz-transition: right .5s,transform,.5s; 31 | -ms-transition: right .5s,transform,.5s; 32 | -o-transition: right .5s,transform,.5s; 33 | transition: right .5s,transform,.5s; 34 | } 35 | .form_bg{ 36 | background-color: #000; 37 | opacity: .5; 38 | height: 100%; 39 | width: 100%; 40 | position: absolute; 41 | top:0; 42 | } 43 | .add_form{ 44 | justify-content: center; 45 | display: none; 46 | } 47 | .add_form img{ 48 | width: 50px; 49 | height: auto; 50 | position: fixed; 51 | z-index: 2; 52 | transform: rotate(45deg); 53 | right: 37%; 54 | margin-top: 33px; 55 | } 56 | .add_form form{ 57 | position: fixed; 58 | z-index: 2; 59 | text-align: center; 60 | color: #fff; 61 | font-size: 20px; 62 | margin-top: 100px; 63 | line-height: 55px; 64 | } 65 | table{ 66 | width: 100%; 67 | } 68 | table .id{ 69 | width: 7%; 70 | } 71 | table .title{ 72 | width: 25%; 73 | } 74 | table .author{ 75 | width: 8%; 76 | } 77 | table .summary{ 78 | width: 30%; 79 | 80 | } 81 | table .href{ 82 | width: 20%; 83 | } 84 | table tr:first-child{ 85 | background-color: #ddd; 86 | height: 30px; 87 | line-height: 30px; 88 | } 89 | table tr{ 90 | text-align: center; 91 | border-bottom: solid 2px #ddd; 92 | } 93 | table td{ 94 | padding-bottom: 20px; 95 | padding-top: 20px; 96 | } 97 | table img{ 98 | width:40px; 99 | height: auto; 100 | } -------------------------------------------------------------------------------- /resources/blog.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : neal 5 | Source Server Type : MySQL 6 | Source Server Version : 50717 7 | Source Host : localhost 8 | Source Database : blog 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50717 12 | File Encoding : utf-8 13 | 14 | Date: 02/25/2017 12:44:35 PM 15 | */ 16 | 17 | SET NAMES utf8; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for `admin_table` 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `admin_table`; 24 | CREATE TABLE `admin_table` ( 25 | `ID` int(11) NOT NULL AUTO_INCREMENT, 26 | `username` varchar(32) NOT NULL, 27 | `password` varchar(32) NOT NULL, 28 | PRIMARY KEY (`ID`) 29 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; 30 | 31 | -- ---------------------------- 32 | -- Records of `admin_table` 33 | -- ---------------------------- 34 | BEGIN; 35 | INSERT INTO `admin_table` VALUES ('1', 'root', 'cecd14064502f3ab34cd5b0dee3545c2'), ('2', 'neal', 'b644584d66f1740887e60408b543cea0'), ('3', 'Nealyang', '9a162c584b7cad5c3eb19e9fbcbd8592'); 36 | COMMIT; 37 | 38 | -- ---------------------------- 39 | -- Table structure for `blog_list_table` 40 | -- ---------------------------- 41 | DROP TABLE IF EXISTS `blog_list_table`; 42 | CREATE TABLE `blog_list_table` ( 43 | `ID` int(11) NOT NULL AUTO_INCREMENT, 44 | `title` varchar(200) NOT NULL, 45 | `summary` varchar(500) NOT NULL, 46 | `href` varchar(300) NOT NULL, 47 | `author` varchar(32) NOT NULL, 48 | PRIMARY KEY (`ID`) 49 | ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; 50 | 51 | -- ---------------------------- 52 | -- Records of `blog_list_table` 53 | -- ---------------------------- 54 | BEGIN; 55 | INSERT INTO `blog_list_table` VALUES ('2', '关于nodejs中cookie和session浅谈', 'cookie,session作用,以及在nodejs中如何操作和利用cookie和session', 'https://my.oschina.net/Nealyang/blog/844049', 'Nealyang'), ('6', 'angularJs中关于ng-class的三种使用方式说明', '在开发中我们通常会遇到一种需求:一个元素在不同的状态需要展现不同的样子。\r\n\r\n而在这所谓的样子当然就是改变其css的属性,而实现能动态的改变其属性值,必然只能是更换其class属性', 'https://my.oschina.net/Nealyang/blog/525865', 'Nealyang'), ('7', '点燃中国冰雪运动的火炬——习近平总书记关心北京冬奥会5个镜头', '筹备工作紧锣密鼓。春节前后,习近平总书记的日程中,北京冬奥会筹备工作占据着重要位置。', 'http://china.huanqiu.com/article/2017-02/10199694.html', 'Huanqiu'), ('8', '缅甸工人罢工还打砸抢,中资服装厂过去两个半月经历了什么', '一家位于缅甸仰光兰达亚工业区的中资服装厂23日遭遇数百名缅籍罢工工人及外来人员打砸破坏,7名中国籍员工在厂内被困9小时才获准离开。企业负责人说,该厂已经被占据近一个月,迄今仍被罢工者控制。', 'http://world.huanqiu.com/article/2017-02/10200088.html', '环球网'); 56 | COMMIT; 57 | 58 | -- ---------------------------- 59 | -- Table structure for `user_table` 60 | -- ---------------------------- 61 | DROP TABLE IF EXISTS `user_table`; 62 | CREATE TABLE `user_table` ( 63 | `ID` int(11) NOT NULL AUTO_INCREMENT, 64 | `username` varchar(32) NOT NULL, 65 | `email` varchar(32) NOT NULL, 66 | `pic_header` varchar(300) NOT NULL, 67 | PRIMARY KEY (`ID`) 68 | ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; 69 | 70 | -- ---------------------------- 71 | -- Records of `user_table` 72 | -- ---------------------------- 73 | BEGIN; 74 | INSERT INTO `user_table` VALUES ('6', 'Nealyang', 'sunsting@163.com', '/files/upload/c630494b49e1e54a713f2e4f275edd2e.jpg'), ('7', 'Neal', 'nealyang231@outlook.com', '/files/upload/68efe455c4ecda9517d644751c749849.jpg'), ('8', '婷婷', 'sunsting@163.com', '/files/upload/872ce9c96a353d082f20f20575af158f.jpg'); 75 | COMMIT; 76 | 77 | SET FOREIGN_KEY_CHECKS = 1; 78 | -------------------------------------------------------------------------------- /static/admin/css/reset.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /************************* 3 | * 4 | * author: Neal 5 | * version: 1.0 6 | * time: 2016-07-11 7 | * 8 | *************************/ 9 | html { -webkit-overflow-scrolling: touch; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%} 10 | article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section,body,div,h1,h2,h3,h4,h5,h6,hr,p,blockquote,dl,dt,dd,ul,ol,li,pre,fieldset,legend,button,input,textarea,form,th,td { margin: 0; padding: 0; vertical-align: baseline} 11 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary { display: block} 12 | audio,canvas,video { display: inline-block; *display: inline; *zoom:1} 13 | body,button,input,select,textarea { font: 12px/1.5 Tahoma,Helvetica,Arial,"\5B8B\4F53","\5FAE\8F6F\96C5\9ED1",sans-serif} 14 | h1,h2,h3,h4,h5,h6 { font-size: 100%; font-weight: normal} 15 | address,cite,dfn,em,var,i { font-style: normal} 16 | ul,ol { list-style: none outside none} 17 | a { text-decoration: none; outline: 0} 18 | a:hover { text-decoration: underline; } 19 | a:active { text-decoration: none; } 20 | pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word} 21 | sub,sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline} 22 | sup { top: -0.5em} 23 | sub { bottom: -0.25em} 24 | fieldset,iframe { border: 0 none} 25 | img { border: 0 none; vertical-align: middle; -ms-interpolation-mode: bicubic; max-width: 100%;} 26 | button,input,select,textarea { font-family: inherit; font-size: 100%; vertical-align: baseline; *vertical-align: middle} 27 | button,input[type=button],input[type=submit],input[type="reset"] { -webkit-appearance: button; cursor: pointer; *overflow: visible} 28 | button[disabled],input[disabled] { cursor: default} 29 | button::-moz-focus-inner,button::-moz-focus-outer,input::-moz-focus-inner,input::-moz-focus-outer { border: 0 none; padding: 0; margin: 0} 30 | input[type=search] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box} 31 | input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration { -webkit-appearance: none} 32 | textarea { overflow: auto; vertical-align: top; resize: vertical} 33 | table { border-collapse: collapse; border-spacing: 0} 34 | strong,em,i { font-weight: normal} 35 | 36 | /**************** base ******************/ 37 | /* display */ 38 | .dn{display:none;} 39 | .di{display:inline;} 40 | .db{display:block;} 41 | .dib{display:inline-block;} /* if the element is block level(eg. div, li), using 'inline_any' instead */ 42 | /* float*/ 43 | .fl { float: left;} 44 | .fr { float: right;} 45 | /* text-align */ 46 | .tc{text-align:center;} 47 | .tr{text-align:right;} 48 | .tl{text-align:left;} 49 | /* vertical-align */ 50 | .vm{ display: inline-block; vertical-align:middle;} 51 | .vtb{ display: inline-block;vertical-align:text-bottom;} 52 | /* position */ 53 | .rel{position:relative;} 54 | .abs{position:absolute;} 55 | /* font-size */ 56 | .f12{font-size:12px;} 57 | .f14{font-size:14px;} 58 | .f16{font-size:16px;} 59 | .f18{font-size:18px;} 60 | .f20{font-size:20px;} 61 | .f24{font-size:24px;} 62 | /* 块状元素水平居中 */ 63 | .auto{margin-left:auto; margin-right:auto;} 64 | /* 清除浮 动*/ 65 | .clearfix{*zoom:1;} 66 | .clearfix:after{display:table; content:''; clear:both;} 67 | /* 单行文字溢出虚点显 示*/ 68 | .ellipsis{text-overflow:ellipsis; white-space:nowrap; overflow:hidden;} -------------------------------------------------------------------------------- /router/admin/blog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/25. 3 | */ 4 | const express = require('express'); 5 | const common = require('../../lib/common'); 6 | const mysql = require('mysql'); 7 | 8 | const db = mysql.createPool({ 9 | host: 'localhost', 10 | port: 3306, 11 | user: 'root', 12 | password: ' neal', 13 | database: 'blog' 14 | }); 15 | 16 | module.exports = function () { 17 | var router = express.Router(); 18 | router.get('/', function (req, res) { 19 | switch (req.query.action) { 20 | case 'del': 21 | //删除操作 22 | db.query('DELETE FROM blog_list_table WHERE id="'+req.query.id+'"',function (err,resultData) { 23 | if(err){ 24 | console.error(err); 25 | res.status(500).send({code:500,msg:'database error'}); 26 | }else{ 27 | res.redirect('/admin/blog'); 28 | } 29 | }); 30 | break; 31 | case 'mod': 32 | //修改操作 33 | db.query('SELECT * FROM blog_list_table WHERE id="'+req.query.id+'"',function (err,modData) { 34 | if(err){ 35 | console.error(err); 36 | res.status(500).send({code:500,msg:'database error'}); 37 | }else if(modData.length == 0){ 38 | res.status(400).send({code:400,msg:'parameters error'}); 39 | }else{ 40 | db.query('SELECT * FROM blog_list_table',function (err,allData) { 41 | if(err){ 42 | console.error(err); 43 | res.status(500).send({code:500,msg:'database error'}); 44 | }else{ 45 | res.render('admin/blog.ejs',{formData:allData,modData:modData}); 46 | } 47 | }); 48 | } 49 | }); 50 | break; 51 | default: 52 | db.query('SELECT * FROM blog_list_table', function (err, resultData) { 53 | if (err) { 54 | console.error(err); 55 | res.status(500).send({code: 500, msg: 'database error'}).end(); 56 | } else { 57 | res.render('admin/blog.ejs', {formData: resultData}); 58 | } 59 | }); 60 | } 61 | 62 | }); 63 | router.post('/', function (req, res) { 64 | //此处验证应该更加严格,比如正则 65 | var title = req.body.title.trim(); 66 | var author = req.body.author.trim(); 67 | var summary = req.body.summary.trim(); 68 | var href = req.body.href.trim(); 69 | 70 | if (title && author && summary && href) { 71 | if(req.body.modified){ 72 | db.query('UPDATE blog_list_table SET title="'+title+'",author="'+author+'",summary="'+summary+'",href="'+href+'" WHERE ID="'+req.body.modified+'"',function (err,resultData) { 73 | if(err){ 74 | console.error(err); 75 | res.status(500).send({code:500,msg:'database error'}); 76 | }else{ 77 | res.redirect('/admin/blog'); 78 | } 79 | }) 80 | }else{ 81 | db.query('INSERT INTO blog_list_table (title,author,summary,href) VALUE("' + title + '","' + author + '","' + summary + '","' + href + '")', function (err, data) { 82 | if (err) { 83 | console.error(err); 84 | res.status(500).send({code: 500, msg: 'database error'}).end(); 85 | } else { 86 | res.redirect('/admin/blog'); 87 | } 88 | }); 89 | } 90 | } else { 91 | res.status(400).send({code: 400, msg: 'parameters error'}).end(); 92 | } 93 | 94 | }); 95 | return router; 96 | }; 97 | -------------------------------------------------------------------------------- /router/admin/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Nealyang on 17/2/25. 3 | */ 4 | const express = require('express'); 5 | const common = require('../../lib/common'); 6 | const mysql = require('mysql'); 7 | const fs = require('fs'); 8 | const pathLib = require('path'); 9 | 10 | const db = mysql.createPool({ 11 | host: 'localhost', 12 | user: 'root', 13 | password: ' neal', 14 | database: 'blog' 15 | }); 16 | 17 | module.exports = function () { 18 | var router = express.Router(); 19 | router.get('/', function (req, res) { 20 | switch (req.query.action) { 21 | case 'del'://删除操作 22 | db.query('SELECT * FROM user_table WHERE ID="' + req.query.id + '"', function (err, queryData) { 23 | if (err) { 24 | console.error(err); 25 | res.status(500).send({code: 500, msg: 'database error'}); 26 | } else if (queryData.length == 0) { 27 | res.status(400).send({code: 400, msg: 'parameters error'}); 28 | } else { 29 | fs.unlink(queryData[0].pic_header.replace('\/files','static'), function (err) { 30 | if (err) { 31 | console.error(err); 32 | res.status(500).send({code:500,msg:'operate err'}); 33 | } else { 34 | db.query('DELETE FROM user_table WHERE ID="'+ 35 | req.query.id+'"',function (err,resultData) { 36 | if(err){ 37 | console.error(err); 38 | res.status(500).send({code: 500, msg: 'database error'}); 39 | }else{ 40 | res.redirect('/admin/users'); 41 | } 42 | }) 43 | } 44 | }) 45 | } 46 | }); 47 | break; 48 | case 'mod': 49 | db.query('SELECT * FROM user_table',function (err,allData) { 50 | if (err) { 51 | console.error(err); 52 | res.status(500).send({code: 500, msg: 'database error'}); 53 | } else { 54 | db.query('SELECT * FROM user_table WHERE ID="'+req.query.id+'"',function (err,modData) { 55 | if (err) { 56 | console.error(err); 57 | res.status(500).send({code: 500, msg: 'database error'}); 58 | }else if(modData.length == 0){ 59 | res.status(400).send({code: 400, msg: 'parameters error'}); 60 | }else { 61 | res.render('admin/users.ejs',{usersData:allData,modData:modData}); 62 | } 63 | }); 64 | } 65 | }); 66 | break; 67 | default: 68 | db.query('SELECT * FROM user_table', function (err, allUsersData) { 69 | if (err) { 70 | console.error(err); 71 | res.status(500).send({code: 500, msg: 'database error'}); 72 | } else { 73 | res.render('admin/users.ejs', {usersData: allUsersData}); 74 | } 75 | }); 76 | } 77 | }); 78 | router.post('/', function (req, res) { 79 | var username = req.body.username; 80 | var email = req.body.email; 81 | if(req.files.length>0){ 82 | var ext = pathLib.parse(req.files[0].originalname).ext; 83 | var pic_header = '/files/upload/' + req.files[0].filename + ext; 84 | } 85 | 86 | //需要进行一些校验,这里就忽略了 87 | if(req.body.modified){//修改 88 | //查看有没有新传来的头像,如果有,则删除,新建,如果没有,直接更新需要更新的内容 89 | if(req.files.length>0){ 90 | //有修改头像,则进行原来头像的删除,再上传 91 | db.query('SELECT * FROM user_table WHERE ID="'+req.body.modified+'"',function (err,modData) { 92 | if (err) { 93 | console.error(err); 94 | res.status(500).send({code: 500, msg: 'database error'}); 95 | }else if(modData.length == 0){ 96 | res.status(400).send({code: 400, msg: 'parameters error'}); 97 | }else{ 98 | fs.unlink(modData[0].pic_header.replace('\/files','static'),function (err) { 99 | if(err){ 100 | console.error(err); 101 | res.status(500).send({code:500,msg:'operate error'}); 102 | }else{ 103 | //删除成功,开始对新的文件进行重命名 104 | fs.rename(req.files[0].path, req.files[0].path + ext, function (err) { 105 | if (err) { 106 | console.error(err); 107 | res.status(500).send({code: 500, msg: 'operate error'}); 108 | } else { 109 | db.query('UPDATE user_table SET username="'+ 110 | username+'",email="' + email + '",pic_header="' + 111 | pic_header + '" WHERE ID="'+req.body.modified+'"',function (err,data) { 112 | if (err) { 113 | console.error(err); 114 | res.status(500).send({code: 500, msg: 'database error'}); 115 | }else{ 116 | res.redirect('/admin/users'); 117 | } 118 | }); 119 | } 120 | }); 121 | } 122 | }) 123 | } 124 | }) 125 | }else{ 126 | db.query('UPDATE user_table SET username="'+username+'",email="' + email + '" WHERE ID="'+ 127 | req.body.modified+'"',function (err,data) { 128 | if (err) { 129 | console.error(err); 130 | res.status(500).send({code: 500, msg: 'database error'}); 131 | }else{ 132 | res.redirect('/admin/users'); 133 | } 134 | }); 135 | } 136 | }else{//新增 137 | fs.rename(req.files[0].path, req.files[0].path + ext, function (err) { 138 | if (err) { 139 | console.error(err); 140 | res.status(500).send({code: 500, msg: 'data error'}); 141 | } else { 142 | db.query('INSERT INTO user_table (username,email,pic_header) VALUE("' + username + '","' + 143 | email + '","' + pic_header + '")', function (err, resultData) { 144 | if (err) { 145 | console.error(err); 146 | res.status(500).send({code: 500, msg: 'database error'}); 147 | } else { 148 | res.redirect('/admin/users'); 149 | } 150 | }); 151 | } 152 | }); 153 | } 154 | 155 | }); 156 | return router; 157 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ejs-express-mysql 2 | == 3 | 4 | ###[基于express,MySQL,ejs实现的一个简单基本的网站后台管理应用](https://github.com/Nealyang/ejs-express-mysql) 5 | 6 | ##前言 7 | 也是这两周才正式的接触node,虽然在前端开发中我们常常说前后端分离,但是在学习过程中,个人感觉还是要刁难刁难自己的。因为用ejs来写前端页面。 8 | 项目主要实现用户的登录,session的存储和加密(准确的说是签名),数据库的CRUD,包括图片的上传,删除和修改等基本功能。 9 | 关于登录,查询等操作本应该更加的严谨,这里只做简单演示。包括一些配置文件的编写。 10 | 11 | --- 12 | 喜欢的朋友方便的话可以给个star (^-^)V 13 | 14 | 顺便推广一波nodejs技术交流群,群号:209530601 15 | *** 16 | 17 | >ejs mysql nodejs express express-router... 18 | 19 | ##效果图 20 | 21 | 登录页 22 | ![登录页](./resources/login.png) 23 | 24 | 整体操作流程图 25 | ![操作图](./resources/show.gif) 26 | 27 | **GIF Brewery转gif真的有点。。。好吧,不吐槽了,后面会分开讲解每一步,好在基本操作还能看得清~** 28 | 29 | 30 | 后台管理首页 31 | ![后台管理首页](./resources/index.png) 32 | 33 | 博文管理 34 | ![博文管理](./resources/blog.png) 35 | 36 | 用户管理页 37 | ![用户管理](./resources/user.png) 38 | 39 | 操作 40 | ![操作](./resources/blog_o.png) 41 | 42 | 43 | ##开发准备 44 | 关于开发前期的准备,这里就不多说了,说实话,自己也没有准备啥,关于nodejs环境,MySQL配置啥的就多少了,关于本项目的[数据字典](./数据字典.txt),还有[SQL文件](./resources/blog.sql)已经在目录里了,这里主要说下后端开发的每一个步骤 45 | ##项目目录 46 | 项目目录 47 | ![项目目录](./resources/project_str.png) 48 | ##整体架构 49 | 项目重点在后端开发中,web端页面并没有涉及到,后端管理流程大致如下: 50 | * 路由控制分为admin,web,还是那句话,我们操作全部在admin中 51 | * 跳转到admin拦截所有的请求,判断用户是否登录 52 | * 未登录则重定向到登录,登陆成功后设置session。[不懂session?点击这里](https://my.oschina.net/Nealyang/blog/844049) 53 | * 登录后则可进行相关的操作,数据的增删改查等功能。 54 | ##后端开发 55 | ###后台基本架构、路由设置 56 | const express = require('express'); 57 | const expressStatic = require('express-static'); 58 | const bodyParser = require('body-parser'); 59 | const multer = require('multer'); 60 | const multerObj = multer({dest:'./static/upload'}); 61 | const cookieParser = require('cookie-parser'); 62 | const cookieSession = require('cookie-session'); 63 | const consolidate = require('consolidate'); 64 | const ejs = require('ejs'); 65 | 66 | //创建服务器 67 | var server = express(); 68 | server.listen(8080); 69 | 70 | //解析请求数据 71 | 72 | server.use(bodyParser({ 73 | extended:false 74 | })); 75 | server.use(multerObj.any()); 76 | 77 | //设置cookie,session 78 | server.use(cookieParser('Neal_signed')); 79 | (function () { 80 | var arr = []; 81 | for(var i = 0;i<10000;i++){ 82 | arr.push('keys_'+Math.random()); 83 | } 84 | server.use(cookieSession({ 85 | name:'session_id', 86 | keys:arr, 87 | maxAge:20*60*1000//一般我会设置20分钟,这里是为了感受session过期~~带来的快感~?(●´∀`●)ノ 88 | })) 89 | })(); 90 | 91 | //设置模板 92 | server.set('view engine','html'); 93 | server.set('views','./views'); 94 | server.engine('html',consolidate.ejs); 95 | //设置路由 96 | server.use('/admin',require('./router/admin/index')()); 97 | server.use('/',require('./router/web/index')()); 98 | 99 | 100 | //静态文件的请求 101 | server.use('/files',expressStatic('./static')); 102 | 我的基本架构如下,关于每一部分的功能,都已经标注。关于路由的控制在admin/index.js跟server.js大同小异,我想大家也都应该知道了。 103 | ###登录功能 104 | 登录功能这里主要说两点 105 | * 密码的md5签名(当然,大多数人说是md5加密) 106 | * session的应用 107 | 在lib中存放着自己写的一些方法,作为一个库,admin初始化有三个用户,包括u/p:root,neal,Nealyang 108 | 关于密码的签名方法主要如下: 109 | var crypto = require('crypto'); 110 | 111 | module.exports = { 112 | MD5_SUFFIX : 'JDSAIOEUQOIoieuoiqv#$%^&dhfja)(* %^&FGHJfyuieyfhfhak(^.^)YYa!!\(^o^)/Y(^o^)Y(*^__^*)ヘ|・∀・|ノ*~●', 113 | md5:function (pwd) { 114 | var md5 = crypto.createHash('md5'); 115 | return md5.update(pwd).digest('hex'); 116 | } 117 | }; 118 | MD5_SUFFIX是加密字符串,用法如路由login.js: 119 | 120 | 121 | router.post('/',function (req,res) { 122 | var username = req.body.username; 123 | var password = common.md5(req.body.password+common.MD5_SUFFIX); 124 | if(username && password){ 125 | db.query('SELECT * FROM admin_table WHERE username="'+username+'"',function (err,userData) { 126 | if(err){ 127 | console.error(err); 128 | res.status(500).send({code:500,data:[],msg:'database error'}); 129 | }else if(userData.length == 0){ 130 | res.status(400).send({code:400,data:[],msg:'parameters error'}); 131 | }else{ 132 | if(userData[0].password != password){ 133 | res.status(400).send({code:400,data:[],msg:'username or password error'}); 134 | }else{ 135 | req.session['user_id'] = userData[0].ID;//注意这里是在req上面 136 | res.status(200).send({code:200,data:[],msg:'success'}); 137 | } 138 | } 139 | }) 140 | }else{ 141 | res.status(400).send({code:400,data:[],msg:'parameters error'}); 142 | } 143 | }); 144 | 从上面的代码中也展现了什么时候设置session,并且值得提一下的是这里提供给前端页面的是接口,这样的话很多逻辑都放到了前端,后面我们都是通过页面渲染来输出的了。下面是所有请求的拦截判断: 145 | 146 | router.use(function (req,res,next) { 147 | if(!req.session['user_id'] && req.url != '/login'){ 148 | res.redirect('/admin/login'); 149 | }else{ 150 | next(); 151 | } 152 | }); 153 | 154 | ##ejs前端页面的重点代码讲解 155 | 公共头部的引入: 156 | 157 | <% include common/top.ejs %> 158 | 159 | 查询数据库的前端展示: 160 | 161 | <% for(var i = 0;i 162 | 163 | <%=formData[i].ID%> 164 | <%=formData[i].title%> 165 | <%=formData[i].author%> 166 | <%=formData[i].summary%> 167 | <%=formData[i].href%> 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | <%}%> 178 | 179 | 修改后我们通过页面给的标识来知道是否为修改的提交,毕竟这里我们没有前端逻辑的js 180 | 181 | <%if(typeof modData != 'undefined'){%> 182 |
183 | 184 |
185 |
186 | 187 | 标题:
188 | 作者:
189 | 摘要:
190 | 链接:
191 | 192 |
193 |
194 | <%}%> 195 | 196 | ##博文管理 197 | 还是那句话,如果是仅仅提供前端的接口的话,这里会方便很多,然后我们用的是ejs,所以很多的逻辑都放在了后端,在get/post到请求的时候需要做很多判断 198 | get方法请求如下: 199 | 200 | router.get('/', function (req, res) { 201 | switch (req.query.action) { 202 | case 'del': 203 | //删除操作 204 | db.query('DELETE FROM blog_list_table WHERE id="'+req.query.id+'"',function (err,resultData) { 205 | if(err){ 206 | console.error(err); 207 | res.status(500).send({code:500,msg:'database error'}); 208 | }else{ 209 | res.redirect('/admin/blog'); 210 | } 211 | }); 212 | break; 213 | case 'mod': 214 | //修改操作 215 | db.query('SELECT * FROM blog_list_table WHERE id="'+req.query.id+'"',function (err,modData) { 216 | if(err){ 217 | console.error(err); 218 | res.status(500).send({code:500,msg:'database error'}); 219 | }else if(modData.length == 0){ 220 | res.status(400).send({code:400,msg:'parameters error'}); 221 | }else{ 222 | db.query('SELECT * FROM blog_list_table',function (err,allData) { 223 | if(err){ 224 | console.error(err); 225 | res.status(500).send({code:500,msg:'database error'}); 226 | }else{ 227 | res.render('admin/blog.ejs',{formData:allData,modData:modData}); 228 | } 229 | }); 230 | } 231 | }); 232 | break; 233 | default: 234 | db.query('SELECT * FROM blog_list_table', function (err, resultData) { 235 | if (err) { 236 | console.error(err); 237 | res.status(500).send({code: 500, msg: 'database error'}).end(); 238 | } else { 239 | res.render('admin/blog.ejs', {formData: resultData}); 240 | } 241 | }); 242 | } 243 | 244 | }); 245 | 这里的switch,主要是分为,查询,删除,和修改 246 | 都是些简单的CRUD操作,这里就不多细说了。不熟悉的兄弟们可以看一看,写的不好,多多提意见。 247 | 248 | 同理,post的请求,主要就是分为,文章列表的添加,和修改的两个post,代码如下: 249 | 250 | router.post('/', function (req, res) { 251 | //此处验证应该更加严格,比如正则 252 | var title = req.body.title.trim(); 253 | var author = req.body.author.trim(); 254 | var summary = req.body.summary.trim(); 255 | var href = req.body.href.trim(); 256 | 257 | if (title && author && summary && href) { 258 | if(req.body.modified){ 259 | db.query('UPDATE blog_list_table SET title="'+title+'",author="'+author+'",summary="'+summary+'",href="'+href+'" WHERE ID="'+req.body.modified+'"',function (err,resultData) { 260 | if(err){ 261 | console.error(err); 262 | res.status(500).send({code:500,msg:'database error'}); 263 | }else{ 264 | res.redirect('/admin/blog'); 265 | } 266 | }) 267 | }else{ 268 | db.query('INSERT INTO blog_list_table (title,author,summary,href) VALUE("' + title + '","' + author + '","' + summary + '","' + href + '")', function (err, data) { 269 | if (err) { 270 | console.error(err); 271 | res.status(500).send({code: 500, msg: 'database error'}).end(); 272 | } else { 273 | res.redirect('/admin/blog'); 274 | } 275 | }); 276 | } 277 | } else { 278 | res.status(400).send({code: 400, msg: 'parameters error'}).end(); 279 | } 280 | 281 | }); 282 | 283 | 284 | ##用户管理 285 | 后台的管理大概也即是这么多,用户管理和博文管理基本都是差不多的,这里重点是说下,这里用到的图片上传。 286 | 图片上传我用的是**multer**中间件,不知道的可以查下,注意用这个中间件接受图片上传时form表单的**enctype**必须要设置为multipart/form-data 287 | 288 | 关于图片上传后,默认是不包括后缀名的,所以这里我们需要用到fs模块的重命名操作,代码如下: 289 | 290 | fs.rename(req.files[0].path, req.files[0].path + ext, function (err) { 291 | if (err) { 292 | console.error(err); 293 | res.status(500).send({code: 500, msg: 'data error'}); 294 | } else { 295 | db.query('INSERT INTO user_table (username,email,pic_header) VALUE("' + username + '","' + 296 | email + '","' + pic_header + '")', function (err, resultData) { 297 | if (err) { 298 | console.error(err); 299 | res.status(500).send({code: 500, msg: 'database error'}); 300 | } else { 301 | res.redirect('/admin/users'); 302 | } 303 | }); 304 | } 305 | }); 306 | 前面的变量定义主要如下: 307 | 308 | var username = req.body.username; 309 | var email = req.body.email; 310 | if(req.files.length>0){ 311 | var ext = pathLib.parse(req.files[0].originalname).ext; 312 | var pic_header = '/files/upload/' + req.files[0].filename + ext; 313 | } 314 | 用到path模块对路径的解析。为了获取后缀名~ 315 | 316 | 说到这,对于修改也就很简单了,就是删除原先有的那个图片,然后换上现在有的图片,具体代码如下: 317 | 318 | //需要进行一些校验,这里就忽略了 319 | if(req.body.modified){//修改 320 | //查看有没有新传来的头像,如果有,则删除,新建,如果没有,直接更新需要更新的内容 321 | if(req.files.length>0){ 322 | //有修改头像,则进行原来头像的删除,再上传 323 | db.query('SELECT * FROM user_table WHERE ID="'+req.body.modified+'"',function (err,modData) { 324 | if (err) { 325 | console.error(err); 326 | res.status(500).send({code: 500, msg: 'database error'}); 327 | }else if(modData.length == 0){ 328 | res.status(400).send({code: 400, msg: 'parameters error'}); 329 | }else{ 330 | fs.unlink(modData[0].pic_header.replace('\/files','static'),function (err) { 331 | if(err){ 332 | console.error(err); 333 | res.status(500).send({code:500,msg:'operate error'}); 334 | }else{ 335 | //删除成功,开始对新的文件进行重命名 336 | fs.rename(req.files[0].path, req.files[0].path + ext, function (err) { 337 | if (err) { 338 | console.error(err); 339 | res.status(500).send({code: 500, msg: 'operate error'}); 340 | } else { 341 | db.query('UPDATE user_table SET username="'+ 342 | username+'",email="' + email + '",pic_header="' + 343 | pic_header + '" WHERE ID="'+req.body.modified+'"',function (err,data) { 344 | if (err) { 345 | console.error(err); 346 | res.status(500).send({code: 500, msg: 'database error'}); 347 | }else{ 348 | res.redirect('/admin/users'); 349 | } 350 | }); 351 | } 352 | }); 353 | } 354 | }) 355 | } 356 | }) 357 | }else{ 358 | db.query('UPDATE user_table SET username="'+username+'",email="' + email + '" WHERE ID="'+ 359 | req.body.modified+'"',function (err,data) { 360 | if (err) { 361 | console.error(err); 362 | res.status(500).send({code: 500, msg: 'database error'}); 363 | }else{ 364 | res.redirect('/admin/users'); 365 | } 366 | }); 367 | } 368 | } 369 | 370 | 看到这个代码是不是感觉想死的心都有了???的确,多少篇文章都说到了关于nodejs的回调地狱,但是Node 7.6 发布了,支持了async函数,JavaScript异步的写法彻底改变了。所以这个大可不必太过于担心 371 | 况且Koa不就这么的出来和投入大范围的使用了嘛,这里我们大可用express,去尽情的感受Node的魅力。 372 | 373 | ##结束语 374 | 说到这,基本的一个小小后台管理应用就完事了,是不是感觉没有想象中的那么难?写的不好,欢迎大家吐槽指教~~~ 375 | 最后,欢迎愿意一起学习nodejs的朋友加入,Nodejs技术群:209530601 376 | ~~ --------------------------------------------------------------------------------