前后端登录注册之node剖析与token的使用状态

登录模块功能详解
1、用户名密码的格式验证
由前端完成,根据需求自行决定,不加叙述
2、点击提交按钮思路详解
前端将用户名 以及加密后的密码还有验证码输入的内容统一发给后端 由后端和数据库的数据进行比对
将比对的结果返回给前端
3、密码加密及解密技术 使用插件包------jsencrypt 和 node-jsencrypt
这里我么利用了阿里的一个加密软件,此软件作用是生成两个不一样的key然后相当于一个键值对可以通过这两个key分别进行加密解密,
其中一个在前端使用加密另一个在后端解密,这样的话就会让安全性提高 具体代码如下
//此模块是根据jsencrypt封装一个加密过程
import JSEncrypt from 'jsencrypt'
export function jsEncrypt(str) {
//实例这个方法
let encrypt = new JSEncrypt();
//根据一款软件 随机生成一段key值 这个key值是两个相互的 前后端各有一个 然后我们根据这个key值可以进行加密解密处理
let key = `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlec7cf57opzCzTxrsSWmnycz0QVg9jA9syIDXyamt0cAypThrLpzFgCA6bTGoRTp5fjUzdQ1Z/PyN6L7BJ01EYQBdp6LERkqCNTySP1furoB1tlsxmi6lvYAFfJXgABJiAv7+5pZGilDtHvJDAduAZD2SKjg4etQom7bkAjV0GCwJnW6VkAUilgV+xwXhMDpjkzgNA6gdKVJjuF4n09fwRO4Y3bnypbOYLb0ks03QH1YkhJglEv6NrFpnUy1qFIkzKwgs0ieZ3qXW5yYmS/I3ZLcmsQ7RutCmJoqwTgfXodUGTxKCjIme+TeqcJmdHc84ElhIuk30nCFqYclehae8wIDAQAB`
//将这个key值传给这个方法 然后后端调用这个方法传入另一个相对的key就会进行解密
encrypt.setPublicKey(key);
//进行加密
let data = encrypt.encrypt(str);
//因为+号可能有其他不好的影响 我们用%2b替换一下这个+号
let code = encodeURI(data).replace(/\+/g, '%2B')
//将加密后的字符串返回
return code
}
//解密函数
export function decode(pwd) {
var decrypt = new JSEncrypt();
//存放key
decrypt.setPrivateKey('MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCV5ztx/nuinMLNPGuxJaafJzPRBWD2MD2zIgNfJqa3RwDKlOGsunMWAIDptMahFOnl+NTN1DVn8/I3ovsEnTURhAF2nosRGSoI1PJI/V+6ugHW2WzGaLqW9gAV8leAAEmIC/v7mlkaKUO0e8kMB24BkPZIqODh61CibtuQCNXQYLAmdbpWQBSKWBX7HBeEwOmOTOA0DqB0pUmO4XifT1/BE7hjdufKls5gtvSSzTdAfViSEmCUS/o2sWmdTLWoUiTMrCCzSJ5nepdbnJiZL8jdktyaxDtG60KYmirBOB9eh1QZPEoKMiZ75N6pwmZ0dzzgSWEi6TfScIWphyV6Fp7zAgMBAAECggEAGk+WyIBhVP5s1rcnM9Wm9EJePu7RwQRgoAN1Ugsnsf2dbvFI1xd2wcLe3aZkQru3/ix5tZLsuM1Bk3Bg3MN3IBbqZtaXFC41iY1O5W7Lkau6TOqmxAB3161gAHojz4y9W0q3NMc3onbhslkTxa+8KDw4bjJuHlk+MvSARzy1wrghDB6QbdJIPzV19dGggez/ICf6UrCgdmQuJAbFdHnoEK83qFSbBz5aljwWnMWTWvMJ0vN4SJXjiLwNMrOyKPkaeMulYFL2avFNeHhj/vzJL5R1bYWyFJ3mFQpSBVGUDfZoeCp5hDbwU6ERe2pjagPgsD2S72IhAEwKrlXUlemDQQKBgQDHA3gKdE47IlNrsqEmPiJHHoXY3Ix+4GPWT513DqwAAgxc0Sd/iFqHrxwAP7ZgohU4Eln/fCd9CsrOLqT9e3EETOjfSxFQjxC/RmcKkg4fXZZCXryx81OpjdTRT4v2mbpA0Hm/Kb4s0tT03vHrPeMOffiKefK0NQnbZfFvpQzk0wKBgQDA08WGesbUJKidTV0AjYNZQjJgXpYoRwneERxQiu7Ofxsc0oKbsYv5rZtKp7LjVkTyXUj8yhzJObVKm3qtoj8qvRhVMUhyPAiJJjFh+gcybV39jyvuq0zCv9n0ytuCfTfvPZEe/bsGCzhv71wuHNhxrkQ3St37APgf6wrzo+WJYQKBgEFoF3TAItH2hxo3PBVYiGV9V5odaiNs1gMiaWsurELYaX2709JrWu2LFJXUWrlJq9Wg2mlIQaYr/NlkpR8WCd/S8xooDsm+K0/h8I2d0PxoArFPd464nP91uMMN9L8YaQlSOyEjs/gBVrIf77xTu6MQrbW9PJITeGjeCUqbITC3AoGAXPX7dTC9qEqgC23fl0Oh/iceuD0BcRuGU0u2ddH0/RJkFMob80luLQmYIy6j3Fub06hLZqtdo1kx4G0CgLEGeOk+0Nt4jLIKf2wtRInQbGwzculSCbcFw6HQRuaBWvBZRfpNez5hqrFAHR6tNwHrCyszceCjEb5O4LxkxD7QiyECgYEAhujdPXQQcn7CqpY61ivxy03LCz4lW3R8xSJMtm1BY+iBp6P2GbgdKpbSXiTSr/Y3//8DwccLf47xc7Otcw6Yz+tcen88UxqHrI1myHltFNYkrbQ692PtszO8n3fta5bCPr8RY2ON3+uGYSHIDGyixAdxRQxkQPwMD0CFntHA2EA=');
pwd = pwd.replace(/%2B/g, "+")
pwd = decrypt.decrypt(pwd);
return pwd;
}
4、图形验证码之过期实现 ----- Redis 软件
关于Redis这个软件不多做叙述,他的作用相当于一个小型的数据库,可以存入一个键值对并对其可以进行设置过期时间
详情请看 https://www.runoob.com/redis/redis-tutorial.html
使用步骤 先让其和我们的node连接起来 代码如下
//连接redis数据库
const redis = require('redis')
const client = redis.createClient();
//如果没有启动redis,会报错,启动redis方法,在cd到redis的安装目录,执行redis-server.exe redis.windows.conf
client.on("error", function (err) {
console.log("Error " + err);
});
module.exports=client;
4-1、图形验证码的实现与存入Redis中去并设置过期时间60s----插件包 svg-captcha
生成一个验证码图片并且将其存到Redis代码
module.exports.Verify = (req, res) => {
var captcha = svgCaptcha.create({});
//此时text生成的就是随机的四位数验证码 真正的验证码
let text = captcha.text;
//console.log(captcha);
//在生成一个随机的key用来做这个验证码的标识
let keyId = createToken();
//console.log(keyId);
//把这个生成的key和captcha.data这个生成的图片返回给前端
let temp = {
keyId: keyId,
captcha: captcha.data,
}
//将其存到Redis这个数据库中 并且设置60s后从这个数据库删除
client.set(keyId, text, 'EX', 60) //60秒后验证码过期知道
//检查一下如果是不是存进去了
client.get(keyId, function (err, v) {
//console.log("图形验证码的值存入redis,值为:", v);
if (err) {
res.statusCode = 500;
res.send({
code: 0,
msg: "请稍后重试"
})
} else {
res.send({
code: 1,
data: temp,
message: '验证码'
});
}
})
}
5、登录接口的处理情况
登录接口的处理,首先先判断验证码是不是成功,成功返回--- 失败返回结果
然后根据用户名去数据库查找该用户是否存在 存在---- 不存在返回结果
将数据库中进行加密的密码取出来解密,并将前端发来的密码进行解密然后比对 密码相等----- 密码不等 ---- 代码如下
module.exports.Login = (req, res) => {
//console.log(req.body)
//console.log(req.body.keyId)
let {
user,
pwd,
keyId,
img
} = req.body;
//console.log(img,"什么")
//接到登录请求后先检验这个图片验证码是不是存在或者不正确
client.get(keyId, (err, succ) => {
if (err) {
console.log(err);
res.statusCode = 500;
res.send({
code: 0,
msg: "验证码已过期"
})
return false;
} else {
//console.log(img.toUpperCase() === succ.toUpperCase())
console.log(succ)
if (succ) {
if (img.toUpperCase() === succ.toUpperCase()) {
//根据用户名去数据库查询这个人是不是存在数据库
//如果存在的话 在对密码进行解密处理
const $sql = `select * from user where user="${user}"`;
connection.query($sql, (err, resaults) => {
if (err) {
res.statusCode = 500;
res.send({
code: 0,
msg: "登录失败,请稍后再试"
})
} else {
if (resaults.length) {
//console.log(resaults[0])
//取出这个密码来进行解密 然后与传过来的密码进行比较 看是否密码一样
var decrypt = new JSEncrypt();
//存放key
decrypt.setPrivateKey('MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCV5ztx/nuinMLNPGuxJaafJzPRBWD2MD2zIgNfJqa3RwDKlOGsunMWAIDptMahFOnl+NTN1DVn8/I3ovsEnTURhAF2nosRGSoI1PJI/V+6ugHW2WzGaLqW9gAV8leAAEmIC/v7mlkaKUO0e8kMB24BkPZIqODh61CibtuQCNXQYLAmdbpWQBSKWBX7HBeEwOmOTOA0DqB0pUmO4XifT1/BE7hjdufKls5gtvSSzTdAfViSEmCUS/o2sWmdTLWoUiTMrCCzSJ5nepdbnJiZL8jdktyaxDtG60KYmirBOB9eh1QZPEoKMiZ75N6pwmZ0dzzgSWEi6TfScIWphyV6Fp7zAgMBAAECggEAGk+WyIBhVP5s1rcnM9Wm9EJePu7RwQRgoAN1Ugsnsf2dbvFI1xd2wcLe3aZkQru3/ix5tZLsuM1Bk3Bg3MN3IBbqZtaXFC41iY1O5W7Lkau6TOqmxAB3161gAHojz4y9W0q3NMc3onbhslkTxa+8KDw4bjJuHlk+MvSARzy1wrghDB6QbdJIPzV19dGggez/ICf6UrCgdmQuJAbFdHnoEK83qFSbBz5aljwWnMWTWvMJ0vN4SJXjiLwNMrOyKPkaeMulYFL2avFNeHhj/vzJL5R1bYWyFJ3mFQpSBVGUDfZoeCp5hDbwU6ERe2pjagPgsD2S72IhAEwKrlXUlemDQQKBgQDHA3gKdE47IlNrsqEmPiJHHoXY3Ix+4GPWT513DqwAAgxc0Sd/iFqHrxwAP7ZgohU4Eln/fCd9CsrOLqT9e3EETOjfSxFQjxC/RmcKkg4fXZZCXryx81OpjdTRT4v2mbpA0Hm/Kb4s0tT03vHrPeMOffiKefK0NQnbZfFvpQzk0wKBgQDA08WGesbUJKidTV0AjYNZQjJgXpYoRwneERxQiu7Ofxsc0oKbsYv5rZtKp7LjVkTyXUj8yhzJObVKm3qtoj8qvRhVMUhyPAiJJjFh+gcybV39jyvuq0zCv9n0ytuCfTfvPZEe/bsGCzhv71wuHNhxrkQ3St37APgf6wrzo+WJYQKBgEFoF3TAItH2hxo3PBVYiGV9V5odaiNs1gMiaWsurELYaX2709JrWu2LFJXUWrlJq9Wg2mlIQaYr/NlkpR8WCd/S8xooDsm+K0/h8I2d0PxoArFPd464nP91uMMN9L8YaQlSOyEjs/gBVrIf77xTu6MQrbW9PJITeGjeCUqbITC3AoGAXPX7dTC9qEqgC23fl0Oh/iceuD0BcRuGU0u2ddH0/RJkFMob80luLQmYIy6j3Fub06hLZqtdo1kx4G0CgLEGeOk+0Nt4jLIKf2wtRInQbGwzculSCbcFw6HQRuaBWvBZRfpNez5hqrFAHR6tNwHrCyszceCjEb5O4LxkxD7QiyECgYEAhujdPXQQcn7CqpY61ivxy03LCz4lW3R8xSJMtm1BY+iBp6P2GbgdKpbSXiTSr/Y3//8DwccLf47xc7Otcw6Yz+tcen88UxqHrI1myHltFNYkrbQ692PtszO8n3fta5bCPr8RY2ON3+uGYSHIDGyixAdxRQxkQPwMD0CFntHA2EA=');
pwd = pwd.replace(/%2B/g, "+")
//此时就能把传过来的密码解密了
pwd = decrypt.decrypt(pwd);
//在把数据库的数据解密出来进行比对
resaults[0].password = resaults[0].password.replace(/%2B/g, "+")
let mysqlPwd = decrypt.decrypt(resaults[0].password)
//console.log(mysqlPwd,"密码是什么")
//比较下两个密码 看是否一样
if (pwd === mysqlPwd) {
res.send({
code: 1,
token: resaults[0].token,
msg: "登陆成功"
})
} else {
res.send({
code: 0,
msg: "密码输入错误"
})
}
} else {
//该用户没有在数据库中
res.send({
code: 0,
msg: "该用户还会注册,请去注册页面进行注册"
})
}
}
})
} else {
res.statusCode = 500;
res.send({
code: 0,
msg: "验证码输入错误"
})
}
} else {
res.send({
code: 0,
msg: "验证码已过期"
})
}
console.log(succ, "===");
}
})
}
6、MySQL数据库的连接测试 ------ 插件包mysql
//数据库连接
const mysql=require("mysql"); var connection=mysql.createConnection({
host:"localhost",
user:"root",
password:"root",
database:"project"//数据库名称
}) connection.connect((error) => {
if (error) {
console.log('数据库连接失败,详情:',error)
} else {
console.log('数据库连接成功')
}
}) module.exports = connection
7、记住密码功能
根据登录成功后返回的token然后携带者其向后端发送请求 ,让后端把用户名和加密后的密码返回回来
由前端进行解密后然后展示到页面
缺点 本来用于前后端加密解密的key这样都暴露给前端了 另外这种实现也不是好的办法 不如浏览器自带的记住密码功能好
module.exports.Remember=(req,res)=>{
let {token}=req.query;
//根据这个token去数据库找到改用户的密码然后返回给前端让其默认加载页面上
const $sql=`select * from user where token="${token}"`;
connection.query($sql,(err,resaults)=>{
if(err){
return false;
}
let {user,password}=resaults[0];
res.send({
code:1,
info:{
user,
password
}
})
})
}
注册模块详解
1、注册前端思路
同理前端基本事项检验 然后将加密后的密码和用户名发给后端然后端进行存储 邮箱的作用是用来找回密码使用的
2、后端思路
存储之前先判断用户是否被注册过 如果没有 将其注册成功后并且一起生成一条token 然后此token在用户登录成功时返给前端并将其存在本地
module.exports.Registry = (req, res) => {
//console.log(req.body)
let {
password,
username,
email
} = req.body;
//此时密码是加密后的密码 我们需要先根据这个用户名去数据库查询该用户
//如果存在该用户 提示已经注册过了 不存在改用户 则让其注册
const $sql = `select * from user where user="${username}"`
connection.query($sql, (err, results) => {
if (err) {
//这样代表后台服务器错误 返回一个500的状态码吧
res.statusCode = 500;
res.send({
code: 0,
msg: "注册失败,请稍后重试"
})
} else {
console.log(results);
if (results.length) {
//证明该用户已经被注册过了 提示注册失败
res.send({
code: 0,
msg: "注册失败,该用户已被注册"
})
} else {
//存入数据库
//在存入数据库的同时为该用户注册一个token 用来做身份认证
let token = createToken(username);
const $save = `insert into user(user,password,token,email) values("${username}","${password}","${token}","${email}")`;
connection.query($save, (err, result) => {
if (err) {
//这样代表后台服务器错误 返回一个500的状态码吧
res.statusCode = 500;
res.send({
code: 0,
msg: "注册失败,请稍后重试"
})
} else {
//注册成功,向用户返回token以及注册成功的信息
res.send({
code: 1,
msg: "注册成功",
token
})
}
})
}
}
})
}
找回密码功能
1、此处介绍个新的加密解密方式 jwt-simple
使用方式 加密
//token加密的key
let secret = 'xxx';
jwt.encode(token, secret)
第一个参数可以是加密的对象 可以是单个字符变量 也可以是对象 但是注意不能传JSON字符串形式的对象 不然解密后结构不出来
解密过程
2、介绍个发送邮件的插件包 nodemailer 代码如下
// async..await is not allowed in global scope, must use a wrapper
const nodemailer=require("nodemailer")
module.exports.sendEmail=async (username, email, url)=>{
// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
let testAccount = await nodemailer.createTestAccount();
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: 'smtp.sina.com',
// service: 'qq',
// port: 465,
//secure: false, // true for 465, false for other ports
secureConnection: true, // 使用了 SSL
auth: {
user: 'qiangchen1996@sina.com', // generated ethereal user
pass: '填写自己的邮箱密码' // generated ethereal password
}
})
// send mail with defined transport object
let info = await transporter.sendMail({
from: '<qiangchen1996@sina.com>', // sender address
to: email, // list of receivers
subject: "重新设置密码", // Subject line
html: `<b>${username}您好!您可以点击下面的链接设置新的密码,<span style="color:red">幽默的小强为您奉上</span></b>
<a href=${url}>${url}</a>,<h2>测试功能,打扰之处抱歉</h2>` // html body
});
console.log("Message sent: %s", info.messageId);
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@example.com>
// Preview only available when sending through an Ethereal account
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
}
具体使用代码如下
module.exports.Retrieve = (req, res) => {
//console.log(req.body);
let {
email
} = req.body;
console.log(email)
//根据这个email去数据库里查到对应的用户去
const $sql = `select * from user where email="${email}"`;
console.log($sql);
connection.query($sql, async (err, results) => {
if (err) {
res.statusCode = 500;
res.send({
code: 0,
msg: "服务器繁忙,请稍后重试"
})
return
}
if (results.length) {
//console.log(results[0])
let {
user
} = results[0];
let token=createToken(user);
//console.log(token,"====")
try {
await sendEmail(user, email, `http://localhost:8080/#/reset/${token}`)
//成功以后就告诉前端
res.send({
msg: "请注意查收邮件",
code: 1
})
} catch (error) {
res.send({
msg: "邮件发送失败,请重新提交",
code: 0
})
}
} else {
res.send({
code: 0,
msg: "该邮箱未被注册"
})
}
})
}

