Node 第 7 天

1. 学习目标

◆ 完成登录功能的开发

◆ 能够获取用户的信息并重置密码和头像

2. 登录功能

2.1 实现步骤

  1. 检测表单数据是否合法
  2. 根据用户名查询用户的数据
  3. 判断用户输入的密码是否正确
  4. 生成 JWTToken 字符串

2.2 检测表单数据是否合法

  1. /router/user.js 中 登录 的路由代码修改进行修改
// 注册新用户
router.post('/reguser', expressJoi(reg_login_schema), userHandle.regUser)// 登录功能
router.post('/login', expressJoi(reg_login_schema), userHandle.login)

2.3 根据用户名查询用户的数据

  1. 接收表单的数据
// 接收表单传递过来的数据
const userInfo = req.body
  1. 定义 sql 语句
// 定义 sql 语句
const sql = `select * from ev_users where username=?`
  1. 执行 sql 语句,查询用户的数据
// 执行 sql 语句,根据用户名查询用户的信息
db.query(sql, userInfo.username, (err, results) => {// 指定 sql 失败if (err) return res.cc(err)// 执行 sql 语句成功,但是获取到的数据条数不等于 1if (results.length !== 1) return res.cc('登录失败')// 判断用户名和密码是否正确 res.send('login Ok')
})
  1. 完整代码
// 登录的处理函数
exports.login = (req, res) => {// 接收表单传递过来的数据const userInfo = req.body// 定义 sql 语句const sql = `select * from ev_users where username=?`// 执行 sql 语句,根据用户名查询用户的信息db.query(sql, userInfo.username, (err, results) => {// 指定 sql 失败if (err) return res.cc(err)// 执行 sql 语句成功,但是获取到的数据条数不等于 1if (results.length !== 1) return res.cc('登录失败')// 判断用户名和密码是否正确res.send('login Ok')})
}

2.4 判断用户输入的密码是否正确

核心实现思路:调用 bcrypt.compareSync(用户提交的密码, 数据库中的密码) 方法比较密码是否一致,

返回值是布尔值(true 一致、false 不一致)

  1. 实现代码
// 判断用户名和密码是否正确
// 将用户输入的密码和数据库中存储的密码进行比较
const compareResult = bcrypt.compareSync(userInfo.password,results[0].password)
// 根据对比后的结果进行判断
if (!compareResult) return res.cc('登录失败!')
  1. 完整代码

    // 登录的处理函数
    exports.login = (req, res) => {// 接收表单传递过来的数据const userInfo = req.body// 定义 sql 语句const sql = `select * from ev_users where username=?`// 执行 sql 语句,根据用户名查询用户的信息db.query(sql, userInfo.username, (err, results) => {// 指定 sql 失败if (err) return res.cc(err)// 执行 sql 语句成功,但是获取到的数据条数不等于 1if (results.length !== 1) return res.cc('登录失败')// 判断用户名和密码是否正确// 将用户输入的密码和数据库中存储的密码进行比较const compareResult =bcrypt.compareSync(userInfo.password,results[0].password)// 根据对比后的结果进行判断if (!compareResult) return res.cc('登录失败!')res.send('login Ok')})
    }
    

2.5 分析生成 Token 字符串的步骤

  1. 通过 ES6 的高级语法,快速剔除 密码头像 的值
  2. 运行如下的命令,安装生成 Token 字符串的包
  3. /router_handler/user.js 模块的头部区域,导入 jsonwebtoken
  4. 创建 config.js 文件,并向外共享 加密 和 还原 TokenjwtSecretKey 字符串
  5. 将用户信息对象加密成 Token 字符串
  6. 将生成的 Token 字符串响应给客户端

2.6 生成 JWTToken 步骤

  1. 通过 ES6 的高级语法,快速剔除 密码头像 的值
// 登录成功以后,给用户返回 token 值
// 剔除 user 返回的 头像和密码 信息,
const user = { ...results[0], password: '', user_pic: '' }
  1. 运行如下的命令,安装生成 Token 字符串的包
