用express脚手架做后台管理系统(思路+代码展示)
所用技术:
1.采用前后端分离:
前后端分离和前后端不分离的区别:
前后端不分离:前端页面看到的效果都是由后端控制和渲染的,前后端耦合度高;
前后端分离:前端根据自己的需求渲染页面效果,前后端耦合度低。
2.ajax
通过ajax请求数据,处理后返回结果
3.jquery
主要是使用iquery中的$.ajax
4.数据库:
mongoose
5.bootstrap
主要是用于静态布局
思路:
一,登录注册:
1.在public文件夹新建index.html 文件,书写静态布局,利用模块化将html代码进行封装---->
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="./css/common/reset.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="./css/index/index.css"> </head> <body> <div class="login_container"></div> </body> </html> <script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script> <script src="./js/js/common/register.js"></script> <script src="./js/js/common/login.js"></script> <script src="./js/js/common/page.js"></script>
2.在public文件夹下新建js文件夹,在js文件夹下新建common文件夹,存放index要封装的模块化JS文件---->
3.在common文件夹下新建page.js文件:创建一个Page对象(该Page对象就是index文件中的登录注册的静态布局)---->>给Page对象一个container实例属性,init以及createContent的原型方法---->
function Page() { //实例属性: this.container = $(".login_container"); //登录注册最外层div的class名 this.flag = true; //设置开关,在注册和登录之间实现跳转 this.init(); } //原型方法: Page.prototype = { init:function(){ this.createContent(); }, //渲染到页面的方法:需要在Register和Login之后调用: createContent:function(params=this.flag){ //判断有参数则直接登录,没有参数则注册传参 this.container.html('') if(params){ //new一个login对象(login.js) this.login = new Login(this.container); }else{ //new一个register对象(register.js) this.register = new Register(this.container); } } } new Page();
4.在common文件夹下新建login.js和register.js文件---->主要是创建登录,注册模板
//创建一个Register对象: function Register(container){ this.container = container; this.init(); } //Resgiter模板 Register.template = ` <div class="login"> <div class="logo"> <img src="https://cas.1000phone.net/cas/images/login/logo.png"> </div> <form class="form"> <div class="form-group"> <label for="register_username">用户名</label> <input type="email" class="form-control" id="register_username" placeholder="Email"> </div> <div class="form-group"> <label for="register_password">密码</label> <input type="password" class="form-control" id="register_password" placeholder="Password"> </div> <div class="form-group"> <div class="alert alert-danger" role="alert">忘记密码?</div> <div class="alert alert-info" role="alert" id="js_login">去登陆</div> </div> <button type="button" class="btn btn-info login_btn" id="login_btn">注册</button> </form> </div> ` Register.prototype = { init:function(){ this.createDom(); this.handlePush(); this.handleRegister(); }, //创建div,将Register.prototype追加到div中,再将div追加到页面中 createDom:function(){ this.el = $("<div class='content'></div>"); this.el.append(Register.template); this.container.append(this.el); }, //点击去登录,切换到登录页面 handlePush(){ this.el.find("#js_login").on("click",$.proxy(this.handleLogin,this)) }, handleLogin(){ new Page().createContent(true); }, //点击去注册,切换到注册页面 handleRegister(){ this.el.find("#login_btn").on("click",$.proxy(this.handleRegisterBtn,this)) }, //给注册按钮添加点击事件:通过$.ajax向服务器发送请求,并将服务器处理的结果返回给客户端 handleRegisterBtn(){ //获取客户信息(用户名和密码) var username = this.el.find("#register_username").val(); var password = this.el.find("#register_password").val(); //var一个userInfo对象,存放用户名和密码:因为key和value一样,所以只写一个就可以了 // var userInfo = { // username:username, // password:password // } var userInfo = { username, password } //向服务端发送请求:将客户传递的参数传递给服务端,服务端进行处理,判断用户名是否存在 $.ajax({ type:"post", url:"/api/register", //接口是由后端提供的,通过路由建立前后端之间的连接 data:userInfo, dataType:"json", success:$.proxy(this.handleRegisterSuccess,this) //获取数据成功返回handleRegisterSuccess方法 }) }, handleRegisterSuccess(data){ if(data.status){ alert("注册成功"); //注册成功跳转到登录页面 new Page().createContent(true); } } }
//创建Login对象: function Login(container){ //实例属性: this.container = container; this.init(); } //登录模板 Login.template = ` <div class="login"> <div class="logo"> <img src="https://cas.1000phone.net/cas/images/login/logo.png"> </div> <form class="form"> <div class="form-group"> <label for="login_username">用户名</label> <input type="email" class="form-control" id="login_username" placeholder="Email"> </div> <div class="form-group"> <label for="login_password">密码</label> <input type="password" class="form-control" id="login_password" placeholder="Password"> </div> <div class="form-group"> <div class="alert alert-danger" role="alert">忘记密码?</div> <div class="alert alert-info" role="alert" id="js_register">去注册</div> </div> <button type="button" class="btn btn-info login_btn" id="login_btn">登陆</button> </form> </div> ` //原型方法 Login.prototype = { init(){ this.createDom(); this.handlePush(); this.loginBtn(); }, createDom:function(){ this.el = $("<div class='content'></div>"); this.el.append(Login.template); this.container.append(this.el); }, //点击去注册切换到注册页面 handlePush(){ this.el.find("#js_register").on("click",$.proxy(this.handleRegister,this)) }, handleRegister(){ new Page().createContent(false); }, //给登录按钮添加点击事件: loginBtn(){ this.el.find("#login_btn").on("click",$.proxy(this.handleLoginCallBack,this)) }, handleLoginCallBack(){ //var一个userInfo对象 var userInfo = { username:this.el.find("#login_username").val(), password:this.el.find("#login_password").val() } //通过ajax将客户端的请求发送给服务端:并接受服务端返回的数据: $.ajax({ type:"post", url:"/api/login", data:userInfo, success:$.proxy(this.handleLoginSucc,this) }) }, //登录成功则跳转到详情页 handleLoginSucc(data){ if(data.status){ location.href="http://localhost:3000/html/detail.html" } } }
以上前端代码书写完毕:从代码中不难看出:前端主要是做了静态布局,以及向后端发送请求,并将返回结果回馈给客户端,进行渲染页面
接下来理一下后端代码:
在客户端向服务端发送请求后,服务端接收到请求后,是要对请求数据做处理的,即对数据的增删改查,主要是放在model层的:
我们先来处理一下注册数据:处理数据之前我们先来连接一下数据库:
新建util文件夹,并新建database.js文件:
//引入mongoose模块: var mongoose = require("mongoose"); //设置路径,连接数据库 let url = "mongodb://127.0.0.1:27017/bk1824" mongoose.connect(url); //导出数据库模块 module.exports = { mongoose }
有了数据库我们再来model文件夹下处理数据:
我们将注册登录的数据放在user.js文件中:
//连接数据库 const mongoose = require("../util/database").mongoose; //在做数据的增删改查时需要先创建表:在数据库中,会默认在表明后加“s”,当表名结尾字母是y时,变y为i再加es const User = mongoose.model("user",{ //设置表的字段:(mongodb和mongoose的区别之一:mongoose可以设置字段) username:String, password:String }) //查 //在处理注册登录数据时,都需要先在数据库中查出对应数据,再进行判断 const userFind = (userInfo,cb)=>{ User.findOne(userInfo).then((res)=>{ cb(res) }) } //增 //判断注册数据中用户名没被占用,则调用该方法将数据增加到数据库中: const userAdd = (userInfo,cb)=>{ let user = new User(userInfo); user.save().then((res)=>{ cb(res); }) } //导出查,增模块:在controller层引入跑业务逻辑 module.exports = { userFind, userAdd }
有了操作数据库的方法,接下来我们跑一下业务逻辑,业务逻辑主要在controller文件夹下,注册登录的业务逻辑主要放在user.js文件下:
//引入model/user.js文件,使用增,查方法: const userModel = require("../model/user"); //引入加密模块 1. 主要用来给密码加密,增加安全性,提高用户体验 const crypto = require('crypto'); //引入token: 判断用户是否已经登录 需要在前端中验证 const jwt = require("jsonwebtoken"); //注册的业务逻辑:先获取用户输入的数据,再从数据库中查找对应的数据,判断用户输入的数据在数据库中能否查到,如果有,则用户名已存在, //注册失败,查不到,添加到数据库中,注册成功 const register = (req,res)=>{ //解构赋值:因为是POST请求数据的,所以用req.body获取数据:此处获取的是用户传递的参数 let {username,password} = req.body; //调用查的方法,从数据库查数据: userModel.userFind({username},function(data){ //如果数据不存在,则添加进数据库: if(!data){ //存 //创建sha256算法 2. 给数据加密:md5和sha256的区别:md5可以解密,sha256不能解密,所以相对安全 const hash = crypto.createHash('sha256'); //需要加密的字符 3. hash.update(password); //对密码进行加密 4. //将加密的数据添加到数据库中: userModel.userAdd({ username:username, password:hash.digest('hex') //加盐 },function(){ //处理完数据返回结果: res.json({ status:true, Info:"注册成功" }) }) }else{ res.json({ status:false, Info:"用户名已存在" }) } }) } const login = (req,res)=>{ //获取用户名和密码 let {username,password} = req.body; //查看当前用户名是否存在 如果不存在告诉用户用户名不存在 如果存在则进行密码判断 userModel.userFind({username},function(data){ if(data){ //创建sha256算法 const hash = crypto.createHash('sha256'); //进行加密 hash.update(password); //因为数据库中的密码是加密的 所以我们进行对比的时候也需要进行加密在对比 if(data.password == hash.digest('hex')){ //设置token let token = jwt.sign({username},"bk1824",{'expiresIn':'1h'}) //种cookie res.cookie("token",token) res.cookie("userInfo",username) //成功 res.json({ status:true, info:"登陆成功", }) }else{ //失败 res.json({ status:false, info:"密码错误" }) } }else{ //用户名不存在 res.json({ status:false, info:"用户名不存在" }) } }) } //导出模块: module.exports = { register, login } /* md5加密 解密 sha256加密 123456 + bk1824 + 秘钥 = 随机字符串 token 1、安装 cnpm install jsonwebtoken -S 2、引入 const jwt = require("jsonwebtoken"); 3、设置token jwt.sign(payload,秘钥,过期时间) payload:相关信息 秘钥:随机字符 过期时间 token什么时候过期 */
万事俱备,只欠东风,最最重要的路由文件:
var express = require('express'); var router = express.Router(); var userController = require("../controller/user"); /* /api/register */ router.post('/register',userController.register); //登陆 /api/login router.post('/login',userController.login); module.exports = router; /* MVC模式 架构思想 M:model层 数据的增删改查 V:view层 视图的展示 C:controller层 负责业务逻辑 MVP MVVM */
注册登录效果到这儿就告一段落了,接下来再做一下职位的增删改查:
还是先来码一码前端代码:
我们的前端文件还是统一放在public中,至于为啥要放在public中,主要是因为因为我们使用了express框架,既然使用了人家,就得遵循人家的规则嘛,在express中,会优先渲染public中的文件:
我们还是利用bootstrap来做静态布局,用JS模板渲染页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="../css/common/reset.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="../css/detail/detail.css"> </head> <body> <div class="header"> <div class="logo"> <img src="http://jx.1000phone.net/Public/assets/css/images/logo.png?1552286991" /> </div> <div class="info"> <p id="js_userName"></p> <p id="js_logout" class="logout">退出</p> </div> </div> <!-- tabBar --> <div class="container_detail"> <div class="tabbar"> <ul> <li class="active">系统首页</li> <li>职位列表</li> <li>职位管理</li> </ul> </div> <div class="content"> <!-- <div class="list"> <div class="list_job"> <div class="job_des"> <div class="job_name">前端开发工程师</div> <div class="job_price">8000</div> <div class="job_ex">经验3-5年 本科</div> </div> <div class="com_des"> <div class="company_logo"> <img src="https://www.lgstatic.com/thumbnail_100x100/i/image3/M00/28/02/Cgq2xlqa2jWAGAnMAAArW2y3MsY901.jpg"/> </div> <div class="company_name">北京阿里科技有限公司</div> </div> <div class="operation"> <button class="btn btn-danger">删除</button> <button class="btn btn-info">修改</button> </div> </div> </div> --> </div> <!-- 模块框 --> <div class="modal fade" tabindex="-1" role="dialog" id="jobModel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title">请修改内容</h4> </div> <div class="modal-body"> <form> <div class="form-group"> <label for="job_modify_name">职位名称</label> <input type="text" class="form-control" id="job_modify_name" placeholder="请输入职位名称"> </div> <div class="form-group"> <label for="job_modify_price">薪资</label> <input type="text" class="form-control" id="job_modify_price" placeholder="薪资范围"> </div> <div class="form-group"> <label for="job_modify_ask">要求</label> <input type="text" class="form-control" id="job_modify_ask" placeholder="招聘要求"> </div> <div class="form-group"> <label for="company_modify_name">公司名称</label> <input type="text" class="form-control" id="company_modify_name" placeholder="请输入公司名称"> </div> <div class="form-group"> <label for="logo_modify">上传公司logo</label> <input type="file" id="logo_modify" multiple> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" id="modify_btn" data-dismiss="modal">修改</button> </div> </div> </div> </div> </div> </body> </html> <script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script> <script src="https://cdn.bootcss.com/js-cookie/2.2.0/js.cookie.min.js"></script> <script src="../js/js/common/auth.js"></script> <script src="../js/js/detail/logout.js"></script> <script src="../js/js/detail/joblist.js"></script> <script src="../js/js/detail/tabbar.js"></script> <script src="../js/js/detail/job.js"></script> <script src="../js/js/detail/modify.js"></script>
静态布局就不详细说了,毕竟审美不同,可以随意调的,这个随意,主要是理一下JS代码:
public文件夹---detail---js-----job.js
function Job() { this.content = $(".content"); this.init() } Job.template = ` <form> <div class="form-group"> <label for="job_name">职位名称</label> <input type="text" class="form-control" id="job_name" placeholder="请输入职位名称"> </div> <div class="form-group"> <label for="job_price">薪资</label> <input type="text" class="form-control" id="job_price" placeholder="薪资范围"> </div> <div class="form-group"> <label for="job_ask">要求</label> <input type="text" class="form-control" id="job_ask" placeholder="招聘要求"> </div> <div class="form-group"> <label for="company_name">公司名称</label> <input type="text" class="form-control" id="company_name" placeholder="请输入公司名称"> </div> <div class="form-group"> <label for="logo">上传公司logo</label> <input type="file" id="logo" multiple> </div> <button type="button" class="btn btn-default" id="js_jobBtn">提交</button> </form> ` //原型对象 Job.prototype = { init(){ this.createDom(); this.JobClick(); }, //动态渲染到页面中: createDom(){ this.el = $("<div class='from'></div>"); this.el.append(Job.template); this.content.html(this.el) }, //点击提交,上传数据: JobClick(){ this.el.find("#js_jobBtn").on("click",$.proxy(this.handleJobCB,this)) }, handleJobCB(){ //创建formData 模拟表单提交数据 兼容性问题 此处用模拟表单提交数据主要是因为要上传图片 //获取要上传的数据: var formData = new FormData(); var jobName = this.el.find("#job_name"); var jobPrice = this.el.find("#job_price"); var jobAsk = this.el.find("#job_ask"); var companyName = this.el.find("#company_name"); var logo = this.el.find("#logo"); //append key val key是服务端接收的key值 formData.append("jobname",jobName.val()); formData.append("jobprice",jobPrice.val()); formData.append("jobask",jobAsk.val()); formData.append("companyname",companyName.val()); formData.append("companylogo",logo[0].files[0]); //通过ajax将请求的数据发送给服务器: $.ajax({ type:"post", url:"/job/addjob", data:formData, cache:false, //不读取缓存中的结果 true的话会读缓存 其实post本身就不会读取缓存中的结果 contentType:false, //数据编码格式不使用jquery的方式 为了避免 JQuery 对其操作,从而失去分界符,而使服务器不能正常解析文件。 processData:false,//默认情况下,通过data选项传递进来的数据,如果是一个对象(技术上讲只要不是字符串),都会处理转化成 //一个查询字符串,以配合默认内容类型 "application/x-www-form-urlencoded"。如果要发送 DOM 树信息或其它不希望转换的 //信息,请设置为 false。 success:$.proxy(this.handleSuccess,this) }) }, handleSuccess(data){ if(data.status){ alert("添加成功"); new JobList(); } } }