国际化多语言功能
结合vue使用的插件包 vue-i18n
此处只展示main.js的布置 具体用法请参考 https://www.npmjs.com/package/vue-i18n
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router' import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css'; import store from "./store/index" //引入语言切换
import VueI18n from 'vue-i18n' //简体中午语言
import zhCN from '@/i18n/zh-CN.js'
//英语
import enUS from '@/i18n/en-US.js'
//繁体字
import zhTW from '@/i18n/zh-TW.js' //将vue-i18n挂载到全局
Vue.use(VueI18n) Vue.use(ElementUI); //配置语言项
const messages = {
'en-US': {...enUS},
'zh-CN': {...zhCN},
'zh-TW': {...zhTW}
} //设置语言项 如果本地有从本地提取 本地没有显示默认简体中午
//存到本地的原因是防止页面刷新导致语言不能显示
let currentLocale = localStorage.getItem('language_type') || 'zh-CN'; const i18n = new VueI18n({
locale: currentLocale, // 设置地区
messages, // 设置地区信息
}) Vue.config.productionTip = false /* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
i18n,
components: { App },
template: '<App/>'
})
未完待续~~~
前后端登录注册之node剖析与token的使用状态的更多相关文章
- thinkphp+jquery+ajax前后端交互注册验证
thinkphp+jquery+ajax前后端交互注册验证,界面如下 register.html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1. ...
- JAVAEE——宜立方商城11:sso登录注册功能实现、通过token获得用户信息、Ajax跨域请求(jsonp)
1. 学习计划 第十一天: 1.sso注册功能实现 2.sso登录功能实现 3.通过token获得用户信息 4.Ajax跨域请求(jsonp) 2. Sso系统工程搭建 需要创建一个sso服务工程,可 ...
- 打通前后端全栈开发node+vue进阶【课程学习系统项目实战详细讲解】(3):用户添加/修改/删除 vue表格组件 vue分页组件
第三章 建议学习时间8小时 总项目预计10章 学习方式:详细阅读,并手动实现相关代码(如果没有node和vue基础,请学习前面的vue和node基础博客[共10章] 演示地址:后台:demo ...
- vue+express利用token 完成前后端登录
token是后端给前端的一个二维码, 这个二维码一般是暗码, 前端拿着这个二维码到后端, 后端便可以通过这个二维码得知用户是否登录过, 用户是谁等信息(具体什么信息,是后端在返回token时候设置的 ...
- SpringBootSecurity学习(20)前后端分离版之OAuth2.0刷新token
刷新token 前面的例子和配置都是从头开始申请授权码和令牌,现在来看一下如何根据获取令牌时,回参中的 refresh_token 来刷新令牌.现在在项目中配置的是内存模式的默认用户名密码,第一步先改 ...
- Laravel5.5+ 区分前后端用户登录
Laravel 的用户认证是通过 Auth Facade 门脸实现的,手动认证可是使用 Auth::login() 或 Auth::attempt() 这两个方法实现. 以下内容纯属个人实现,也许有 ...
- 前后端分离及React的一些研究
前言 在对英才网企业线前端不断的完善过程中,我们尝试进行了前后端分离,引入Node环境.以及在使用React的过程中,自行开发DOM渲染框架,解决React兼容低版本IE的问题,在这个过程中,我们有了 ...
- Spring Cloud实战 | 最八篇:Spring Cloud +Spring Security OAuth2+ Axios前后端分离模式下无感刷新实现JWT续期
一. 前言 记得上一篇Spring Cloud的文章关于如何使JWT失效进行了理论结合代码实践的说明,想当然的以为那篇会是基于Spring Cloud统一认证架构系列的最终篇.但关于JWT另外还有一个 ...
- 从一张图开始,谈一谈.NET Core和前后端技术的演进之路
从一张图开始,谈一谈.NET Core和前后端技术的演进之路 邹溪源,李文强,来自长沙.NET技术社区 一张图 2019年3月10日,在长沙.NET 技术社区组织的技术沙龙<.NET Core和 ...
随机推荐
- 移动端页面输入法挡住input输入框的解决方法
1,宽高用了百分比或者vw/vh布局的,input输入框的最外层父容器的可用JS动态设置为当前窗口的宽高(防止输入法的弹出令页面变形) 2,最外层父容器用了fixed定位的,不要用top:0;要用bo ...
- 公共钥匙盒 ccf
试题编号: 201709-2 试题名称: 公共钥匙盒 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 有一个学校的老师共用N个教室,按照规定,所有的钥匙都必须放在公共钥匙盒里, ...
- MySQL错误1055
问题描述:在MySQL数据库下,执行SQL插入语句报错.错误信息如下: 错误原因:在MySQL5.7之后,sql_mode中默认存在ONLY_FULL_GROUP_BY,SQL语句未通过ONLY_FU ...
- CSS制作的32种图形效果[梯形|三角|椭圆|平行四边形|菱形|四分之一圆|旗帜]
转载链接:http://www.w3cplus.com/css/css-simple-shapes-cheat-sheet 前面在<纯CSS制作的图形效果>一文中介绍了十六种CSS画各种不 ...
- 洛谷3953 (NOIp2017) 逛公园——记忆化搜索+用栈判0环
题目:https://www.luogu.org/problemnew/show/P3953 因为K只有50,所以想到用dp[ cr ][ j ]表示在点cr.比最短路多走了 j 的方案数.(看了TJ ...
- Laravel 精选资源大全
原文链接 必备品 文档:Documentation API:API Reference 视频:Laracasts 新闻:Laravel News 中文文档 Laravel学院– Laravel 5. ...
- 【JZOJ5060】【GDOI2017第二轮模拟day1】公路建设 线段树+最小生成树
题面 在Byteland一共有n 个城市,编号依次为1 到n,它们之间计划修建m条双向道路,其中修建第i 条道路的费用为ci. Byteasar作为Byteland 公路建设项目的总工程师,他决定选定 ...
- 顶级测试框架Jest指南:跑通一个完美的程序,就是教出一群像样的学生
facebook三大项目:yarn jest metro,有横扫宇宙之势. 而jest项目的宗旨为:减少测试一个项目所花费的时间成本和认知成本. --其实,它在让你当一个好老师. jest文档非常简略 ...
- [Vue CLI 3] 配置解析之 parallel
官方文档中介绍过在 vue.config.js 文件中可以配置 parallel,作用如下: 是否为 Babel 或 TypeScript 使用 thread-loader. 该选项在系统的 CPU ...
- python findall函数