随着Node.js的横空处世,本来目的是解决部分后端的问题,谁知道却无意间给前端开发带来了一场颠覆性的革命,从此前端拉开了现代化开发的序幕。如今,作为前端开发,无论是想进阶或是拓宽个人知识边界,node.js早已是前端必须掌握的了。拿下node.js,你还在犹豫什么?

Koa.js是基于node.js的一个开发框架,小巧灵活,对于一些中小型项目开发还是比较友好的。Koa上手简单,因此成为了不少小伙伴上手node开发的选择之一。本文主要从以下几个方面讲解koa后端开发最核心的部分内容,让人人都是全栈小能手:

  • 安装Koa2与启动一个hello world服务
  • Koa的接口开发
  • 什么是REST API?
  • 在Koa开发REST风格的API
  • 使用PostMan测试我们的接口
  • 安装MongoDb数据库
  • MongoDb可视化工具的使用
  • 使用mongoose操作MongoDb数据库
  • 对前端提交的密码进行加密与解密
  • 在Koa中使用JWT做登录鉴权
  • 过滤用户提交的数据,防止XSS攻击
  • 使用mocha进行同步、异步和接口的单元测试
  • 单元测试覆盖率

安装Koa2与启动一个hello world服务

开发node后端需要安装node.js环境和npm包管理工具,这块就不多说了,相信现在的前端小伙伴基本都会的。首先创建一个文件文件夹作为我们的项目目录:

// 终端。创建koa-test并进入该目录
mkdir koa-test
cd koa-test
复制代码

紧接着,像我们平时做前端项目一样安装koa库:

// 终端:安装koa2
cnpm i koa -S
复制代码

根目录下创建app.js作为我们程序的入口,就像我们vue项目中的main.js:

// 引入Koa库并初始化
const Koa = require('koa');
const app = new Koa();// 启动服务
// 监听3000端口,就像vue中默认的是监听8080一样
// 当然了你也可以监听其他端口
const appService = app.listen(3000, () => {console.log('[Koa]Server is starting at localhost:3000');
});// 导出服务(是为了供单测使用)
// 即使这里不导出也正常可以跑项目,后面会讲解这里为什么需要导出
module.exports = appService;
复制代码

最后在终端启动我们的程序服务:

// node是node.js的命令
node app.js
复制代码

如下图,在浏览器输入localhost:3000,就可以看我们开启的服务了:

koa路由(接口编写)

关于接口,相信大家都不陌生。作为前端开发,每天都会和后台人员提供的接口打交道。下面,我们看如何在Koa开发的服务中,开发供前端使用的接口吧!

  • 开发接口,其实就是在Koa中写路由,需要用到koa-router这个库,就好比前端vue开发中的vue-router也是前端的路由一样:
// 安装路由库
// -S是--save的简写,表示在生成环境中使用
// -D是--save-dev的简写,表示在开发环境中使用
cnpm i koa-router -S// 对于前端的参数,我们是需要获取使用的
// get提交的参数我们可以轻松获得,
// 但是post的数据,我们需要解析才能使用
// 因此需要安装koa-bodyparser库来处理post的数据
// 终端执行:
cnpm i koa-bodyparser -S
复制代码
  • 挂载koa-bodyparser和api路由

出于标准,我们需要将api相关的内容独立出来。就像我们vue项目开发中的src下也会分components、pages、api、assets等。这里我们在根目录下创建api文件夹,用来存放我们的路由文件,如图:

koa-test/api/index.js是我们api模块的出口,modules文件用来存放所有的API模块,例如这里有user相关接口都在user.js中等。具体的内容会放在后面细说。

下面我们看如何在app.js中挂载路由和其他中间件:

// koa-test/app.js// 引入koa-bodyparser用于解析post数据
const bodyParser = require('koa-bodyparser');// 引入根目录下的api路由
// 即把koa-test/api/index.js暴露出来的路由引入进来
const router = require('./api')// app.js中挂载koa-bodyparser
// 注意:在路由挂载前先挂载 koa-bodyparser
app.use(bodyParser());// 挂载路由
// 服务启动后可以在浏览器输入localhost:3000看到提示
app.use(async ctx => ctx.body = '服务启动成功');
app.use(router.routes());
app.use(router.allowedMethods());// 省略上面的其他代码
复制代码
  • 编写api的index.js
// 引入koa-router
const Router = require('koa-router');// 引入modules文件夹下的路由模块
const articleRouter = require('./modules/articles');// 实例化Router中间件
const router = new Router();// 注册路由
// 注意该路由模块文件在注册时增加了'/articles前缀
// 即该模块下所有的接口地址都会以/articles作为前缀
router.use('/articles', articleRouter.routes(), articleRouter.allowedMethods())// 将注册后的路由导出
// 供app.js中的koa挂载
module.exports = router;
复制代码
  • 编写具体的路由文件,eg:articles.js文件:
