英雄管理系统
项目要求
a.注册页要有验证码,账号重复、密码重复、验证码错误要提示
b.登录页面用户名和密码要正确,不正确要提示
c.进入主页面,显示查找、显示新增、头像、姓名、技能、操作(编辑和删除)
d.点击查找可以快速找到列表中的信息,没有信息要显示
e.编辑和新增包括姓名、技能、头像、保存
f.点击删除,删除的头像、姓名、技能、操作(编辑和删除)在后端保存,不能够真正的删除(软删除)
g.页面要求简洁、美观、大方
项目代码下载
实现效果展示
- 总体效果
- 注册页面
- 登录界面
- 查找界面
- 新添界面
- 编辑界面
- 删除界面
项目准备阶段
- 首先,我们要从王者荣耀官网上爬取一定数量的内容,为之后做准备爬取王者荣耀.js
- 导包
const express = require(“express”);
const hm = require(“mysql-ithm”);
const cors = require(“cors”);
const multer = require(“multer”);
var bodyParser = require(“body-parser”);
var svgCaptcha = require(‘svg-captcha’); //验证码插件
const cookieSession = require(“cookie-session”);
- 连接数据库
hm.connect({
host: 'localhost',//数据库地址
port: '3306',
user: 'root',//用户名,没有可不填
password: '123',//密码,没有可不填
database: 'cqmanager503'//数据库名称
});
- 创建英雄模型和用户模型
// 英雄模型
let heroModel = hm.model("hero", {
heroName: String,
heroSkill: String,
heroIcon: String,
isDelete: String,
});
// 用户模型
let userModel = hm.model('users', {
username: String,
password: String
});
- 托管静态资源
app.use(express.static('www'));
app.use(express.static('uploads'));
app.use(express.static('static'));
- 配置中间件
app.use(cors());
// 4.1 body-parser:解析post参数,给req添加属性body,存储解析好的post参数
app.use(bodyParser.urlencoded({ extended: false }));
// 4.4 cookie-session中间件:给req添加session成员
app.use(cookieSession({
name: 'session',
keys: ['1', '2'],//加密(盐)
// 有效期
maxAge: 7 * 24 * 60 * 60 * 1000 // 7*24 hours
}));
- 开启服务器
app.listen(4399, () => {
console.log('服务器开启成功');
});
参数说明
- API接口
接口说明 | URL | 请求方式 | 请求参数 | 返回值 |
---|---|---|---|---|
查询英雄列表 | /hero/list | get | search :英雄名称,不传返回所有 | [heros:{英雄列表}] |
查询英雄详情 | /hero/info | get | id:英雄id | {data:英雄详情} |
编辑英雄 | /hero/update | post | name,skill, icon,id | {code: 200) |
删除英雄 | /hero/delete | post | id | {code: 200) |
新增英雄 | /hero/add | post | name,skill, icon | {code: 200) |
验证码 | /captcha | get | 无 | 验证码图片 |
用户注册 | /user/register | post | username, password, code:验证码 | {code:200|401|402} |
用户登录 | /user/login | post | username, password | {code:200|401|402} |
退出登录 | /logout | get | 无 | 无 |
- 服务器
服务器说明 | 作用描述 |
---|---|
http://127.0.0.1:4399 | 服务器基地址 |
200 | 请求成功状态码 |
401 | 用户名已存在或者用户名错误 |
402 | 密码错误或者验证码错误 |
500 | 服务器内部错误 |
302 | 服务器重定向 |
服务端流程
查询英雄列表
请求并且判断是否传入参数
- 没有传入查询参数
- 要查询所有的英雄
- 判断是否查询到
- 查询到
- 返回服务器内部错误
- 没有查询到
- 返回内部
- 查询到
- 判断是否查询到
- 要查询所有的英雄
- 有传入查询参数
模糊查询
,还有一个并且语法 and isDelete = ‘false’- 判断是否查询到
- 查询到
- 返回服务器内部错误
- 没有查询到
- 返回内部
- 查询到
- 判断是否查询到
// 5.1 查询英雄列表(查询没有删除所有的英雄)
app.get('/hero/list', (req, res) => {
// (1) 请求
let { search } = req.query;
//console. log(search);//有查询参数就是有值的,没有查询参数就是undefined.
// b.判断
// (2) 处理(查询数据库)
if (!search) {
//如果进到这里来了, 说明没有查询参数,我就要查询出所有的英雄。
heroModel.find('isDelete="false" ',(err, results) => {
if (err) {
res.send({
code: 500,
msg: '服务器内部错误'
});
} else {
// (3) 响应
res.send({
code: 200,
heros: results
})
}
});
} else {
// 如果有search,则根据条件查询数据(包含查询)
heroModel.find(`heroname like "%${search}%" and isDelete="false"`, (err, results) => {
if (err) {
res.send({
code: 500,
msg: '服务器内部错误'
});
} else {
// (3) 响应
res.send({
code: 200,
heros: results
})
}
});
}
});
新增英雄
这里需要用到
multer
模块
app.post(’/profile’, upload.single(‘avatar’), function (req, res, next) {
// req.file is theavatar
file
// req.body will hold the text fields, if there were any
})
- 这里需要接收用户新增的英雄名,英雄技能,英雄头像
- 将接受到的参数插入到数据库中
- 判断是否接受到
- 接收到
- 返回新增成功
- 没有接收到
- 返回服务器内部错误
- 接收到
- 判断是否接受到
// 5.5 新增英雄
app.post("/hero/add", upload.single("heroIcon"), (req, res) => {
//a.接收用户新增的英雄名,英雄技能,英雄头像
let { heroName, heroSkill, isDelete = "false" } = req.body;
let heroIcon = req.file.filename;
//b.插入到数据库中
heroModel.insert(
{ heroName, heroSkill, heroIcon, isDelete },
(err, results) => {
if (err) {
res.send({
code: 500,
msg: "服务器内部错误" + err,
});
} else {
res.send({
code: 200,
msg: "新增成功",
});
}
}
);
});
根据 ID 查询英雄详情
- 接收传递过来的英雄ID
- 根据id查询英雄的详细信息
- 判断查询信息
- 没有错误,且有数据
- 则返回
data: results[0]
- 则返回
- 有错误
- 返回服务器内部错误
- 无错误,但没有英雄
- 返回没有此英雄,或已被删除!
- 没有错误,且有数据
- 判断查询信息
// 5.2 查询英雄的详情(编辑的第一步:根据ID查询,显示到编辑页)
app.get("/hero/info", (req, res) => {
// a. 接收传递过来的英雄ID
let { id } = req.query;
// b.根据id查询英雄的详细信息
heroModel.find(`id=${id} and isDelete="false"`, (err, results) => {
if (err == null && results.length != 0) {
res.send({
code: 200,
data: results[0],
});
} else if (err) {
res.send({
code: 500,
mgs: "服务器内部错误"
});
} else {
res.send({
code: 201,
mgs: "没有此英雄,或已被删除!",
});
}
});
});
英雄编辑
- 判断是否修改了头像
- 修改了头像
- obj 对象里面就有id, heroName,heroSkill , heroIcon
- 没有修改头像
- obj 对象里面就只有id,heroName, heroSkill
- 修改了头像
这里要调用一下mysq1-ithm模块,使用其中的方法吧数据更新带数据库中
- 判断是否更新到数据库
- 更新了
- 返回修改成功
- 没有更新
- 返回服务器内部错误
- 更新了
// 5.3 编辑英雄
app.post("/hero/update", upload.single("heroIcon"), (req, res) => {
//如果不改头像,只改名字和技能.我们希望这种需求也是可以的。
//赋值
let { id, heroName, heroSkill } = req.body;
let obj = {
heroName,
heroSkill,
};
//如果修改了头像,那obj对象里面就有id, heroName,heroSkill , heroIcon
//如果没有修改头像,那obj对象里面就只有id,heroName, heroSkill
if (req.file != undefined) {
//能够进到这里来 ,说明传了修改后的头像进来。
obj.heroIcon = 'http://127.0.0.1:4399/' + req.file.filename;
}
//调用mysq1-ithm模块中的方法,把数据更新到数据库中.
heroModel.update(`id=${id}`, obj, (err, results) => {
if (err) {
res.send({
code: 500,
msg: "服务器内部错误"
});
} else {
res.send({
code: 200,
msg: "修改成功",
});
}
});
});
英雄删除(软删除)
实际上修改的是当前id的英雄isDelete为true
- 接收前端传递过来的要删除的英雄id
- 调用mysql-ithm模块中的方法,来完成
- 判断是否删除英雄(改为true)
- isDelete为true
- 返回删除成功
- isDelete不为true
- 返回服务器内部错误
- isDelete为true
- 判断是否删除英雄(改为true)
// 5.4 删除英雄(软删除,实际上修改的是当前id的英雄isDelete为true)
app.post("/hero/delete", (req, res) => {
// a.接收前端传递过来的要删除的英雄id
let { id } = req.body;
//b.调用mysql-ithm模块中的方法,来完成
heroModel.update(`id=${id}`, { isDelete: "true" }, (err, results)=> {
if (err) {
res.send({
code: 500,
msg: "服务器内部错误"
});
} else {
res.send({
code: 200,
msg: "删除成功"
});
}
});
});
验证码
利用
svg-captcha
:验证码插件(因为不是每个地方都会用到,不需要像中间件那样要用app.use())
- 创建一个验证码
- 返回验证码(实际上就是一个svg格式的图片)
- 响应给客户端
// 5.6 验证码
// svg-captcha:验证码插件(因为不是每个地方都会用到,不需要像中间件那样要用app.use())
// (2)声明全局变量存储验证码文本
//b.搞一个全局变量来保存一下这个验证码文字
let captchaText = '';
//c.使用
app.get('/captcha', (req, res) => {
// //创建一个验证码
var captcha = svgCaptcha.create();
// console.log(captcha);
// 文本:服务器存起来用于注册接口验证
//返回验证码
captchaText = captcha.text;
// 图片:响应给客户端
// 返回验证码,实际上就是一个svg格式的图片
res.type('svg');
res.status(200).send(captcha.data);
});
用户注册
- 获取前端传递过来的用户名/加密的密码/验证码
- 验证验证码输入的是否正确
- 不正确
- 返回验证码错误
- 正确
- 判断用户名是否已经被注册
- 无法判断
- 返回服务器内部错误-查询用户名是否存在
- 可以判断
- 如果用户名存在
- 返回用户名已存在
- 如果用户名不存在
- 判断是否接受
- 接受到
- 返回注册成功
- 无法接受
- 返回服务器内部错误-注册时错误
- 接受到
- 判断是否接受
- 如果用户名存在
- 无法判断
- 判断用户名是否已经被注册
- 不正确
常用的加密方式
// 5.7 用户注册
app.post("/user/register", (req, res) => {
//a.获取前端传递过来的用户名/加密的密码/验证码
let { username, password, code } = req.body;
console.log(username, password, code);
//b.验证验证码输入的是否正确.
if (code.toLocaleLowerCase() != captchaText.toLocaleLowerCase()) {
res.send({
code: 402,
msg: "验证码错误",
});
} else {
//进到这里来了, 说明验证码是对的,那就要开始注册了.
//要不要去数据库中判断一下以前这个用户名是否已经被注册了?
userModel.find(`username="${username}"`, (err, results) => {
if (err) {
res.send({
code: 500,
msg: "服务器内部错误-查询用户名是否存在",
});
} else {
//说明可以查询成功,但是还要看看查出来有没有结果.
if (results.length > 0) {
// 用户名是否存在
res.send({
code: 401,
msg: "用户名已存在",
});
} else {
//该用户名不存在,不存在就往册呀(增加/插入操作)
userModel.insert(
{
username,
password,
}, (err, results) => {
// 注册时的错误判断
if (err) {
res.send({
code: 500,
msg: "服务器内部错误-注册时错误'",
});
} else {
res.send({
code: 200,
msg: "注册成功",
});
}
}
);
}
}
});
}
});
用户登录
- 先获取用户登录时候,传递过来的用户名和加密后的密码
- 去数据库中判断有没有这样的账号和密码.
- 判断是否查询成功
- 查询失败
- 返回服务器内部错误
- 查询成功
- 返回是否有数据
- 有数据
- 返回登录成功
- 没有数据
- 返回账号或密码错误
- 有数据
- 返回是否有数据
- 查询失败
- 判断是否查询成功
// 5.8 用户登录
app.post("/user/login", (req, res) => {
//a.先获取用户登录时候,传递过来的用户名和加密后的密码
let { username, password } = req.body;
//b.去数据库中判断有没有这样的账号和密码.
userModel.find(`username="${username}" and password="${password}"`, (err, results) => {
//判断
if (err) {
res.send({
code: 500,
msg: "服务器内部错误",
});
} else {
//判断查成功的结果,有没有数据
if (results.length > 0) {
// 发送的session的键是user,值是一个对象,对象里面包含账号密码.
req.session.user = { username, password };
res.send({
code: 200,
msg: "登录成功",
});
} else {
res.send({
code: 401,
msg: "账号或密码错误",
});
}
}
});
});
检测是否登录
- 判断是否登录
- 登录了
- 响应回去的就是有内容的
- 没有登录
- 响应回去的就是没有内容的
- 登录了
// 5.0 判断有没有登录
app.get("/isLogin", (req, res) => {
//如果你登录了, 那你响应回去的就是有内容的.
//如果你没有登录,那你响应回去的就是没有内容的.
res.send(req.session.user);
});
退出登录
- 清除session
- 使用重定向技术刷新首页
// 5.9 退出登录
app.get('/logout', (req, res) => {
// (1) 清除session
req.session = null;
// (2) 使用重定向技术刷新首页
res.writeHead(302, {
Location: './login.html'
});
res.end();
});
客户端流程
查询英雄列表
- 获取传递过来的英雄id
- 发送ajax请求
- 对应的信息显示在对应的页面标签上,
// 一、根据ID发送Ajax请求,获取详细信息 并显示在页面标签上
//1. 获取传递过来的英雄id
var id = window.location.search.split('=')[1]
//2.发送ajax请求
$.ajax({
url: 'http://127.0.0.1:4399/hero/info',
type: 'get',
data: { id },
success: function (backData) {
//对应的信息显示在对应的页面标签上,
if (backData.code == 200) {
$('#id').val(id)//隐藏域存放id
$('#name').val(backData.data.heroName)
$('#skill').val(backData.data.heroSkill)
$('#iconImg').attr('src', backData.data.heroIcon)
}
}
})
新增英雄
- 图片预览
- 获取用户选择的图片
- 创建一个url
- 把url赋值给预览用的img的src属性
- 完成新增
- 给新增按钮设置一个点击事件
- 去掉默认提交行为
- 创建一个FormData对象,获取新增的英雄的信息(名字,技能,头像)
- 发送ajax请求
- 跳转首页
// 入口函数
$(function () {
// 1. 图片预览
$('#icon').on('change', function () {
// 1. 获取用户选择的图片
var file = this.files[0]
// 2. 创建一个url
var url = URL.createObjectURL(file)
// 3. 把url赋值给预览用的img的src属性
$('#iconImg').attr('src', url)
})
// 2. 完成新增
//1.给新增按钮设置一个点击事件
$('.btn-insert').on('click', function (e) {
//2.去掉默认提交行为
e.preventDefault()
//3.创建一个FormData对象,获取新增的英雄的信息(名字,技能,头像)
// var fd = new FormData(document.querySelector('form'))
var fd = new FormData($('#form')[0])
//form表单是dom元素,form表单中需要获取数据的标签都应该有name属性并且name属性的值应该和接口参数值一致
//4.发送ajax请求.
$.ajax({
url:'http://127.0.0.1:4399/hero/add',
type:'post',
dataType:'json',
data:fd,
contentType: false,
processData: false,
success: function(backData){
console.log(backData);
if (backData.code==200) {
alert('新增成功');
// 跳转首页
window.location.href='./index.html';
}
}
});
})
})
根据 ID 查询英雄详情
- 给查询按钮设置一个点击事件
- 去掉默认的提交行为
- 获取用户输入的要查询的关键词
- 发送ajax请求,获取数据
- 通过模板引擎渲染页面
//二:查询符合条件(英雄名包含查询关键词)的所有英雄
//1.给查询按钮设置一个点击事件.
$('#searchBtn').on('click', function (e) {
//2.去掉默认的提交行为
e.preventDefault()
//3.获取用户输入的要查询的关键词
let search = $('#search').val().trim()
//4.发送ajax请求,获取数据
$.ajax({
type: 'get',
url: 'http://127.0.0.1:4399/hero/list',
data: { search },
success: function (backData) {
// console.log(backData);
if(backData.heros.length == 0){
$('tbody'). html ('没有数据');
return;
}
if(backData.code == 200){
//5.通过模板引擎渲染
// 调用模板引擎渲染页面
var resHtml = template('cq',backData);
$('tbody'). html (resHtml);
} else {
alert('出问题了')
}
}
});
})
英雄编辑
- 给编辑按钮设置一个点击事件
- 去掉默认的提交行为
- 获取編辑后的英雄信息(FormData对象获取)
- 发送ajax请求,完成編辑
- 完成了编辑,提示一下跳转到首页
// 二:头像预览
$('#heroIcon').on('change', function () {
var file = this.files[0]
var url = URL.createObjectURL(file)
$('#iconImg').attr('src', url)
})
// 保存按钮事件
// 1. 给编辑按钮设置一个点击事件
$('.btn-save').on('click', function (e) {
//2.去掉默认的提交行为
e.preventDefault()
//3.获取編辑后的英雄信息(FormData对象获取)
var fd = new FormData(document.querySelector('form'))
// form表单是dom元素,要获取值的标签要有name属性,name属性的值要和接口参数一致
//4.发送ajax请求,完成編辑
$.ajax({
type: 'post',
url: 'http://127.0.0.1:4399/hero/update',
// data是FormData对象,不要忘记2个false.
contentType: false,
processData: false,
data: fd,
success: function (backData) {
if (backData.code == 200) {
//5.完成了编辑,提示一下跳转到首页
alert('编辑成功')
window.location.href = '/index.html'
}
}
})
})
英雄删除
- 注册点击事件
- 获取当前点击的这个删除按钮自定义属性保存的英雄id
- 发送ajax请求,把id传过去
- 删除成功了,就重 新加载一下数据,或者刷新一下页面
//1.注册点击事件.
$('tbody').on('click', '.btn-delete', function () {
if (confirm( '你确定要删除吗?')) {
//2.获取当前点击的这个删除按钮自定义属性保存的英雄id
var id = $(this).attr('data-id')
//3.发送ajax请求,把id传过去
$.ajax({
type: 'post',
url: 'http://127.0.0.1:4399/hero/delete',
data: {
id
},
success: function (backData) {
if (backData.code == 200) {
//4. 删除成功了,就重 新加载一下数据,或者刷新一下页面
alert('删除成功')
getData()
}
}
})
}
})
验证码
- 点击验证码图片刷新验证码
- 验证码不能使用ajax发送请求
- 原因:验证码服务器返回的不是字符串,而是图片二进制数据
- 验证码只需重设img标签的src属性即可
- 问题:img标签自带缓存功能,如果src一致,则只发一次请求
- 解决方案:添加随机参数
- 验证码不能使用ajax发送请求
// 1.点击验证码图片刷新验证码
$('.code').on('click', function () {
$(this).attr('src', 'http://127.0.0.1:4399/captcha?sb=' + Math.random());
});
用户注册
- 注册按钮的点击事件
- 取消默认的提交行为
- 获取用户注册时候输入的用户名/密码/验证码
- 在密码加密之后获取密码,获取到的就是密文
- 发送ajax请求,完成注册
//二:注册
// 2.注册按钮的点击事件
$('#register').on('click', function (e) {
// 2.取消默认的提交行为
e.preventDefault();
// 3.获取用户注册时候输入的用户名/密码/验证码
let username = $('#username').val().trim()//账号
let code = $('#code').val().trim()//验证码
$('#password').val(md5($('#password').val().trim(), 'dg'))//密码
//在密码加密之后获取密码,获取到的就是密文
let password = $('#password' ).val().trim();
console . log(password) ;
// md5加密 md5();
// 第一个参数:要加密的明文
// 第二个参数:加盐(作用:增加密码的复杂度;特点:同样的明文,盐不同,得到的密文不同)
// 细节:尽量不在控制台打印用户的明文和密码
// let password = $('#password').val();
// let miwen = md5(password,'jane');
// 取出表单数据,加密完之后,重新赋值给表单
/*
FormData:(1)自动获取每个表单的name和value拼接成参数 (2)上传文件
$('#form').serialize():(1)自动获取每个表单的name和value拼接成参数
*/
//4.发送ajax请求,完成注册
$.ajax({
url: 'http://127.0.0.1:4399/user/register',
type: 'post',
// dataType: 'json',
data: {
username,
password,
code
},
success: function (backData) {
if (backData.code == 200) {
alert('注册成功');
// 跳转首页
window.location.href = "./login.html";
} else {
alert(backData.msg);
}
}
});
})
用户登录
- 给登录按钮设置一个点击事件
- 去掉默认的提交行为
- 获取用户输入的账号和密码(要和注册的时候一样的加密规则的加密后的密码)
- 把用户输入的密码用md5加盐的方式加密后,重新赋值给密码输入框
- 发送ajax请求,完成登录
$(function () {
//一:登录
//1.给登录按钮设置一个点击事件
$('.btn-login').on('click', function (e) {
//2.去掉默认的提交行为
e.preventDefault()
//3.获取用户输入的账号和密码(要和注册的时候一样的加密规则的加密后的密码)
let username = $('#username').val().trim()
// 把用户输入的密码用md5加盐的方式加密后,重新赋值给密码输入框
$('#password').val(md5($('#password').val().trim(), 'dg'))
let password = $('#password').val().trim()
//4.发送ajax请求,完成登录
$.ajax({
type: "post",
url: 'http://127.0.0.1:4399/user/login',
data: { username, password },
success: function (backData) {
if (backData.code == 200) {
alert('登录成功');
window.location.href = './index.html'
} else {
alert(backData.msg)
}
}
})
})
})
检测是否登录
在页面加载后自动发送请求判断是否登陆
<script>
(function () {
$.ajax({
type: 'get',
url: '/isLogin',
success: function (backData) {
if (backData == '') {
alert('没有登陆')
window.location.href = '/login.html'
}
}
})
})()
</script>