cnpm i jsonwebtoken@8.5.1 -S
  1. /router_handler/user.js 模块的头部区域,导入 jsonwebtoken
// 生成 Token 字符串
const jwt = require('jsonwebtoken')
  1. 创建 config.js 文件,并向外共享 加密 和 还原 TokenjwtSecretKey 字符串
// 全局配置文件module.exports = {// 加密和解密的 Token 密钥jwtSecretKey: 'itheima No1. ^_^',expiresIn: '10h' // 设置 token 有效期为 10 小时
}
  1. 将用户信息对象加密成 Token 字符串
// 导入配置文件
const config = require('../config')
// 生成 Token 字符串内容
const tokenStr = jwt.sign(user, config.jwtSecretKey, {expiresIn: config.expiresIn})
  1. 将生成的 Token 字符串响应给客户端
// 将生成的 Token 字符串响应给客户端
res.send({status: 0,message: '登录成功!',// 为了方便客户端使用 Token,在服务器端直接拼接上 Bearer 的前缀token: 'Bearer ' + tokenStr,
})
  1. 完整代码
/*** 定义和用户相关的路由处理函数,功 /router/user.js 模块尽心调用*/// 导入数据库操作模块
const db = require('../db/index')
const bcrypt = require('bcryptjs')
// 生成 Token 字符串
const jwt = require('jsonwebtoken')
// 导入配置文件
const config = require('../config')// 注册用户的处理函数
exports.regUser = (req, res) => {}// 登录的处理函数
exports.login = (req, res) => {// 接收表单传递过来的数据const userInfo = req.body// 定义 sql 语句const sql = `select * from ev_users where username=?`// 执行 sql 语句,根据用户名查询用户的信息db.query(sql, userInfo.username, (err, results) => {// 指定 sql 失败if (err) return res.cc(err)// 执行 sql 语句成功,但是获取到的数据条数不等于 1if (results.length !== 1) return res.cc('登录失败')// 判断用户名和密码是否正确// 将用户输入的密码和数据库中存储的密码进行比较const compareResult = bcrypt.compareSync(userInfo.password, results[0].password)// 根据对比后的结果进行判断if (!compareResult) return res.cc('登录失败!')// 登录成功以后,给用户返回 token 值// 剔除 user 返回的 头像和密码 信息,const user = { ...results[0], password: '', user_pic: '' }// 生成 Token 字符串内容const tokenStr = jwt.sign(user, config.jwtSecretKey, {expiresIn: config.expiresIn})// 将生成的 Token 字符串响应给客户端res.send({status: 0,message: '登录成功!',// 为了方便客户端使用 Token,在服务器端直接拼接上 Bearer 的前缀token: 'Bearer ' + tokenStr,})})
}

2.7 配置解析 Token 的中间件

  1. 运行如下的命令,安装解析 Token 的中间件
cnpm i express-jwt@5.3.3 -S
  1. app.js 中注册路由之前,配置解析 Token 的中间件
