git log
可以查看提交记录,按q退出
准备: cmd 进入到安装redis的文件夹,启动redis服务器。
运行redis-server
在项目文件夹安装redis
npm i redis --save
新建config文件夹,新建db.js写一些redis的配置,这个配置需要根据不同的当前环境而设置,所以需要一个环境变量,下面只是在开发环境下的存储配置。
js doc注释方式
/**
*@description 存储配置
*@author bokolin
*/
let REDIS_CONF = {
port: 6379,
host: '127.0.0.1'
}
module.exports ={
REDIS_CONF
}
新建utils/env.js
这个文件主要是为了输出一个当前环境变量,它建立在cross-env的基础之上。
问:如何获取当前环境的变量。
const ENV = process.env.NODE_ENV
module.exports = {
isDev: ENV === 'dev',
notDev: ENV !== 'dev',
isProd: ENV === 'production',
notProd: ENV !== 'production'
}
新建cache
文件夹,cache是缓存的意思,这个文件夹用来存放对redis的操作
在这里面新建一个_redis.js
文件,问:下划线有什么意义么?通常用来表示内部文件,是自己用的
这个文件用来输出get 和set 方法,首先是创建redis客户端,然后才能提供get和set方法。
数据库是本身就有的,前端要做的就是连接数据库(配置+操作)
ps:使用jsdoc的注释形式
/**
*@description 连接redis的方法,get set
*@author bokolin
*/
const redis = require('redis')
const { REDIS_CONF } = require('../conf/db')
//创建客户端
const redisClient = redis.createClient(REDIS_CONF.port,REDIS_CONF.host)
/**
* @param {string} key 键
* @param {string} val 值
* @param {number} timeout 过期时间 单位s
*/
function set(key, val, timeout = 60*60){
if(typeof val == 'object'){
//
val = JSON.stringify(val)
}
redisClient.set(key, val)
redisClient.expire(key,timeout)
}
/**
*
* @param {string} key
*/
function get(key){
return new Promise((resolve, reject)=>{
redisClient.get(key,(err,val)=>{
if(err) {
reject(err)
return
}
if(val == null) {
resolve(null)
return
}
try {
resolve(
JSON.parse(val)
)
} catch (error) {
resolve(val)
}
})
})
}
问:在一个function声明函数之前,我们/**回车可以起到什么效果
问:怎么把一个对象序列化?
问:redis的get函数算不算一个异步操作呢?
问:尝试把val变成一个对象,如果变不成就直接返回
目前主流的实现登录的机制
1. 用cookie加上session的方式做登录
2. 使用jwt的方式
问: session又被称为什么?
问: session会存储每一个用户的什么?登录信息比如说用户登录之后的用户名,还有昵称、头像、如果是在cms系统中还有权限、是不是管理员等登录信息。
用户A从浏览器A登录,用户B从浏览器B登录,他们的登录信息都会存储到哪里? session
session是一个集合,它像对象一样,有很多的什么? key
思考这个集合怎么跟客户端对应呢?怎么知道用户A访问和
这里是引用
用户B访问呢?依赖于什么机制?
比如用户A登录成功之后,浏览器A的cookie中有一个userid。每个用户在访问server的时候会把什么带到后端?这是浏览器本身符合http的一个做法。把cookie带过来之后我们判断这个cookie中的useid到底是谁,然后就可以根据userid从session中把这个用户信息取出来。这个就是cookie+session登录机制。
问:session为什么要存储到redis中?而不直接存储在nodejs的内存中。
1.操作系统会限制一个进程的最大可用内存
2.操作系统中进程与进程之间是有数据隔离的
3.session访问频繁,对性能要求极高
4.session存储在redis中不考虑断电丢失数据的问题(内存硬伤)
5.session数据量不会太大
redis和webServer拆分成两个单独的服务,双方独立可扩展
不使用mysql存储session的原因
为什么网站数据不使用redis存储?
首先redis是一个内存数据库,内存成本很高,redis断电丢失,但是网站数据断电不能丢失 网站的操作频率不高。
真正的使用-koa2怎么去配置session
安装依赖
npm i koa-redis koa-generic-session --save
koa-redis是koa连接redis的一个工具,koa-generic-session是koa生成session的工具。
session存储在redis中,因为redis是以键值对的形式存储数据的。
打开app.js,做一些配置
const session = require('koa-generic-session')
const redisStore = require('koa-redis')
const {REDIS_CONF} = require('./conf/db')
咋一看其实不知道引入REDIS_CONF的作用在哪,REDIS_CONF输出了redis服务器的host和port
接下来的代码需要在路由注册之上写,在注册路由之前需要把session给注册上。
server端不能信任客户端,所以userid不能用明文传递,需要加密一下。
//session配置,这个app.keys应该是用来加密的秘钥,加密的其实是sessonID,这个sessionId使我们自己命名的,
app.keys = ['UIs123@#zs7da']
app.use(session({
key:'weibo.sid',//cookie的name,默认是koa.sid
prefix:'weibo:sess',//redis key的前缀,默认是koa:sess
cookie:{
path:'/',
httpOnly:true,//表示key(weibo.sid)这个值只能在server端修改,不能在客户端修改
maxAge:24*60*60*1000, //cookie的过期时间,登录的过期时间
},
ttl:24*60*60*1000,//redis的过期时间,默认和cookie过期时间保持一致
store:redisStore({
all:`${REDIS_CONF.host}:${REDIS_CONF.port}`
})
}))
用户每次访问,不管有没有登录server都会创建一个cookie给客户端保存,cookie的名字就是weibo.sid
客户端再访问的时候就带着这个cookie过来,会以weibo:sess
为前缀创建一个redis的key,里面会存储一些比较基本的配置,因为这个时候用户还没有登录,所以我们也不知道谁是用户名。然后cookie里面存的值其实就是这个redis的key?
做测试:
npm run dev
ps:redis-server需要一直开着。
情景:访问页面,看cookie的值,cookie的值没看到(前缀是weibo.sid)?
思考:是不是还没有开始用redis,所以他还没有开始连。
到routes文件夹下的index.js
使用ctx.session就可以进入当前访问的一个会话
router.get('/json',aysnc (ctx,next)=>{
const session = ctx.session;//一个对象,里面包含很多信息
if(session.viewNum == null){
session.viewNum =0
}
session.viewNum++
ctx.body = {
viewNum:session.viewNum //每个用户会话的数量是不一致的
}
})
运行之后,在浏览器的控制台可以看到cookie的weibo.sid
我们再来到redis客户端窗口,输入keys * 就能够redis里面有前缀是 weibo:sess的两个key
session里面的有个key值是和cookie里面的值是对应上的。
get (key)可以获取到session的
通过ttl key可以看每个key的过期时间。
单元测试:
jest单元测试
对单个的功能或者接口给定输入,看输出是否符合要求。
手动编写测试用例代码,然后统一执行
能一次性执行所有单测,段时间内验证所有功能是否正常
需要为新作的功能编写单元测试,一次执行,证明新写的功能不能
jest
.test.js结尾的文件
常用的断言,判断输出是否符合要求
http接口测试
git diff可以查看当前文件与线上文件之间的变动
依赖安装:
npm i jest --save-dev
配置启动package.json
"test":"cross-env NODE_ENV=test jest --runInBand --forceExit --colors"
测试运行(此时没有测试用例,可能会报错,写demo)
npm run test
新建test文件夹(与src同级),新建demo.test.js
/**
* @description jest测试demo
* @author bokolin
*/
function sum(a,b){
return a+b;
}
test('10+20应该等于30',()=>{
const res = sum(10,20)
expect(res).toBe(30)
expect(res).not.toBe(40)
})
测试http接口
安装依赖
npm i supertest --save-dev
在test文件夹下新建server.js
const request = require('supertest')
const server = require('../src/app').callback()
module.exports = request(server)
同级文件夹下新建json.test.js
/**
* @description jest test
* @author bokolin
*/
const server = require('./server')
test('json 接口返回数据格式正确',async ()=>{
// 测试get类型接口
const res = await server.get('/string')
console.log(res.body)
expect(res.body).toEqual({
title:'koa2 string'
})
// 测试post类型接口 演示
// const res = await server.post('/login').send({
// userName: 'zhangsan',
// password: '123'
// })
})
运行 npm run test
koa2开发环境配置
配置eslint,以及pre-commit
inspect 调试
404页和错误页
在src目录下新建db文件夹,新建seq.js
安装依赖
npm i mysql2 sequelize --save
看看package.json中的dependencies有这两个依赖。
创建sequelize的实例
/**
* @description sequelize实例
* @author bokolin
*/
const Sequelize = require('sequelize');
const conf = {
host: 'localhost',
dialect: 'mysql'
}
const seq = new Sequelize('flyingfish_db', 'root', '950210zsrtxdy', conf)
module.exports = seq
可以做的操作就是把里面的参数给提取出来,用来适应不同的环境
在config/db.js文件,添加MYSQL的环境配置
/**
* @description 输出数据库连接配置
* @author bokolin
*/
const { isProd } = require('../utils/env')
let REDIS_CONF = {
port: 6379,
host: '127.0.0.1'
}
// const seq = new Sequelize(database, user, password, conf)
let MYSQL_CONF = {
host: 'localhost',
port: 3306,
user: 'root',
password: '950210zsrtxdy',
database:'flyingfish_db'
}
if(isProd){
// 线上的配置
REDIS_CONF = {
port: 6379,
host: '127.0.0.1'
}
MYSQL_CONF = {
host: 'localhost',
port: 3306,
user: 'root',
password: '950210zsrtxdy',
database:'flyingfish_db'
}
}
module.exports ={
REDIS_CONF,
MYSQL_CONF
}
然后回到seq.js
/**
* @description sequelize实例
* @author bokolin
*/
const Sequelize = require('sequelize');
const {database, user, password, host} =require('../config/db').MYSQL_CONF
const conf = {
host,
dialect: 'mysql'
}
const seq = new Sequelize(database, user, password, conf)
// const seq = new Sequelize('flyingfish_db', 'root', '950210zsrtxdy', conf)
module.exports = seq
如果是线上环境就需要连接池,所以我们添加
if(isProd){
// 线上环境,使用连接池
conf.pool = {
max: 5,
min: 0,
idle: 1000
}
}
在做单元测试的时候希望不要打印sql日志,在env.js文件新增两个变量isTest notTest
const ENV = process.env.NODE_ENV
module.exports = {
isDev: ENV === 'dev',
notDev: ENV !== 'dev',
isProd: ENV === 'production',
notProd: ENV !== 'production',
isTest: ENV === 'test',
notTest: ENV !== 'test',
}
回到seq.js,使不打印sql语句日志
if(isTest){
conf.logging = ()=>{}
}
接下来进行同步
在db文件夹下新建sync.js,在执行同步时有{force: true}和没有的区别
const seq = require('./seq.js')
// require('./model.js')
// 3 测试连接
seq.authenticate()
.then(() => {
console.log('Connection has been established successfully.');
})
.catch(err => {
console.error('Unable to connect to the database:', err);
});
// 4 执行同步
seq.sync().then(()=>{
console.log('sync ok')
process.exit() //不退出sequelize会一直占用进程
})
配置eslint
新建.eslintrc.json
.eslintignore
{
"parser": "babel-eslint",
"env": {
"es6": true,
"node": true,
"commonjs":true
},
"extends": "eslint:recommended",
"rules": {
"indent": [
"error",
"tab" //4
],
"linebreak-style": [
"error",
"windows"
],
"quotes": [
"error",
"single",
{
"allowTemplateLiterals":true //允许模板字符转
}
],
"semi": [
"error",
"never" //always 结尾要不要分号
]
// "no-console": 0,//手动添加
// "eqeqeq": 2//手动添加
}
}
安装依赖
开发环境下
npm i eslint babel-eslint --save-dev
添加检查启动项
"lint": "eslint --ext .js ./src",
vscode如何配置eslint的自动检查的?
{
"eslint.format.enable": true,
"eslint.run": "onSave",
// "editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
}
参考
在eslint上配置 "no-unused-vars": "off",
可以解决'next' is defined but never used
的问题。再次运行就不会出现错误了
有一种方式就是eslint过不了你就无法提交。
安装pre-commit依赖,意思是在git commit之前做什么。
npm i pre-commit --save-dev
在package.json下面
"pre-commit":[
"lint"
]
开发环境的debug
inspect调试,在生产环境下进行调试,加上 --inspect=9229
"dev": "cross-env NODE_ENV=dev ./node_modules/.bin/nodemon --inspect=9229 bin/www",
怎么打开,在chrome浏览器上打开
点击inspect,查看console
访问localhost:3000
光用console不行,需要打断点调试
加断点就是加debugger
debugger怎么调试?在source栏,跟chrome的调试貌似很相似
完善开发环境 -404和错误页 -路由
routes文件夹下新建view文件夹和api文件夹,在view文件夹下新建error.js
/**
* @description error 404 页面路由
* @author bokolin
*/
const router = require('koa-router')()
router.get('/error',async (ctx,next)=>{
ctx.body = {
title:'后端错误页面'
}
})
router.get('*',async (ctx,next)=>{
ctx.body = {
title:'后端404页面'
}
})
module.exports = router
在app.js里面注册这个路由
const errorViewRouter = require('./routes/views/error')
//因为有404路由,所以放在最下方
app.use(errorViewRouter.routes(), errorViewRouter.allowedMethods())
如果是开发环境,发生错误的时候就会输出错误信息,如果是生产环境发生错误时会跳转到错误页。
const {isProd} =require('./utils/env')
// error handler
let onErrorConf = {}
if(isProd){
onErrorConf = {
redirect:'/error'
}
}
onerror(app,onErrorConf)
如何人为的去抛出一个错误
throw Error()
发生错误的时候跳转到了错误页
但是访问*页面的时候没有跳转到404页面,因为之前加了个前缀。