// 还是需要先导入koa-router
const Router = require('koa-router');
// 实例化router
const router = new Router();// 注册get方法
// 可以通过ctx.query获取parse后的参数
// 或者通过ctx.queryString获取序列化后的参数
router.get('/list', (ctx, next) => {ctx.body = {code: 200,data: [{id: 1,name: '小明',sex: 0,age: 22}],message: 'ok'};
});// 注册post方法
// app.js中挂载koa-bodyparse中间件后,
// 可以通过ctx.request.body获取post参数
// eg:这里的data就是前端post时提交的数据
router.post('/update', (ctx, next) => {let data = ctx.request.bodyctx.body = {code: 200,data,message: 'ok'};
});// 将该模块的路由(api接口)暴露出去
// 供api/index.js路由注册
module.exports = router;
复制代码

这里的ctx.body,就是返回给前端的json数据。

基本的路由编写就到这了,当然了,实际业务开发中还会涉及到put、delete类型等等的接口。基本写法都大同小异,这里附上koa-router的官网文档地址,查看更多的路由编写细节把。

什么是REST API?

引用网上的定义就是:

REST 指的是一组架构约束条件和原则。 满足这些约束条件和原则的应用程序或设计就是 RESTful

下面看如何定义rest风格的api接口:

// 获取操作使用get:
// 例如:获取全部文章
get /api/articles
// 带搜索条件带获取文章(例如页数、每页条数、文章类型等等)
get /api/articles?page=1=pageSize=50&type=1
// 获取id为12345带单条文章
get /api/articles/12345// 资源分类,
// eg:获取id为12345的文章的评论
get /api/articles/12345/comments
// 获取id为12345的文章的带搜索条件的评论
get /api/articles/12345/comments?page=1&pageSize=50// 提交数据使用post类型:
// 创建文章
post /api/articles// 更新数据使用put类型:
// 例如:更新id为12345的文章内容
put /api/articles/12345// 删除id为12345的文章
delete /api/articles/12345
复制代码

REST风格的API编写可以参考廖雪峰大大的这篇 编写REST API 文章

在Koa开发REST风格的API

在Koa中开发REST风格的API也很简单,koa-router为我们的ctx对象提供了params对象,可以获取REST风格的API中的参数。很像vue-router中的动态路由有木有?