// 导入全局的配置文件
const config = require('./config')
// 解析 token 的中间件
const expressJWT = require('express-jwt')
// 使用 .unless 方法指定哪些接口不需要进行 Token 的身份认证
app.use(expressJWT({ secret: config.jwtSecretKey })).unless({ path: [/^\/api\//] })// 导入并注册用户路由模块
const userRouter = require('./router/user')
app.use('/api', userRouter)
  1. app.js 中的 错误级别中间件 里面,捕获并处理 Token 认证失败后的错误
// 错误中间件
app.use((err, req, res, next) => {// 数据验证失败if (err instanceof joi.ValidationError) return res.cc(err)// 在身份认证失败后,捕获并处理 Token 认证失败后的错误if (err.name === 'UnauthorizedError') return res.cc('身份认证失败!')// 未知错误res.cc(err)
})

3. 获取用户的基本信息

3.1 实现步骤

  1. 初始化 路由 模块
  2. 初始化 路由处理函数 模块
  3. 获取用户的基本信息

3.2 初始化路由模块

  1. 创建 /router/userinfo.js 路由模块,并初始化如下的代码结构

    // 导入 express
    const express = require('express')
    //?exports 本身导出的就是对象 解构赋值
    const { route } = require('./user')
    // 创建路由对象
    const router = express.Router()// 获取用户的基本信息
    router.get('/userinfo', (req, res) => {res.send('ok')
    })// 向外共享路由对象
    module.exports = router
  2. app.js 中导入并使用个人中心的路由模块

// 导入并注册用户路由模块
const userRouter = require('./router/user')
// 导入并使用用户信息的路由模块
const userinfoRouter = require('./router/userinfo')app.use('/api', userRouter)
app.use('/my', userinfoRouter)

3.3 初始化 路由处理函数 模块

  1. 创建 /router_handler/userinfo.js 路由处理函数模块,并初始化如下的代码结构
// 创建用户基本信息的处理函数
exports.getUserInfo = (req, res) => {res.send('Ok')
}
  1. 修改 /router/userinfo.js 中的代码
// 导入 express
const express = require('express')
const { route } = require('./user')
// 创建路由对象
const router = express.Router()
// 导入用户信息的处理函数模块
const userinfo_handler = require('../router_handler/userinfo')// 获取用户的基本信息
router.get('/userinfo', userinfo_handler.getUserInfo)// 向外共享路由对象
module.exports = router

3.4 获取用户的基本信息

  1. /router_handler/userinfo.js 头部导入数据库操作模块
// 导入数据库操作模块
const db = require('../db/index')
  1. 定义 SQL 语句
// 定义查询用户信息的 sql 语句
const sql = `select id, username, nickname, email, user_pic from ev_users where id=?
  1. 调用 db.query() 执行 SQL 语句
// 调用 db.query() 执行 sql 语句
db.query(sql, req.user.id, (err, results) => {// 执行 sql 语句失败if (err) return res.cc(err)// 执行的 sql 语句成功,但是查询的结果可能为空if (results.length !== 1) return res.cc('获取用户信息失败!')// 用户信息获取成功res.send({status: 0,message: '获取用户基本信息成功!',data: results[0],})
})
  1. 完成代码
// 导入数据库操作模块
const db = require('../db/index')// 创建用户基本信息的处理函数
exports.getUserInfo = (req, res) => {// 定义查询用户信息的 sql 语句const sql = `select id, username, nickname, email, user_pic from ev_users where id=?`// 调用 db.query() 执行 sql 语句db.query(sql, req.user.id, (err, results) => {// 执行 sql 语句失败if (err) return res.cc(err)// 执行的 sql 语句成功,但是查询的结果可能为空if (results.length !== 1) return res.cc('获取用户信息失败!')// 用户信息获取成功res.send({status: 0,message: '获取用户基本信息成功!',data: results[0],})})
}

4. 更新用户的基本信息

4.1 实现步骤

  1. 定义路由和处理函数
  2. 验证表单数据
  3. 实现更新用户基本信息的功能

4.2 定义路由和处理函数

  1. /router/userinfo.js 模块中,新增 更新用户基本信息 的路由
// 更新用户的基本信息
router.post('/userinfo', userinfo_handler.updateUserInfo)
  1. /router_handler/userinfo.js 模块中,定义并向外共享 更新用户基本信息 的路由处理函数
// 更新用户基本信息的处理函数
exports.updateUserInfo = (req, res) => {res.send('Ok')
}

4.3 定义验证规则对象

  1. /schema/user.js 验证规则模块中,定义 idnicknameemail 的验证规则
// 定义 id, nickname, emial 的验证规则integer() 整数
const id = joi.number().integer().min(1).required()
const nickname = joi.string().required()
const email = joi.string().email().required()
  1. 并使用 exports 向外共享如下的 验证规则对象
// 更新用户基本信息的验证规则对象
exports.update_userinfo_schema = {body: {id,nickname,email,}
}
  1. 完整代码
// /schema/user.jsconst joi = require('@hapi/joi')// 验证用户名、密码的规则
const username = joi.string().alphanum().min(1).max(10).required()
const password = joi.string().pattern(/^[\S]{6,12}$/).required()// 定义 id, nickname, emial 的验证规则
const id = joi.number().integer().min(1).required()
const nickname = joi.string().required()
const email = joi.string().email().required()// 登录和注册表单的验证规则对象
exports.reg_login_schema = {// 表示需要对 req.body 中的数据进行验证body: {username,password,}
}// 更新用户基本信息的验证规则对象
exports.update_userinfo_schema = {body: {id,nickname,email,}
}

4.4 验证数据规则的合法性

  1. /router/userinfo.js 模块中,导入验证数据合法性的中间件
// 导入验证数据合法性的中间件
const expressJoi = require('@escook/express-joi')
  1. /router/userinfo.js 模块中,导入需要的验证规则对象
// 导入验证数据合法性的中间件
const expressJoi = require('@escook/express-joi')
// 导入需要的验证规则对象
const { update_userinfo_schema } = require('../schema/user')
  1. /router/userinfo.js 模块中,修改 更新用户的基本信息 的路由如下
// 更新用户的基本信息
router.post('/userinfo', expressJoi(update_userinfo_schema), userinfo_handler.updateUserInfo)
  1. 完整代码
// /router/userinfo.js// 导入 express
const express = require('express')
const { route } = require('./user')
// 导入验证数据合法性的中间件
const expressJoi = require('@escook/express-joi')
// 导入需要的验证规则对象
const { update_userinfo_schema } = require('../schema/user')// 创建路由对象
const router = express.Router()
// 导入用户信息的处理函数模块
const userinfo_handler = require('../router_handler/userinfo')// 获取用户的基本信息
router.get('/userinfo', userinfo_handler.getUserInfo)
// 更新用户的基本信息
router.post('/userinfo', expressJoi(update_userinfo_schema), userinfo_handler.updateUserInfo)// 向外共享路由对象
module.exports = router

4.4 实现更新用户基本信息的功能

  1. 定义待执行的 SQL 语句
// 定义更新用户信息的 sql 语句
const sql = `update ev_users set ? where id=?`
  1. 调用 db.query() 执行 SQL 语句并传参
// 调用 db.query() 执行 sql 语句
db.query(sql, [req.body, req.body.id], (err, results) => {// 执行 sql 语句失败if (err) return res.cc(err)// 执行 sql 语句成功,但影响函数不为 1、if (results.affectedRows !== 1) return res.cc('修改用户基本信息失败!')// 修改用户信息成功return res.cc('修改用户基本信息成功!', 0)
})
  1. 完整代码
// 更新用户基本信息的处理函数
exports.updateUserInfo = (req, res) => {// 定义更新用户信息的 sql 语句const sql = `update ev_users set ? where id=?`// 调用 db.query() 执行 sql 语句db.query(sql, [req.body, req.body.id], (err, results) => {// 执行 sql 语句失败if (err) return res.cc(err)// 执行 sql 语句成功,但影响函数不为 1、if (results.affectedRows !== 1) return res.cc('修改用户基本信息失败!')// 修改用户信息成功return res.cc('修改用户基本信息成功!', 0)})
}

5. 重置密码

5.1 实现步骤

  1. 定义路由和处理函数
  2. 验证表单数据
  3. 实现重置密码的功能

5.2 定义路由和处理函数

  1. /router/userinfo.js 模块中,新增 重置密码 的路由
// 重置密码的路由
router.post('/updatepwd', userinfo_handler.updatePassword)
  1. /router_handler/userinfo.js 模块中,定义并向外共享 重置密码 的路由处理函数

    // 重置密码的处理函数
    exports.updatePassword = (req, res) => {res.send('ok')
    }
    

5.3 验证数据表单

核心验证思路:旧密码与新密码,必须符合密码的验证规则,并且新密码不能与旧密码一致

  1. /schema/user.js 模块中,使用 exports 向外共享如下的 验证规则对象

    • joi.ref('oldPwd') 表示 newPwd 的值必须和 oldPwd 的值保持一致
    • joi.not(joi.ref('oldPwd')) 表示 newPwd 的值不能等于 oldPwd 的值
    • .concat() 用于合并 joi.not(joi.ref('oldPwd'))password 这两条验证规则
    // 重置密码验证规则对象
    exports.update_password_schema = {body: {oldPwd: password,newPwd: joi.not(joi.ref('oldPwd')).concat(password),}
    }

  1. /router/userinfo.js 模块中,导入需要的验证规则对象
// 导入需要的验证规则对象
const { update_userinfo_schema, update_password_schema } = require('../schema/user')
  1. 并在 重置密码的路由 中,使用 update_password_schema 规则验证表单的数据
// 重置密码的路由
router.post('/updatepwd', expressJoi(update_password_schema), userinfo_handler.updatePassword)

5.4 实现重置密码的功能

  1. 根据 id 查询用户是否存在 判断用户在不在???可以不判断 req.user.id是通过token来的

  2. 但是查询完了有results 用results 用来进行对比,是为了方便下面判断旧密码是否正确

  3. 要有自己的思考 不要完全按照老师的 不然以后自己说不出来过程

    // 重置密码的处理函数
    exports.updatePassword = (req, res) => {// 执行根据 id 查询用户数据的 SQL 语句const sql = `select * from ev_users where id=?`// 执行 SQL 语句查询用户是否存在db.query(sql, req.user.id, (err, results) => {// 执行 SQL 语句失败if (err) return res.cc(err)// 判断结果是否存在if (results.length !== 1) return res.cc('用户不存在!')// 判断用户输入的旧密码是否正确res.cc('ok')})
    }

5.5 判断提交的 旧密码 是否正确

  1. 在头部区域导入 bcryptjs
const bcrypt = require('bcryptjs')
  1. 使用 bcrypt.compareSync(提交的密码,数据库中的密码) 方法验证密码是否正确
  • compareSync() 函数的返回值为布尔值,true 表示密码正确,false 表示密码错误
// 判断用户输入的旧密码是否正确
const compareResult = bcrypt.compareSync(req.body.oldPwd, results[0].password)
if (!compareResult) return res.cc('旧密码错误!')
  1. 完整密码
// 重置密码的处理函数
exports.updatePassword = (req, res) => {// 执行根据 id 查询用户数据的 SQL 语句const sql = `select * from ev_users where id=?`// 执行 SQL 语句查询用户是否存在db.query(sql, req.user.id, (err, results) => {// 执行 SQL 语句失败if (err) return res.cc(err)// 判断结果是否存在if (results.length !== 1) return res.cc('用户不存在!')// 判断用户输入的旧密码是否正确const compareResult = bcrypt.compareSync(req.body.oldPwd, results[0].password)if (!compareResult) return res.cc('旧密码错误!')res.cc('ok')})
}

5.6 实现重置密码的功能

  1. 定义更新用户密码的 SQL 语句
// 定义更新密码的 SQL 语句
const sql = `update ev_users set password=? where id=?`
  1. 对新密码进行加密处理
// 对新密码进行加密处理
const newPwd = bcrypt.hashSync(req.body.newPwd, 10)
  1. 执行 SQL 语句
// 执行 SQL 语句,根据 id 更新用户的密码
db.query(sql, [newPwd, req.user.id], (err, results) => {//语句执行失败if (err) return res.cc(err)// 语句执行成功,但是影响行数不等于 1if (results.affectedRows !== 1) return res.cc('更新密码失败!')// 更新密码成功res.cc('更新密码成功!', 0)
})
  1. 完整代码
// 重置密码的处理函数
exports.updatePassword = (req, res) => {// 执行根据 id 查询用户数据的 SQL 语句const sql = `select * from ev_users where id=?`// 执行 SQL 语句查询用户是否存在db.query(sql, req.user.id, (err, results) => {// 执行 SQL 语句失败if (err) return res.cc(err)// 判断结果是否存在if (results.length !== 1) return res.cc('用户不存在!')// 判断用户输入的旧密码是否正确const compareResult = bcrypt.compareSync(req.body.oldPwd, results[0].password)if (!compareResult) return res.cc('旧密码错误!')// 定义更新密码的 SQL 语句const sql = `update ev_users set password=? where id=?`// 对新密码进行加密处理const newPwd = bcrypt.hashSync(req.body.newPwd, 10)// 执行 SQL 语句,根据 id 更新用户的密码db.query(sql, [newPwd, req.user.id], (err, results) => {//语句执行失败if (err) return res.cc(err)// 语句执行成功,但是影响行数不等于 1if (results.affectedRows !== 1) return res.cc('更新密码失败!')// 更新密码成功res.cc('更新密码成功!', 0)})})
}

不传递id怎么知道更新的数据库中哪个用户呢 id 通过 req.user 通过解析token获取到的 最早登录之后就会把token传递到前端了 每次请求携带着就知道是哪个id了

6. 更换头像

6.1 实现步骤

  1. 定义路由和处理函数
  2. 验证表单数据
  3. 实现更新用户头像的功能

6.2 定义路由和处理函数

  1. /router/userinfo.js 模块中,新增 更新用户头像 的路由
// 更新用户头像的路由
router.post('/update/avatar', userinfo_handler.updateAvatar)
  1. /router_handler/userinfo.js 模块中,定义并向外共享 更新用户头像 的路由处理函数
// 更新用户头像的处理函数
exports.updateAvatar = (req, res) => {res.send('ok')
}

6.3 验证表单数据

  1. /schema/user.js 验证规则模块中,定义 avatar 的验证规则
  • dataUri() 指的是如下格式的字符串数据
  • data:image/png;base64,VE9PTUFOWVNFQ1JFVFM=
// 验证头像数据
const avatar = joi.string().dataUri().required()// 验证规则对象 - 更新头像
exports.update_avatar_schema = {body: {avatar}
}
  1. 并使用 exports 向外共享如下的 验证规则对象
// 导入需要的验证规则对象
const { update_avatar_schema } = require('../schema/user')// 更新用户头像的路由
router.post('/update/avatar', expressJoi(update_avatar_schema), userinfo_handler.updateAvatar)

需要验证都放在userinfo里面 不需要token验证的登录注册放在user.js里面

6.4 实现更新用户头像的操作

  1. 定义更新用户头像的 SQL 语句
const sql = 'update ev_users set user_pic=? where id=?'
  1. 调用 db.query() 执行 SQL 语句,更新对应用户的头像
// 更新用户头像的处理函数
exports.updateAvatar = (req, res) => {// 更新用户头像的 sql 字段const sql = 'update ev_users set user_pic=? where id=?'db.query(sql, [req.body.avatar, req.user.id], (err, results) => {// SQL 语句失败if (err) return res.cc(err)// SQL 语句成功,但是影响行数不等于 1if (results.affectedRows !== 1) return res.cc('更新头像失败!')// 更新用户头像成功return res.cc('更新头像成功!', 0)})
}

发现一个BUG 使用登录的用户的token可以更改其它用户的信息 post userinfo时 传递一个其它id就可以改其它用户的昵称和邮箱
把req.body改成req.user

Nodejs Day07 登录+路由相关推荐

  1. Nodejs用户登录,退出案例

    supervisor / nodemon每次修改代码后,都会重写启动 npm install -- save-dev supervisor npm install --save-dev nodemon ...

  2. node mysql登录注册_图解NodeJS实现登录注册功能

    该Demo根据菜鸟教程的练手项目,请提前到菜鸟教程的官网查看nodejs的相关教程,根据教程实际操作一遍,然后自己动手去实现登录.注册功能,此Demo只作参考,不符合前端相关规范. 使用的技术栈 no ...

  3. NodeJS入门04-Express路由和中间件 - 小之 - 博客园

    nodeJS入门04-Express路由和中间件 Express框架是后台的Node框架,在后台的受欢迎的程度,和jQuery一样,就是企业的事实上的标准. 路由 路由是指如何定义应用的端点(URIs ...

  4. vue实现登录路由跳转到成功页面

    一. 效果 1.未登陆之前 2.登录界面 3.登录之后路由跳转 二.需求分析 要求: 使用vue-cli搭建 创建项目 密码输入框有 两个功能,当用户输入时候如果坚持caps lock处于开启状态. ...

  5. node.js express php,nodejs开发——express路由与中间件

    路由 通常HTTP URL的格式是这样的: http表示协议. host表示主机. port为端口,可选字段,不提供时默认为80. path指定请求资源的URI(Uniform Resource Id ...

  6. nodejs之express路由与动态路由

    1.快速创建express项目步骤 /*** 1.cd 到项目里面* 2.npm init --yes 创建package.json文件* 3.安装express* npm install expre ...

  7. 【开源】NodeJS仿WebApi路由

    用过WebApi或Asp.net MVC的都知道微软的路由设计得非常好,十分方便,也十分灵活.虽然个人看来是有的太灵活了,team内的不同开发很容易使用不同的路由方式而显得有点混乱. 不过这不是重点, ...

  8. NodeJS仿WebApi路由

    用过WebApi或Asp.net MVC的都知道微软的路由设计得非常好,十分方便,也十分灵活.虽然个人看来是有的太灵活了,team内的不同开发很容易使用不同的路由方式而显得有点混乱. 不过这不是重点, ...

  9. 分布式系统用户登录路由

    转载于:https://www.cnblogs.com/wangbin/p/8544496.html

最新文章

  1. 电动力学每日一题 2021/10/13 用Fourier变换法计算静止电荷产生的电场
  2. 雷蛇灯光配置文件_没想到吧,雷蛇竟然出61键小尺寸机械键盘了!雷蛇猎魂光蛛迷你版上手...
  3. 干货报告:八大科技领域,280 页,InfoQ《2020 中国技术发展白皮书》开放下载...
  4. 20155204 2016-2017-2 《Java程序设计》第8周学习总结
  5. [vue] vue常用的修饰符有哪些?列举并说明
  6. 网站换服务器需要注意什么问题,网站更换服务器要注意哪些事项
  7. python安装到桌面的路径是什么_Python 获取windows桌面路径的5种方法小结
  8. Mysql ORDER BY用法的一点理解
  9. NDT 算法和一些常见配准算法
  10. securecrt 上传文件到服务器,如何在SecureCRT下使用Zmodem传输文件
  11. 自定义log函数@2018-06-07
  12. Cell:基于33个遗传多样性水稻种质泛基因组分析揭示“隐藏”的基因组变异
  13. 大数据标准化白皮书(2020版) 附下载地址
  14. 如何查询windows的版本号?
  15. SpringBoot配置文件的说明
  16. JAVA之简陋打地鼠(监听事件的练习)
  17. 匠客传媒:论文降重的必备技巧
  18. 三十一、java版商城之移动商城金刚区管理 Spring Cloud+SpringBoot+mybatis+uniapp b2b2c o2o 多商家入驻商城 直播带货商城 电子商务
  19. Riccati 方程求解及MATLAB function遇到的代码生成问题
  20. Java开发工具安装MySql

热门文章

  1. Ubuntu篇——终端操作常用快捷键
  2. Linux—更换国内镜像源
  3. java 窗体代码_Java 用户界面设计 求界面代码
  4. gearman java例子_Gearman 在java中的使用
  5. 设计模式之王者原则 开闭原则
  6. iOS项目_常用备忘
  7. 12306 购票小助手
  8. 学习笔记-工程图学基础(一)
  9. 服务器电脑性能测试,TPC-E测试的不仅仅是服务器性能
  10. 用html制作初相遇,初相遇