准备
mysql-ithm的使用
安装npm i mysql-ithm
使用
参照文档即可。
简单示例
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| const hm = require("mysql-ithm");
hm.connect({ host: "localhost", port: "3306", user: "root", password: "root", database: "cqmanager502", });
let herotModel = hm.model("hero", { heroName: String, heroSkill: String, });
var arr = [ { heroName: "张三10", heroSkill: "吃吃吃" }, { heroName: "张三11", heroSkill: "吃吃吃" }, { heroName: "张三12", heroSkill: "吃吃吃" }, ];
herotModel.insert(arr, (err, results) => { console.log(err); console.log(results); if (!err) console.log("增加成功"); });
|
抓包入口
发起两个请求
只需要实例化第二个请求,在第一个请求中使用crawler.queue
方法即可。
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 26 27 28 29 30 31 32 33 34 35 36
| var c = new Crawler({ maxConnections: 10, callback: function (error, res, done) { if (error) { console.log(error); } else { var $ = res.$; JSON.parse(res.body).forEach((v) => { xq.queue(`https://pvp.qq.com/web201605/herodetail/${v.ename}.shtml`); }); } done(); }, }); c.queue("https://pvp.qq.com/web201605/js/herolist.json");
var xq = new Crawler({ maxConnections: 10, callback: function (error, res, done) { if (error) { console.log(error); } else { var $ = res.$; heroes.push({ heroName: $(".cover-name").text(), heroSkill: $(".skill-name>b").first().text(), heroIcon: "https:" + $(".ico-play").prev("img").attr("src"), isDelete: false, }); } done(); }, });
|
入库
应等待所有请求完成后在进行入库操作。
1 2 3 4 5
| xq.on("drain", function () { heroModel.insert(heroes, (err, results) => { if (!err) console.log("增加成功"); }); });
|
完整代码
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| var Crawler = require("crawler"); var hm = require("mysql-ithm");
var c = new Crawler({ maxConnections: 10, callback: function (error, res, done) { if (error) { console.log(error); } else { var $ = res.$; JSON.parse(res.body).forEach((v) => { xq.queue(`https://pvp.qq.com/web201605/herodetail/${v.ename}.shtml`); }); } done(); }, }); c.queue("https://pvp.qq.com/web201605/js/herolist.json");
var heroes = [];
var xq = new Crawler({ maxConnections: 10, callback: function (error, res, done) { if (error) { console.log(error); } else { var $ = res.$; heroes.push({ heroName: $(".cover-name").text(), heroSkill: $(".skill-name>b").first().text(), heroIcon: "https:" + $(".ico-play").prev("img").attr("src"), isDelete: false, }); } done(); }, });
xq.on("drain", function () { heroModel.insert(heroes, (err, results) => { console.log(err); console.log(results); if (!err) console.log("增加成功"); }); });
hm.connect({ host: "localhost", port: "3306", user: "root", password: "root", database: "cqmanager", });
let heroModel = hm.model("hero", { heroName: String, heroSkill: String, heroIcon: String, isDelete: String, });
|
项目服务端
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 | 无 | 无 |
后端基本架构
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| const express = require("express");
const app = express();
app.use(express.static("www"));
app.get("/hero/list", (req, res) => { res.send("sb"); });
app.get("/hero/info", (req, res) => { res.send("sb"); });
app.post("/hero/update", (req, res) => { res.send("sb"); });
app.post("/hero/delete", (req, res) => { res.send("sb"); });
app.post("/hero/add", (req, res) => { res.send("sb"); });
app.get("/captcha", (req, res) => { res.send("sb"); });
app.post("/user/register", (req, res) => { res.send("sb"); });
app.post("/user/login", (req, res) => { res.send("sb"); });
app.get("/logout", (req, res) => { res.send("sb"); });
const port = 3000; app.listen(port, () => console.log(`Server running at http://127.0.0.1:${port}`) );
|
查询英雄列表
考虑是否传入了参数
此接口代码
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| app.get("/hero/list", (req, res) => { let { search } = req.query; console.log(search); if (!search) { heroModel.find('isDelete="false"', (err, results) => { if (err) { res.send({ code: 500, msg: "服务器错误" + err, }); } else { res.send({ code: 200, heros: results, }); } }); } else { heroModel.find( `heroName like "%${search}%" and isDelete="false"`, (err, results) => { if (err) { res.send({ code: 500, msg: "服务器错误" + err, }); } else { res.send({ code: 200, heros: results, }); } } ); } });
|
新增英雄
由于英雄是有头像的,因此接收参数需要用到multer
模块
其余参数可使用req.body
接收。
将其插入到数据库
需要对isDelete
设置默认值。
此接口代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| app.post("/hero/add", upload.single("heroIcon"), (req, res) => { let { heroName, heroSkill, isDelete = "false" } = req.body; let heroIcon = req.file.filename; 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进行查询
为了防止恶意查询,应设置所查询的ID字段没有被删除
没有错误,且有数据
返回该数据
有错误
返回错误信息
无错误,但没有英雄
返回提示
此接口代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| app.get("/hero/info", (req, res) => { let { id } = req.query; 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: "服务器内部错误" + err, }); } else { res.send({ code: 201, mgs: "没有此英雄,或已被删除!", }); } }); });
|
编辑英雄
- 根据前端传来的参数,判断是否传入了图片
- 根据ID进行对应的修改
此接口代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| app.post("/hero/update", upload.single("heroIcon"), (req, res) => { let { id, heroName, heroSkill } = req.body; let obj = { heroName, heroSkill, }; if (req.file != undefined) { obj.heroIcon = req.file.filename; } heroModel.update(`id=${id}`, obj, (err, results) => { if (err) { res.send({ code: 500, msg: "服务器内部错误" + err, }); } else { res.send({ code: 200, msg: "修改成功", }); } }); });
|
英雄删除
- 接收传来的ID
- 软删除,而不是真的删除
此接口代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| app.post("/hero/delete", (req, res) => { let = { id } = req.body; hm.update(`id=${id}`, { isDelete: "true" }, function (err, results) { if (err) { res.send({ code: 500, msg: "服务器内部错误" + err, }); } else { res.send({ code: 200, msg: "删除成功", }); } }); });
|
生成验证码
- 安装插件
svg-captcha
- 验证码实际返回的是一个svg的图片
此接口代码
1 2 3 4 5 6 7 8
| const svgCaptcha = require("svg-captcha"); app.get("/captcha", (req, res) => { var captcha = svgCaptcha.create(); res.type("svg"); res.status(200).send(captcha.data); });
|
注册用户
建立一个表格,用于存放用户信息
进入路由逻辑后,首先判断验证码是否正确
可以通过一个变量接收验证码的值
当验证码正确时,应首先验证数据库无此用户,然后在进行逻辑处理
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| app.post("/user/register", (req, res) => { let { username, password, code } = req.body; console.log(username, password, code); if (code.toLocaleLowerCase() != captchaCode.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: "注册成功", }); } } ); } } }); } });
|
用户登录
- 只需要查询用户名与密码匹配的数据即可
核心代码
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 26
| app.post("/user/login", (req, res) => { let { username, password } = req.body; userModel.find( `username="${username}" and password="${password}"`, (err, results) => { if (err) { res.send({ code: 500, msg: "服务器内部错误" + err, }); } else { if (results.length > 0) { res.send({ code: 200, msg: "登录成功", }); } else { res.send({ code: 401, msg: "账号或密码错误", }); } } } ); });
|
加入Cookie登录验证
cookie使用cookie-session
可以使用模块。
1 2 3 4 5 6 7 8
| const cookieSession = require("cookie-session"); app.use( cookieSession({ name: "session", keys: ["123", "456", "xiaokang"], maxAge: 24 * 69 * 60 * 1000, }) );
|
由于多个页面都是需要判断用户是否登陆,因此需要单独写一个接口,用于判断用户有没有登陆。
1 2 3 4
| app.get("/isLogin", (req, res) => { res.send(req.session.user); });
|
当用户登陆后会返回一个设定的值,否则返回空字符串。
退出登陆
- 退出登陆即删除cookie
核心代码
1 2 3 4 5 6 7
| app.get("/logout", (req, res) => { req.session = null; res.writeHead(302, { Location: "login.html", }); res.end(); });
|
项目客户端
首页
进入页面
点击查询按钮
查询数据框内的内容
效果展示
核心代码
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <script id="cq" type="text/html"> {{ each heros v }} <tr> <td>{{ v.heroName }}</td> <td>{{ v.heroSkill }}</td> <td><img src="{{ v.heroIcon }}" alt="" /></td> <td> <button class="btn btn-success btn-edit" onclick='location.href="./update.html?id={{v.id}}"'>编辑🍞</button> <button class="btn btn-danger btn-delete" data-id='{{v.id}}'>删除👍</button> </td> </tr> {{ /each }}
</script>
<script> $.ajax({ type: 'get', url: '/hero/list', success: function (backData) { if (backData.code == 200) { var resHtml = template('cq', backData) $('tbody').html(resHtml) } } }); $('#searchBtn').on('click', function (e) { e.preventDefault() let search = $('#search').val().trim() $.ajax({ type: 'get', url: '/hero/list', data: { search }, success: function (backData) { if (backData.heros.length == 0) { $('tbody').html('没有数据') return } if (backData.code == 200) { var resHtml = template('cq', backData) $('tbody').html(resHtml) } } }); }) </script>
|
新增页面
- 点击新增按钮,跳转新增页面
- 输入英雄昵称、技能与头像后点击提交
- 将输入内容新增,并且跳回首页
效果展示
核心代码
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 26 27 28 29 30 31
| <script> $(function () { $('#heroIcon').on('change', function () { var file = this.files[0] var url = URL.createObjectURL(file) $('.preview').attr('src', url) }) $('.btn-add').on('click', function (e) { e.preventDefault() var fd = new FormData(document.querySelector('form')) $.ajax({ type: 'post', url: '/hero/add', data: fd, contentType: false, processData: false, success: function (backData) { if (backData.code == 200) { alert('新增成功') window.location.href = '/' } } }) }) }) </script>
|
根据ID查询英雄(编辑第一步)
- 根据id查询英雄是放在编辑页面展示,也就是说进入编辑页首先发送Ajax请求,获取英雄的信息
效果展示
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script> $(function () { var id = window.location.search.split('=')[1] $.ajax({ url: '/hero/info', type: 'get', data: { id }, success: function (backData) { if (backData.code == 200) { $('#id').val(id) $('#name').val(backData.data.heroName) $('#skill').val(backData.data.heroSkill) $('#iconImg').attr('src', backData.data.heroIcon) } } })
}) </script>
|
编辑英雄
- 与新增英雄较为类似,获取formData数据后提交即可
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| $('#heroIcon').on('change', function () { var file = this.files[0] var url = URL.createObjectURL(file) $('#iconImg').attr('src', url) })
$('.btn-save').on('click', function (e) { e.preventDefault() var fd = new FormData(document.querySelector('form')) $.ajax({ type: 'post', url: '/hero/update', contentType: false, processData: false, data: fd, success: function (backData) { if (backData.code == 200) { alert('编辑成功') window.location.href = '/' } } }) })
|
删除英雄
- 点击按钮后,发送POST请求即可
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| app.post("/hero/delete", (req, res) => { let = { id } = req.body; heroModel.update(`id=${id}`, { isDelete: "true" }, function (err, results) { if (err) { res.send({ code: 500, msg: "服务器内部错误" + err, }); } else { res.send({ code: 200, msg: "删除成功", }); } }); });
|
显示验证码
- 只需要将图片的路径设置为验证码接口即可
- 但需要注意的是
img
标签有缓存,如果路径相同则不发送请求。 - 因此解决这个问题只需要在请求时加入一个随机参数即可,而参数值使用随机数即可
效果展示
核心代码
1 2 3 4 5 6 7
| <script> $(function () { $('.vCode').on('click', function () { $(this).attr('src', '/captcha?xc=' + Math.random()) }) }) </script>
|
注册用户
- 获取输入框的内容,发送Ajax请求即可。
- 但是发送的密码是明文,因此需要进行加密处理(MD5)
效果展示
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| $('.register').on('click', function (e) { e.preventDefault() let username = $('#username').val().trim() $('#password').val(md5($('#password').val().trim(), 'yingyingguaiguai')) let password = $('#password').val().trim() let code = $('#code').val().trim() $.ajax({ type: 'post', url: '/user/register', data: { username, password, code }, success: function (backData) { if (backData.code == 200) { alert(backData.msg); window.location.href = '/' } else { alert(backData.msg) } } }) })
|
用户登录
- 获取输入框的内容,发送Ajax请求即可。
- 但是发送的密码是明文,因此需要进行加密处理(MD5)
效果展示
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| $(function () { $('.login').on('click', function (e) { e.preventDefault() let username = $('#username').val().trim() $('#password').val(md5($('#password').val().trim(), 'yingyingguaiguai')) let password = $('#password').val().trim() $.ajax({ type: "post", url: '/user/login', data: { username, password }, success: function (backData) { if (backData.code == 200) { window.location.href = '/' } else { alert(backData.msg) } } }) }) })
|
判断是否登陆
- 因为已经存在了一个接口用于判断是否登陆,因此在页面加载后自动发送请求判断是否登陆即可。
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script> (function () { $.ajax({ type: 'get', url: '/isLogin', success: function (backData) { if (backData == '') { alert('没有登陆') window.location.href = '/login.html' } } }) })() </script>
|
涉及概念
COOKIE工作流程
一个简单的演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const express = require("express");
const app = express();
app.get("/login", (req, res) => { res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8", "Set-Cookie": "userid=123456", }); res.end(); });
app.get("/list", (req, res) => { console.log(req.headers); res.send("sb"); }); app.listen(8086, () => { console.log("服务器已开启"); });
|