router.get('/user/:userId', async ctx => {// 获取动态路由的参数// 通过koa-router提供的ctx.params对象获取const id = ctx.params.userId// 省略其他代码
}
复制代码

使用PostMan测试我们的接口

我们在开发接口过程中,肯定需要测试我们写的接口正不正确,有木有按照预期返回结果。那么怎么访问我们的接口查看是否正确呢?最简单的肯定有那么一款工具完,我们直接在上面操作就好了,呢~~如下,可以安装postman使用:

这样的话,我们可以创建接口来访问我们写的接口服务,还可以携带各种参数,调试起来还是非常方便的,这个就不多说了,网上搜postman下载就可以了,免费开源的。

安装MongoDb数据库

OK,上面说完了编写接口,对应的肯定需要我们操作数据库,然后给前端返回数据,例如基本的增删改查呀。下面先看基本mongodb数据的安装吧,这里以Mac OS为列:

这里介绍的是在终端用curl的方式安装的(显示骚骚的~~),其实直接到官网下载mongo的安装包也是一样的:

  • (很骚气滴)安装mongoDb
# 进入 /usr/local
cd /usr/local# 下载
sudo curl -O https://fastdl.mongodb.org/osx/mongodb-osx-ssl-x86_64-4.0.9.tgz# 解压
sudo tar -zxvf mongodb-osx-ssl-x86_64-4.0.9.tgz# 重命名为 mongodb 目录
sudo mv mongodb-osx-x86_64-4.0.9/ mongodb// 添加环境变量
export PATH=/usr/local/mongodb/bin:$PATH// 新建一个数据库存储目录
sudo mkdir -p /data/db// 启动mongod
sudo mongod
复制代码

mongodb的安装过程很简单,就不赘述了,更多的安装方法可以参考 mongoDb 安装参考地址

MongoDb可视化工具的使用

mongodb的可视化工具,我这里推荐的是Studio 3T,可以很方便的连接数据库,查看数据库的内容,或者操作数据库等。

最后这里附上Robo3的下载安装地址,安装很简单,就像装个qq一样,不赘述了。

mongoose操作MongoDb数据库

在node中,我们基本上是使用mongoose连接、操作数据库。首先,我们需要安装mongoose:

// 安装mongoose
cnpm i mongoose -S
复制代码

而后,在文件根目录下新建databse文件夹,用来专门放置连接和操作数据相关的文件:

database/index.js中,我们用来写连接数据库的方法,最后将其导出供app.js中连接使用:

// 引入mongoose库
const mongoose = require('mongoose');// 定义数据库地址的常量
// 更标准的可以新建一个数据配置文件,
// 用来专门存放数据相关的配置,比如账号密码等等
const DB_ADDRESS = 'mongodb://localhost/koa-test';mongoose.Promise = global.Promise;
mongoose.set('useCreateIndex', true);// 简单封装log
const log = console.log.bind(console);// 定义连接函数
const connect = () => {// 重连次数let connectTimes = 0;// 设置最大重连次数const MAX_CONNECT_TIMES = 3;// 断线重连const reconnectDB = (resolve, reject) => {if (connectTimes < MAX_CONNECT_TIMES) {connectTimes++;mongoose.connect(DB_ADDRESS, connectConfig);} else {log('[mongodb] database connect fail!');reject();}}// 连接数据库mongoose.connect(DB_ADDRESS, connectConfig);return new Promise((resolve, reject) => {// 监听数据库断开,重新连接mongoose.connection.on('disconnected', () => {reconnectDB(reject);});// 监听数据库连接出错,重新连接mongoose.connection.on('error', err => {log(err);reconnectDB(reject);});// 监听连接成功mongoose.connection.on('open', () => {log('[mongodb server] database connect success!');resolve();});});
};// 暴露出去
exports.connect = connect;// 还需要引入schema,在下面演示
// ……
复制代码

这里主要的作用就是:

(1)通过mongoose.connect()方法连接数据库;  (2)监听disconnected和error事件,进行数据库重连,并且最多重连三次;(3)返回promise来告知连接成功与否
复制代码
  • 引入所有的schema

通过之前的文件夹截图可以看出,我们创建了schema文件夹,是用来存放所有数据库建模相关的内容,其实就是通过schema来对数据库进行操作的。下面看下如何导入我们所有的schema下的文件的(虽然一个个引入也是可以的,但是我们是有追求的程序猿~~):

在databse/index.js:

// 引入glob
const glob = require('glob');// 引入弄的的path方法
// 可以读取、解析、拼接路径等等
const path = require('path');// 暴露一个initSchemas方法
// 用于导入database/schema文件夹下所有schema
exports.initSchemas = () => {// 通过glob读取schema文件夹下内容glob.sync(path.resolve(__dirname, './schema/', '**/*.js')).forEach(require);
}
复制代码

不清楚glob用法的,这里附上glob文档地址;

更多mongoose的内容可以查看mongoose中文文档地址;

  • 简单说下path模块常用的方法:

path是node提供的一个模块,主要用来处理和路径相关的内容:

// path使用前,还是需要先导入
const path = require('path');// join方法可以将所有参数连接起来,返回一个路径
path.join() 
// eg:
path.join('a', 'b', 'c', 'd'); // a/b/c/d
path.join(__dirname, '/a', '//b', '///c', 'd'); // /Users/yoreirei/Documents/demo/node-demo/a/b/c/d
path.join(__dirname, 'a', 'b', '../c', 'd'); // /Users/yoreirei/Documents/demo/node-demo/a/c/d
path.join(__dirname, 'a', './b', './c', './d'); // /Users/yoreirei/Documents/demo/node-demo/a/b/c/d// parse方法将路径解析为一个路径对象
path.parse()
// eg:
path.parse(path1) // { root: '', dir: 'a/b/c', base: 'd', ext: '', name: 'd' }
path.parse(path2) // { root: '/', dir: '/Users/yoreirei/Documents/demo/node-demo/a/b/c', base: 'd', ext: '', name: 'd' }// format方法将路径对象转换成路径地址
path.format(parse1) // a/b/c/d
复制代码

注意:__dirname获取的是当前文件模块所在的绝对路径。这个前端小伙伴在vue-cli的entry应该看到过,很熟悉吧。

拓展来一下,OK,我们继续schema建模。

  • Schema建模,通过schema操作数据库

// database/schema/User.js

// 引入mongoose
const mongoose = require('mongoose');
// 获取mongoose.Schema方法用于建模
const { Schema } = mongoose;// 生成id
let ObjectId = Schema.Types.ObjectId;// 创建用户的schema
// 例如创建一个包含用户名、密码、创建时间、
// 最后登录时间、点赞内容、收藏内容的schema
const userSchema = new Schema({UserId: ObjectId,// 我们可以定义每个字段的类型,例如String、Number、Array等等// 可以定义该字段的值是否唯一,如果设置了唯一,// 那么后续插入相同的值时就会报错userName: {unique: true,type: String},password: String,likes: {type: Array,default: []},collect: {type: Array,default: []}
}, {// 加入该配置项,会自动生成创建时间// 在文档更新时,也会自动更新时间timestamps: {createdAt: 'createdAt',updatedAt: 'updatedAt'}
});// 最后,使用mongoose发布模型
mongoose.model('User', userSchema);
复制代码

使用schema建模就是这么简单,小伙伴可以自己扩展创建其他schema。这个操作其实就类似于其他数据中的建表。

对前端提交的密码进行加密与解密

基本上我们的服务中都会涉及到用户的注册和登录等等。而对于用户注册的密码,我们是不会明文保存的,这样是不安全的。一般的做法都是对明文密码进行加密后存储,而用户登录时再对用户的密码加密后后和数据库中加密过的密码进行比对,看是否正确。而前端常见的也可以在用户提交时进行md5等方式的加密提交。

关于加密,我们可以使用bcript对密码的加密与解密。

  • 加密

我们需要对用户注册时的密码进行加密,使其不可逆。首先,我们需要安装bcript库:

// 安装bcript
cnpm i bcript -S
复制代码

database/schema/User.js

// 引入bcript
const bcrypt = require('bcrypt');// 定义bcrip加密时的配置常量
const SALT_ROUNDS = 10;// 每次保存时进行密码加密
// 注意此处pre的第二个参数,不能是箭头函数,不然拿不到this
userSchema.pre('save', function(next) {bcrypt.genSalt(SALT_ROUNDS, (err, salt) => {if (err) return next(err);bcrypt.hash(this.password, salt, (err, hash) => {if (err) return next(err);// 将用户提交的密码替换成加密后的hashthis.password = hash;next();});});
});
复制代码

注意:我们这里的加密做法时,在用户建模的时候,监听save事件,即用户每次存储数据的时候,都会执行我们定义的回调,而我们就在回调的函数中进行加密的操作。

  • 解密

验证用户登录的密码时,我们需要拿到用户的密码然后通过bcript验证是否和加密后的数据一样。

database/schema/User.js:

// 定义userSchema的实例方法
// 解密user password
// 注意mehtod要加s
userSchema.methods = {// 定义一个对比密码是否正确的方法// userPassword用户提交的密码// passwordHash数据库查出来的加过密的密码comparePassword (userPassword, passwordHash) {return new Promise((resolve, reject) => {bcrypt.compare(userPassword, passwordHash, (err, res) => {// 验证完成// res值为false|true,表示密码不同/相同if (!err) return resolve(res);// 验证出错return reject(err);});});}
}
复制代码

注意:我们这里的做法是给schema增加一个实例方法,那么我们(例如编写登录接口,那么用户的密码后)通过调用schema的实例去比对密码是否正确。

  • 简单演示一下login接口(去掉了参数验证和JWT)
/*** 用户登录* @param { String } userName 用户名* @param { String } password 密码*/
router.post('/login', async ctx => {// 前提引入mongoose// 获取User集合(类似于其他数据的表)const userModal = mongoose.model('User');// 集合的实例const userInstance = new userModal();// 定义查询参数const query = { userName: data.userName };// 先查找用户是否存在await userModal.findOne(query).exec().then(async res => {// 用户存在,拿到用户数据// 调用集合的实例方法,比对密码是否正确// then回调表示验证操作完成// 通过返回的参数isMatch(true/false)表示验证是否正确await userInstance.comparePassword(data.password, res.password).then((isMatch) => {// 验证密码是否正确if (isMatch) {// 此处省略token生成,会在后面讲解// *****return ctx.body = {code: 200,message: 'ok'};}return ctx.body = {code: 400,message: '账号密码错误'};}).catch(() => {return ctx.body = {code: 500,message: error.message};})// 用户不存在,直接提示}).catch(() => {return ctx.body = {code: 400,data: null,message: '当前用户不存在'};});
});
复制代码

关于bcript的内容可以参考 bcript的npm文档地址

其实关于登录这一块,我们是需要做登录鉴权的,比如是否过期等等,再复杂一些还会有redis持久化等等。这里省略了JWT登录鉴权,下面会介绍。

在Koa中使用JWT做登录鉴权

jwt是常用的用户登录鉴权方式:

(1)前端通过登录接口拿到token,存到本地,前端在后续的增删改查的时候会在请求头携带token。

(2)后端会根据请求时携带的authorization(即用户token),判断用户是否登录过期(统一拦截),登录过期则返回401,或者判断当前用户是否有权限进行此操作。

在koa2中使用jwt,要提到两个中间件:

(1)jsonwebtoken 生成和解析token

(2)koa-jwt 拦截(全部/部分)用户请求并验证token

  • 生成token

首先安装jsonwebtoken:

// 安装jsonwebtoken
cnpm i jsonwebtoken -S
复制代码

api/modules/user.js中

const { createToken } = require('../../utils/account');
// 根据上面的登录接口,在用户账号密码查询正确后
// 生成token返回给前端,createToken方法往后看
const token = createToken(res)
return ctx.body = {code: 200,data: token,message: 'ok'
};
复制代码

根目录下新建utils文件夹,

utils/account.js

// 引入jsonwebtoken
const JWT = require('jsonwebtoken');// 自定义生成token的密钥(随意定义的字符串)
// 就其安全性而言,不能暴露给前端,不然就可以随意拿到token
const JWT_SECRET = 'system-user-token';// 生成JWT Token
// 同时可以设置过期时间
exports.createToken = (config = {}, expiresIn = '7 days') => {const { userName, _id } = config;const options = { userName, _id };const custom = { expiresIn };// 通过配置参数,然后调用JWT.sign方法就会生成tokenreturn JWT.sign(options, JWT_SECRET, custom);
};// 暴露出密钥
// 这里将密钥暴露出去是为了后面验证的时候会用到
// 为了统一,不用处处写'system-user-token'这个字符串而已
exports.JWT_SECRET = JWT_SECRET;
复制代码
  • 请求拦截验证token

现在完成了登录接口生成token问题,那么在用户请求的时候,我们还需要拦截用户请求并验证是否过期。下当然就是koa-jwt上场了:

首先安装:

// 安装koa-jwt
cnpm i koa-jwt -S
复制代码

app.js中做统一拦截,并设置不需要token的接口(例如登录、注册等接口):

// 引入jwt
const jwt = require('koa-jwt');
// 拿到我们的密钥字符串
const { JWT_SECRET } = require('./utils/account');// jwt验证错误的处理
// jwt会对验证不通过的路由返回401状态码
// 我们通过koa拦截错误,并对状态码为401的返回无权限的提示
// 注意:需要放在jwt中间件挂载之前
app.use(function(ctx, next){return next().catch((err) => {if (401 == err.status) {ctx.status = 401;ctx.body = {code: 401,message: '暂无权限'};} else {throw err;}});
});// 挂载jwt中间件
// secret参数是用于验证的密钥
// unless方法,设置不需要token的接口
app.use(jwt({ secret: JWT_SECRET }).unless({path: ['/login','/register']})
);
复制代码

其实,koa-jwt是封装了koa-less和jsonwebtoken这两个中间件,。

koa-less的作用是只有在koa-less参数不匹配的时候才执行前面的中间件。 jsonwebtoken就是上面我们用的用于生成和解析token的。

看到这,小伙伴是不是想说:

  • 解析token

在很多时候我们需要拿到前端携带的token,从token中获取用户相关的信息,然后再做某些事情。例如,拿到用户的token后,根据token解析出用户的id,然后根据id查询用户存在后再执行某些操作。

api/article.js接口文件,这里的发布文章接口,我们在拿到用户提交的数据时,根据header中的authorization读取到用户的信息:

// 省略了部分代码
const Router = require('koa-router');
const mongoose = require('mongoose');
// 先导入我们封装的两个方法
const { decodeToken, parseAuth } = require('../../utils/account');// 定义文章发布接口
router.post('/article', async ctx => {let data = ctx.request.body;// 省略参数验证部分// ****// 解析出用户tokenconst authorization = parseAuth(ctx);// 根据token解析出token中的用户_idconst tokenDecoded = decodeToken(authorization);const { _id } = tokenDecoded;const userModal = mongoose.model('User');// 先查询用户是否存在,拿到用户信息await userModal.findById(_id).exec()// 在用户查到后,再进行创建文章的操作.then(async res => {data.author = res.userNameconst articleModal = mongoose.model('Article');const newArticle = articleModal(data);// 存储文章数据await newArticle.save().then(() => {return ctx.body = {code: 200,message: '保存成功'};}).catch(error => {return ctx.body = {code: 500,message: error.message || '保存失败'};});}).catch(err => {return ctx.body = {code: 500,message: err.message || '用户不存在'}})
});module.exports = router;
复制代码

utils/account.js:

const JWT = require('jsonwebtoken');// 从ctx中解析authorization
exports.parseAuth = ctx => {if (!ctx || !ctx.header.authorization) return null;const parts = ctx.header.authorization.split(' ');if (parts.length < 2) return null;return parts[1];
}// 解析JWT Token
exports.decodeToken = (token) => {return JWT.decode(token);
};
复制代码

注意: (1)主要思路就是,我们通过用户请求的header中携带的authorization,解析出用户的_id,然后通过_id去数据库查出用户对应的信息(项目大的话,这里会使用例如redis的缓存技术去读取用户的信息,而不是每次直接操作数据库),然后再创建文章。

(2)解析authorization的方法很简单,即使直接调用jsonwebtoken这个库的decode方法即可,相关介绍官网都有说明。

(3)关于怎么拿到authorization这里要说明一点,其实我们拿到的是下面这种数据,它其实包含了两部分,所以我们需要解析出我们想要的内容(即空格后面的内容):

解析方法也很简单,就是根据空格分割,取后面那部分。

过滤用户数据,防止XSS攻击

上面提到了发布用户数据,那么对于用户提交的数据,如果不做过滤,用户很可能传一些<script>alert('动态注入js')</script>的恶意代码。

针对这种情况,我们可以对用户的内容进行过滤,对一些关键字符进行转译。

// 安装xss库
cnpm i xss -S// 使用,在我们存文章数据的schema中进行处理
// 在每次存的时候进行过滤
// schema/Article.js
const mongoose = require('mongoose');
const { Schema } = mongoose;
const xss = require('xss');const articleSchema = new Schema({// 省略部分内容
});// 每次保存时进行密码加密
// 注意此处pre的第二个参数,不能是箭头函数,不然拿不到this
articleSchema.pre('save', function(next) {// 对标题和内容进行xss过滤this.title = xss(this.title);this.content = xss(this.content);next();
});mongoose.model('Article', articleSchema);
复制代码

如下图,可以看到过滤前后的数据库数据对比:

更多关于xss的内容可查看xss 中文文档

注意:如果是mySql等关系型数据库,我们还会处理防止sql的注入等操作,关于这块,有兴趣的小伙伴可以自行研究研究。

使用mocha进行同步、异步和接口的单元测试

单元测试常见的风格有:行为驱动开发(BDD),测试驱动开发(TDD)。那么两者有什么区别呢?

(1)BDD关注的是整个系统的最终实现是否和用户期望一致。

(2)TDD关注的是取得快速反馈,使所有功能都是可用的。

  • 使用Mocha进行单元测试

首先安装:

// 安装
cnpm i mocha -D
复制代码

配置npm测试命令:

// 配置npm script命令
"scripts": {"test": "mocha"
}
复制代码

新建text/test.js,写测试代码:

// 引入node的断言库
// 其实就像是一个工具库
const assert = require('assert');
// 引入待测试文件
const lib = require('./index');// 测试iterate这个函数的功能
// describe定义测试的描述
// it定义打印的内容
describe('Math', () => {describe('#iterate', () => {it('should return 10', () => {// 通过断言判断是否通过测试assert.equal('10', lib.iterate(5, 5));});it('should return 0', () => {assert.equal('0', lib.iterate());});it('should return 10', () => {assert.equal('10', lib.iterate(1, 1));});});
});// test/index.js
const iterate = (...arg) => {if (!arg.length) return 0;return arg.reduce((val, cur) => val + cur);
};module.exports = {iterate
}// 终端运行
// 会自动查找test文件夹下所有的测试文件并执行测试
npm test
复制代码

可以看出,当结果不符合预期时会测试不通过:

更多内容可以参考 mocha 文档地址

  • 使用should断言库

上面的断言库我们使用的是node提供的asset断言模块,其实我还有很多其他选择,例如should.js和chai等等,因为这些库提供了比asset更为丰富的供。

这里演示一些should这个断言库:

// 安装should
cnpm i should -D// 引入
const should = require('should');// 使用(和上面node的类似)
require('should');
const lib = require('./index');/*** 单元测试
*/
describe('Math', () => {describe('#iterate', () => {it('should return 10', () => {lib.iterate(5, 5).should.be.equal(10);});it('should return 0', () => {lib.iterate().should.be.equal(0);});it('should return 10', () => {lib.iterate(1, 1).should.be.equal(10);});});
});
复制代码

should断言库的内容比node的assert断言功能更丰富。断言效果如下:

更多内容可以查看 shuold文档地址

到目前来看,chai其实更为流行一些,chai同时提供了TDD和BDD风格的用法。有兴趣的小伙伴可以翻阅其文档学习查看,基本用法也都大同小异吧。

  • 使用mocha异步测试

以上演示都是一些同步测试,那么mocha对异步的测试该怎么做呢?其实很简单,只需要手动调用一个回调函数:

// 待测试文件
// 模拟定义普通的一个异步函数
const asyncFunc = (cb) => {setTimeout(() => {console.log('async init after 1000')cb()}, 1000)
}// 测试代码
// PS:省略导入导出的操作了// 测试普通异步函数
describe('Async', () => {describe('#asyncFunc', () => {it('should an async function', done => {// eg:最关键的是在异步调用完成后,// 需要手动调用回调函数done,// 来告诉mocha异步调用完成asyncTest.asyncFunc(done)})})
});
复制代码

效果如下:

  • 对于需要异步调用的情况
// async异步函数的测试
// 模拟一个异步函数:
const getInfo = async (bool) => {return new Promise((resolve, reject) => {setTimeout(() => {// 如果参数是true,则成功返回// 否则返回失败if (bool) return resolve('success');return reject('fail');}, 1000);});
};// 测试
describe('Async', () => {describe('#getInfo', () => {it('should return success', done => {// 还是需要手动调用回调函数// async函数需要写在内部(async function () {try {// 等待异步完成后,手动调用done()await asyncTest.getInfo(true);done();} catch (error) {done(error)}})();});});
})
复制代码

效果如下:

如果 await asyncTest.getInfo(true);参数传入false,模拟测试不通过的效果,会看到下这个样子:

  • 更简单的写法,可以直接将it的回调第二个参数写成异步函数:
// 效果也是一样的
describe('#getInfo', () => {it('should return success', async () => {await asyncTest.getInfo(false);});
});
复制代码
  • 接口的单元测试

首先要安装supertest这个库

// 安装
cnpm i supertest -D
复制代码

测试内容:

// 导入我们的服务
// 前提是在app.js文件中,将启动后的服务导出
//eg: moudle.exports = app.listen(3000)
const app = require('../app');
// 导入用于接口的测试的supertest
const request = require('supertest');describe('GET /', () => {it('should return status with 200', (done) => {// 测试// get是测试get请求// expect是期望的内容request(app).get('/').expect(200).end((err, res) => {// 在end中得到接口的内容// 然后根据情况手动调用doneif (err) return done(err);done();});});
});
复制代码

测试结果通过,如下图:

// 文章详情接口需要authorization
// 我们的测试用例希望返回200状态码,但是返回了401
// 所以当前测试不通过
describe('GET /artiles/:id', () => {it('should an article info', (done) => {request(app).get('/api/articles/5d2edc370fddf68b438b6b53').expect(200, done);})
})
复制代码

更多内容可以参考 supertest文档地址

单元测试覆盖率

关于单元测试覆盖率,简单提一下,可以测试出我们的测试代码的覆盖情况是怎样的。

  • 安装
// 首先安装istanbul这个库
cnpm i istanbul -D
复制代码
  • 使用
 // 在test文件夹下,打开终端执行以下命令// 会测试index.js文件的代码测试覆盖情况istanbul cover index.js
复制代码

如图所示,我们可以看到8块代码覆盖了3个,2个分支但是一个都没有覆盖到,0个函数,6行代码覆盖了个,以及对应的覆盖率是多少。

同时,在test文件夹下可以看到生成了coverage文件夹,里面保存了覆盖率结果,可以点击index.html查看结果。

index.html可以看的更直观:

更多的内容参考istanbul文档吧。

好了,关于Koa2与mongodb的内容,就到这了,二期初步打算扩展以下关于Koa的内容:

  • 拓展:Koa安装mySql数据库
  • 扩展:Koa中mySql的基本操作(增删改查),做个curl仔
  • 扩展:mySql数据库的可视化工具

参考文献

  • 《koa官网》
  • 《REST API规范》作者:廖雪峰
  • 挑战全栈 Koa2免费视频教程 (共13集)作者:技术胖
  • 《Node.js开发实战》作者:忽如寄

示例代码同步在github,欢迎访问!

百尺竿头、日进一步
我是愣锤,欢迎交流与分享

转载于:https://juejin.im/post/5d3c51ad6fb9a07ead5a42bf

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 基于github和hexo搭建属于自己的博客

    虽然网络上已经有很多教程,但还是觉得把搭建过程自己描述下来更好! 操作步骤:可以自行搜索安装node.js和配置node.js环境,通过cmd命令或终端查看是否成功,成功界面如下:ps:版本可以不同,图中所示只是本机安装版本。安装git和配置git环境,可搜索廖雪峰老师的git教学学习…...

    2024/4/24 13:19:29
  2. node.js 学习笔记1-mocha

    1.mocha是什么mocha是一款流行的额JavaScript测试框架。 2.mocha的基本用法1.使用npm 安装mocha包,我并没有选择全局安装。只是在mocha的文件夹里的Package.json添加mocha的依赖。如下图{"name": "mocha-test","version": "0.0.1",&q…...

    2024/4/24 13:19:29
  3. Node.js学习笔记(二)

    今天的内容涉及Node的原理、运行机制和CommonJS的内容,会有点沉闷,也会有点困难,建议像我一样做一些笔记。 模块 在开发大型应用的时候,我们常常会用到全局变量,例如:var s="Hello"。但是,当我们的应用越来越大时,我们可能会不小心重复用了几个相同的变量或者…...

    2024/4/24 13:19:30
  4. NodeJS中的网络编程

    欢迎访问我的博客,祝码农同胞们早日走上人生巅峰,迎娶白富美~~~文章目录1 前言2 网络编程2.1 什么是网络编程2.2 网络编程需要什么3 TCP Server4 结语5 参考文章1 前言常听到网络编程,自己也有些模糊的概念,或许在哪里都用到过,只是不知道那是网络编程而已,今天花时间来好…...

    2024/4/28 4:11:07
  5. 记一次完整的新浪云部署nodejs项目上线完整流程及填坑处理!

    工欲善其事,必先利其器。在开始本次部署新浪云nodejs项目之前,请先做好以下准备工作:1.注册一个新浪微博账号! 2.使用注册好的新浪微博账号,登录新浪云网站:http://www.sinacloud.com/public/login/inviter/gaimrn-mddmzeKWrhKW7roB4gWZ_eIVrfrKydg.html 3.在你的计算机上…...

    2024/4/15 4:04:14
  6. 用hexo和github免费搭建自己的博客(win):1.准备

    用hexo和github免费搭建自己的博客(win):1.准备 要准备的有:node.js npm(node.js自带了) cmpn git hexo Visual Studio Code(非必须,但是在写博客.md和编写配置文件的时候很有用) markdown编辑器(非必须,如果你下载Visual Studio Code那么不用下载了)1.node.js/npm/cmpn…...

    2024/4/28 6:17:16
  7. webpack安装

    安装webpack ‘webpack’ 不是内部或外部命令解决办法以及npm配置 一、下载安装node.js(npm) 官网有最新版的nodejs,但是最新版可能有各种情况出现,网上的教程针对旧版的教程,这里使用一个廖雪峰老师给出的nodejs链接–》node.js国内镜像 按照步骤直接点击下一步完成安装,n…...

    2024/4/28 0:10:59
  8. VUE环境搭建

    一、下载安装node.js(npm)官网有最新版的nodejs,但是最新版可能有各种情况出现,网上的教程针对旧版的教程,这里使用一个廖雪峰老师给出的nodejs链接--》node.js国内镜像按照步骤直接点击下一步完成安装,nodejs默认集成了npm,无需再次安装。输入npm -v 也可以查看npm是否成…...

    2024/4/24 13:19:27
  9. 廖雪峰-WebSocket详解

    一、WebSocket是什么WebSocket是HTML5新增的协议,它的目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,比如说,服务器可以在任意时刻发送消息给浏览器。为什么传统的HTTP协议不能做到WebSocket实现的功能?这是因为HTTP协议是一个请求-响应协议,请求必须先由浏…...

    2024/4/24 13:19:25
  10. NodeJs学习笔记(一)

    我是跟着廖雪峰学习Nodejs 第一个node程序由于Node.js平台是在后端运行JavaScript代码,所以,必须首先在本机安装Node环境。命令行模式:Node交互模式:使用严格模式:1.后缀为.js的文件,就可以用node命令直接运行这个程序了。 2.Node的交互模式和直接运行.js文件区别 直接输…...

    2024/4/24 13:19:24
  11. Javascript学习资料

    node.js廖雪峰Node.js: https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434501245426ad4b91f2b880464ba876a8e3043fc8ef000...

    2024/4/24 13:19:24
  12. 使用 nodejs 写爬虫(一): 常用模块和 js 语法

    常用模块 常用模块有以下几个: fs-extra superagent cheerio log4js sequelize chalk puppeteer fs-extra 使用 async/await 的前提是必须将接口封装成 promise, 看一个简单的例子: const sleep = (milliseconds) => {return new Promise((resolve, reject) => {setTimeo…...

    2024/4/24 13:19:22
  13. 大神廖雪峰的web全栈学习体系图,拿走不谢!

    「 反思 」很多童鞋总是时不时的给我要一些,学习路线和一些vue、angular、react教程,我自己很懒,也没有系统的教程,所以有事件就整理了下这篇文章,干货福利内容 在文末↓几乎从事前端的童鞋都反思过:工作有几年的时间了,为什么自己技术水平提高缓慢,薪资也不如人意?对…...

    2024/4/24 13:19:21
  14. js温故而知新9(操作DOM)——学习廖雪峰的js教程

    1.操作DOM 操作一个DOM节点实际上就是这么几个操作:更新、遍历、添加、删除。 由于ID在HTML文档中是唯一的,所以document.getElementById()可以直接定位唯一的一个DOM节点。document.getElementsByTagName()和document.getElementsByClassName()总是返回一组DOM节点。要精确地…...

    2024/4/24 13:19:20
  15. Webpack傻瓜入门指南

    Webpack傻瓜式指南 这个写的好啊,师傅推荐的,安利安利! 最激动的是啥呢,今天我第一次听到了 热加载 ,哈哈~其实词挺多,然后就那么回事嘛,我没有傲娇脸,毕竟我是水货啊。 看完了是不是要自己试着敲一下?!var webpack = require(webpack); module.exports = {entry:…...

    2024/4/24 13:19:19
  16. node.js(npm)|bower(bootstrap)|git

    为什么80%的码农都做不了架构师?>>> node.js 安装步骤:http://www.runoob.com/nodejs/nodejs-install-setup.html Node.js is a JavaScript runtime built on Chromes V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes…...

    2024/4/24 13:19:18
  17. Koa学习2

    前言学习来源:廖雪峰教程 , 开发工具:WebStorm 该文主要记录下我自己URL的处理,参照上海电气node项目的思路来进行。正文思路:所有的URL处理函数都放到app.js里显得很乱,而且,每加一个URL,就需要修改app.js。随着URL越来越多,app.js就会越来越长。如果能把URL处理函数…...

    2024/4/24 13:19:18
  18. node了解

    学习资料 廖雪峰的官方网站—node.js Node.js 中文网:http://nodejs.cn/ Node.js 教程(菜鸟教程):http://www.runoob.com/nodejs/nodejs-tutorial.html 背景 1. chrome V8 简介 Apple 基于 WebKit 渲染引擎推出 Safari 浏览器,之后谷歌也基于 WebKit 渲染引擎推出 Chrome …...

    2024/4/24 13:19:16
  19. 廖雪峰JavaScript教程学习笔记(浏览器)06

    由于JavaScript的出现就是为了能在浏览器中运行,所以,浏览器自然是JavaScript开发者必须要关注的。目前主流的浏览器分这么几种:IE 6~11:国内用得最多的IE浏览器,历来对W3C标准支持差。从IE10开始支持ES6标准;Chrome:Google出品的基于Webkit内核浏览器,内置了非常强悍的…...

    2024/4/24 13:19:15
  20. NodeJs学习——模块系统

    module.exports与exports的区别每一个node.js执行文件,都自动创建一个module对象,同时,module对象会创建一个叫exports的属性,初始化的值是 {}module.exports = {};Node.js为了方便地导出功能函数,node.js会自动地实现以下这个语句foo.jsexports.a = function(){console.l…...

    2024/4/24 13:19:14

最新文章

  1. 2024.阳光能源追光计划暨大陆考察团交流分享会

    近日大陆考察团抵达香港&#xff0c;受到了本司热情接待和安排。公司于4月27日下午举办了阳光能源追光计划主题交流会。 会上公司营销部总监张超&#xff0c;分享了阳光能源近几年的能源发展之路及公司新推出的追光计划&#xff0c;得到了大陆考察交流团团长杨国均先生的高度赞…...

    2024/4/28 7:26:54
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. jQuery(一)

    文章目录 1. 基本介绍2.原理示意图3.快速入门1.下载jQuery2.创建文件夹&#xff0c;放入jQuery3.引入jQuery4.代码实例 4.jQuery对象与DOM对象转换1.基本介绍2.dom对象转换JQuery对象3.JQuery对象转换dom对象4.jQuery对象获取数据获取value使用val&#xff08;&#xff09;获取…...

    2024/4/26 21:12:47
  4. MongoDB聚合运算符:$map

    文章目录 语法举例对数组元素取整将摄氏度转为华氏度 $map聚合运算符将指定的表达式应用于数组元素&#xff0c;对数组每个元素进行计算并返回计算后的数组。 语法 { $map: { input: <expression>, as: <string>, in: <expression> } }参数说明&#xff1a…...

    2024/4/23 15:27:46
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/26 18:09:39
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/28 3:28:32
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/27 4:00:35
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/27 9:01:45
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/28 1:22:35
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/27 11:43:08
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/27 8:32:30
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57