本文主要介绍了上传单个文件、多个文件,文件数量大小限制、限制文件上传类型和对上传的图片进行不同大小的裁剪,阅读本篇文章需要具备一定的 node 和 koa 框架的基础知识以及 async await 语法,如果遇到不了解的知识点可以自己查阅资料,不了解按照本文的案例也可以实现,希望可以通过本篇文章让你掌握在 node 中实现文件上传,如果只需要源码,可在文章最后进行获取

目录

  • 环境准备
  • 项目初始化
  • 创建上传单个文件接口
  • 使用 koa-multer 实现单文件上传
  • 使用 koa-multer 实现多文件上传
    • array 的使用
    • fields 的使用
    • array 与 fields 的区别
  • 对文件进行限制-limits
  • 过滤文件-fileFilter
  • 使用 jimp 对文件进行不同大小的裁剪
  • 文件上传错误捕获
  • 源码展示

环境准备

  1. 需要本机具备 nodejs 环境

  2. postman 接口测试工具,或者其他测试工具都可以

  3. 需要的依赖如下:

    {"jimp": "^0.22.5","koa": "^2.14.1","koa-multer": "^1.0.2","koa-router": "^12.0.0","nodemon": "^2.0.20"
    }
    
  4. nodemon:让项目启动后,代码更改自动重新启动服务,方便调试

    • 当然如果你希望输入 nodemon 命令就可以启动项目可以在 package.json 文件中进行如下设置:

      {"name": "node-upload","version": "1.0.0","description": "file upload","main": "./main.js", // 设置为你项目的入口文件路径"scripts": {"test": "echo \"Error: no test specified\" && exit 1","start": "nodemon" // 设置启动命令为 nodemon },"author": "coderjc","license": "ISC","dependencies": {"jimp": "^0.22.5","koa": "^2.14.1","koa-multer": "^1.0.2","koa-router": "^12.0.0","nodemon": "^2.0.20"}
      }
      
  5. koa:是一个新的 web 框架,想在 node 中使用需要这个依赖

  6. koa-route:koa 框架的路由系统采用第三方库的形式实现

  7. koa-multer:帮助我们在 koa 中进行文件上传

  8. jimp:将图片进行不同大小的裁剪

项目初始化

  • 本文主为了案例演示,所以不进行对项目进行模块化的划分,统一在入口文件中进行演示,初始化入口文件如下:

    // 引入内置模块
    const path = require('path')
    // 引入第三方库
    const Koa = require('koa')
    const Router = require('koa-router')
    const multer = require('koa-multer')
    const Jimp = require('jimp')// 创建app实例
    const app = new Koa()// 创建路由实例
    //  - prefix:配置根路径
    const uploadRouter = new Router({ prefix: '/upload' })// 注册接口
    app.use(uploadRouter.routes())// 监听
    app.listen(8001, () => {console.log('koa server start success~')
    })
    

创建上传单个文件接口

  1. 我们通过创建的路由实例,可以在上面指定 http 请求类型,上传文件为 post 请求,因为已经配置了根路径,所以上传单个文件的路径为 / ,表示为默认路径,具体路由系统这部分本文不在进行赘述,创建接口如下:

    // 单文件上传接口
    uploadRouter.post('/', (ctx, next) => {ctx.body = '文件上传成功' // ctx.body 方法可以返回指定返回给客户端的信息
    })
    
  2. 上述就是在 koa 中一个接口的创建,我们可以使用 postman 测试一下工具是否搭建成功,postman 的使用方式本文不进行讲解,使用很简单,大家查阅资料即可,结果如下:

  3. 返回 文件上传成功 或自定义的信息,即表示接口搭建成功

使用 koa-multer 实现单文件上传

  1. 在实现文件上传之前,我们需要一个文件夹来存储我们上传的文件,在真实的开发中,一般会有一台其余的服务器用来存储文件,本案例就直接用本地代替了,我们在项目的根目录中创建文件夹 uploads

  2. 在实现之前,我们先来看一写参数解析,摘自官网,大家可以放心参考

    属性名 描述
    fieldname 表单中指定的字段名称
    originalname 用户计算机上文件的名称
    encoding 文件的编码类型
    mimetype 文件的 MIME 类型
    size 文件大小(以 bytes 为单位)
    destination 保存文件的文件夹
    filename 控件中的文件名
    path 上传文件的完整路径
    buffer Buffer 整个文件
  3. 在 koa-multer 中实现文件上传具备两种写法:

    1. 不带文件后缀名存储

      1. 后端代码:

        // 配置 multer
        const upload = multer({dest: './uploads' // 文件存储地址
        })// 使用 single() 方法获取单张图片,此方法需要传递一个参数,为前端是上传键名一致
        //  - single 方法会返回一个函数,可以作为一个中间件使用,根据这个特性,我们可以使用使用一个变量接收返回的函数
        const one = upload.single('file')// 将返回的函数 one 作为中间件
        uploadRouter.post('/', one, (ctx, next) => {// 如果你需要在后面的中间件获取文件信息,可以使用 ctx.req.file获取ctx.body = {flie: ctx.req.file}
        })
        
      2. postman 模拟前端发送请求

      3. 我们来看一下上传前的 uploads 文件夹,展开没有任何内容

      4. 上传后 uploads 文件夹内容如下:不过由于没有添加后缀名,无法直接查看,可以自己添加后缀名进行查看


      5. 来看一下返回给客户端的文件信息具体内容:

    2. 文件携带后缀名存储

      1. 如果需要设置后缀名,我们有两个选项可用,destinationfilename。它们都是确定文件存储位置的函数

      2. destination:用于确定存储文件的地址

      3. filename:用于确定文件夹中的文件命名

      4. 首先我们需要使用配置一下两个函数,如下:

        // 文件配置
        //  - req 请求信息
        //  - flie 文件信息
        //  - cb 回调函数
        const storage = multer.diskStorage({// 存储路径destination: (req, flie, cb) => {// cb: (error: Error | null, destination: string)//  - 通过将鼠标放入 cb 上,会出现一个提示,传入两个参数//    - 参数一:传入一个错误//    - 参数二:传入存储路径cb(null, './uploads')},// 设置文件名filename: (req, flie, cb) => {// path.extname() 方法获取文件后缀名//  - originalname 用户计算机上的文件的名称// 为了保证名称的唯一性,可以使用时间戳作为文件名称// path.extname() 可以获取一个文件的后缀名cb(null, Date.now() + path.extname(flie.originalname))}
        })// multer 方法会返回一个函数,作为中间件
        //  - 将配置项传入 multer
        const upload = multer({ storage })const one = upload.single('file')// 将返回的函数 one 作为中间件
        uploadRouter.post('/', one, (ctx, next) => {// 如果你需要在后面的中间件获取文件信息,可以使用 ctx.req.file获取ctx.body = {flie: ctx.req.file}
        })
        
      5. 演示效果我这里就不在进行展示了,大家可以自行尝试

  4. 采用那种方式都应该是实际需求为准,没有定论,大家自行斟酌即可

使用 koa-multer 实现多文件上传

  • 实现多文件上传官方文档中给我们提供了两个方法,array 和 fields,具体使用方法与区别看后续讲解

array 的使用

  1. array 方法的使用与单个文件上传的区别很小,首先我我们需要将调用的方法从 single 改成 array,如下:

    //   - 第一个参数指定文件名称
    //   - 第二个参数指定文件上传的数量
    // 其余文件配置项即使用与单文件上传别无而至
    // - 接收函数变量为了符合语义我这里改为
    const more = upload.array('files', 2)
    
  2. 为了方便给大家演示,多文件上传我更换了一个接口路径,如下:

    uploadRouter.post('/more', more, pictureResize, (ctx, next) => {// 如果是非文件的文本信息,可以从 ctx.req.body 获取,这里会给大家进行一个展示// 单文件获取信息方式是 ctx.req.file,而多文件是 ctx.req.files,需要大家注意一下ctx.body = {text: ctx.req.body || '',flies: ctx.req.files}
    })
    
  3. 配置完成后我们使用 postman 模拟请求发送,如下:

  4. 我们先来看一下上传的文件,是否在 uploads 文件夹中成功上传,为了方便对比,我已经提前将 uploads 文件夹清空,查看一下结果,如下:

  5. 在看一下返回给客户端的信息

  6. 当然超过文件限制数量,会导致报错,让程序崩溃,具体的如何处理这个异常,我们后续会讲解到

fields 的使用

  1. fields 的使用方法与 array 大同小异,只是在使用方式上有一些差距,我们先看一下具体上使用的差距,如下:

    // 对fields多文件上传时出现的错误进行捕获
    // fields应该是一个对象数组,并且 name 可以选择一个 maxCount,
    //  - name 即文件的字段名
    //  - maxCount 即指定这个子段的文件可以上传的数量
    const moreFields = upload.fields([{ name: 'avatar', maxCount: 1 },{ name: 'files', maxCount: 2 }
    ])
    
  2. 还是为了进行演示,我新建了一个接口路径,如下:

    // 请求路径修改为
    uploadRouter.post('/fields', moreFields, (ctx, next) => {ctx.body = {text: ctx.req.body || '',flies: ctx.req.files}
    })
    
  3. 使用 postman 发送请求,如下:

  4. 还是清空了 uploads 文件夹,方便我们查看,如下:

array 与 fields 的区别

  • 相信经过上面两个案例的对比,大家对两者的区别已经知道了,两者的区别在于:array 方法接收的多个文件必须是同一字段名称,fields 是可以对多个字段名称进行获取的,一般情况下推荐大家使用 arrary 方法,别问,问就是方便好用

对文件进行限制-limits

  1. 什么是对文件的限制,比如限制一下当前文件的上传的大小,数量等等,还是一样我给大家从官方上带来了 limits 的属性解析,如下:

    钥匙 描述 默认
    fieldNameSize 最大字段名称大小 100 字节
    fieldSize 最大字段值大小(以字节为单位) 1MB
    fields 非文件字段的最大数量 无穷
    fileSize 对于多部分表单,最大文件大小(以字节为单位) 无穷
    files 对于多部分表单,文件字段的最大数量 无穷
    parts 对于多部分表单,部分的最大数量(字段 + 文件) 无穷
    headerPairs 对于多部分表单,要解析的标题键=>值对的最大数量 2000
  2. 我给大家解析一下非文件字段,一般表单提交的时候,我们只有 文本和文件 的区别,非文件指的就是文本数据,这里的文本数据可不是 txt 文件结尾的,就如上述提交中我们提交的文本数据,即下图中橙色框中部分,如下:

  3. 那我现在介绍一下 limits 的使用,如下:

    // 限制上传的文件
    // tips:使用的如果是 single 方法则只能发送 1 个文件,否则会报错:Unexpected field,所以单个文件时不用添加 files 限制单次文件数量// 这里附上单位转换
    // B(Byte) 1Byte=8bit
    // KB 1KB=1024B
    // MB 1MB=1024KB
    // GB 1GB=1024MB
    // TB 1TB=1024GB
    const limits = {fields: 10,fileSize: 104857600,files: 5
    }// 将此项传入 multer
    const upload = multer({ storage, limits })
    
  4. 至于效果演示我这里就不进行演示了,属性比较多,大家有兴趣可以自行演练

过滤文件-fileFilter

  1. 文件过滤指的是排除一些指定类型的文件,为了安全考虑,我们通常会将可执行文件排除,不过这里只是作于演示,我就采用排除 png 格式的文件

  2. fileFiter 为我们提供了三个回调函数,分别是:

    1. cb(null, true):放行文件
    2. cb(null, false):禁止文件
    3. cb(new Error(‘png格式文件不允许上传~’)):抛出一个错误
  3. fileFiter 的使用如下:

    const fileFilter = (req, file, cb) => {// 获取文件类型后缀名const type = path.extname(file.originalname)// 排除 png 格式的图片if (type !== '.png') {cb(null, true)} else {cb(null, false)}
    }// 将此项传入 multer
    const upload = multer({fileFilter, storage, limits })
    
  4. 按照上述的例子就可以对文件类型进行过滤了,我们先来看在 postman 中发送请求,如下:

  5. 我们看一下 uploads 文件下的内容,如下:

  6. 可以看到 png 的格式是没有上传的,我们再看一下返回给客户端的结果,如下:

  7. 是不是发现好像这样有一些不友好,并不清楚那个文件没有符合格式,没有上传成功,怎么实现这个功能暂且先放下,稍后在讲,我们先看一下第三个回调函数,修改一下 fileFilter 的配置项,如下:

    const fileFilter = (req, file, cb) => {// 获取文件类型后缀名const type = path.extname(file.originalname)// 排除 png 格式的图片if (type !== '.png') {cb(null, true)} else {// 在此处抛出一个错误cb(new Error('png格式文件不允许上传~'))}
    }
    
  8. postmon 模拟的请求不变,我们在看一下执行结果,看看 uploads 文件夹下是否多出文件,如下:

  1. 可以看到图片依然没有增加,证明此次因为一个文件的不符合,导致其他符合的文件也没有上传,这样一看好像也是不太符合我们的需求,在解决之前我们看一下返回给客户端的结果,如下:

  2. 这样虽然有了提示,但是就没有实现符合的文件成功存储的功能,关于这一点我在官网中也没有找到合适的例子,所以我采用了一个挂载变量的方法来实现这个效果,如下:

    const path = require('path')
    const Koa = require('koa')
    const Router = require('koa-router')
    const multer = require('koa-multer')
    const Jimp = require('jimp')const app = new Koa()// 在顶部注册一个普通的中间件
    //  - 在这里注册中间件会让任何请求都会触发这个中间件,原因的话我这里就不赘述了大家可以去学习一个相关的知识
    //  - 当然可以只注册在需要处理文件上传的接口处,具体位置自己决定即可
    app.use(async (ctx, next)=>{// 通过给 ctx.req 属性上挂在一个 错误文件的 属性,赋值为空数组,来记录本次上传失败的文件名称ctx.req.failFiles = []await next()
    })// storage limits 等配置项我这里就不在书写了,大家自行补充即可,fileFilter 配置项需要更改
    const fileFilter = (req, file, cb) => {const type = path.extname(file.originalname)if (type !== '.png') {cb(null, true)} else {// 如果文件类型不符合就会经过这里,所以可以在这里将失败的文件名称存储进 failFiles 数组req.failFiles.push(file.originalname)cb(null, false)}
    }const upload = multer({fileFilter, storage, limits })const uploadRouter = new Router({ prefix: '/upload' })uploadRouter.post('/more', captures, (ctx, next) => {ctx.body = {// 通过读取 failFiles 数组,即可展示是失败的文件fail: `${ctx.req.failFiles}等文件上传失败`,flies: ctx.req.files}
    })app.use(uploadRouter.routes())app.listen(8001, () => {console.log('koa server start success~')
    })
    
  3. 我们发送请求看一下 uploads 文件下是否会增加新两个文件,如下:

  4. 可以看到,符合类型的文件存储成功,在看一下返回给客户端的信息,如下:

  5. 现在就可以完美实现我们的需求,当然三种方式需要结合具体业务场景使用,如果大家发现有更好的方式可以一起谈论

使用 jimp 对文件进行不同大小的裁剪

  1. 为什么需要进行不同大小的裁剪呢,在真实的开发中,通常有些时候我们只需要展示一个预览图,也就是一张比较小的图片,而不需要展示一张原图,当然这个图片的展示可以由前端通过样式限制,但是如果每次都返回一张原图,那么请求压力无疑会大一些,所以可以在上传时就进行不同大小的裁剪,在合适的请求返回对应的图片

  2. 这个库的使用方式如下:

    // 获取图片不同大小
    const pictureResize = async (ctx, next) => {// 获取所有图像信息const files = ctx.req.files// 通过 sharp/jimp 库对图片进行裁剪for (const item of files) {// 通过 Jimp.read(路径) 读取文件信息,并返回一个对象//  - 如果不希望使用同步的方法等待图片处理,就可以使用 then 方法异步处理,处理图片的时间还是比较长的// 拼接写入路径// 获取文件类型后缀const type = path.extname(item.filename)const filename = item.filename.replace(type, '')const url = path.join(item.destination, filename)Jimp.read(item.path).then(res => {// 通过 resize 方法裁剪:参数一设置宽度,参数二:让高度随高度按比例裁剪// 在通过 write 方法写入,传入写入路径即可//  - 并拼接后缀 large(大图) middle(中等图片) small(小图)res.resize(1280, Jimp.AUTO).write(`${url}-large${type}`)res.resize(640, Jimp.AUTO).write(`${url}-middle${type}`)res.resize(320, Jimp.AUTO).write(`${url}-small${type}`)})}await next()
    }// 在接口处添加使用即可
  3. 来看一下 uploads 问价下是否存储了 原图,大小中图等不同的图片,我在前端发送了两张图片,所以会有八张图片,如下:

  4. 我们将图片打开对比看一下:

文件上传错误捕获

  1. 关于这个错误捕获官网中给出的例子是给 express 框架的,且是在 multer 的库中,那难道就没有办法可以捕获这个错误了吗,当然老生常谈的 trycatch 这里就不在讨论,在 koa 框架中,中间件返回的是一个 promise 对象,所以 koa-multer 这个库自然也不能例外,返回的函数是 promise 类型

  2. 所以根据这一点,我们可以单独的对这个方法进行错误捕获,如下:

    const captures = async (ctx, next) => {// 文件上传错误捕获// 通过使用 then 和 catch 方法进行捕获,不理解的可以去补充一个 promise 相关的知识//  - 限制文件上传为 1let err = await upload.array('files', 2)(ctx, next).then(res => res).catch(err => err)if (err) {ctx.body = {msg: err.message}}
    }// 当然,接口中的函数也需要更换
    uploadRouter.post('/more', captures, (ctx, next) => {ctx.body = {fail: `${ctx.req.failFiles}等文件上传失败`,flies: ctx.req.files}
    })
    
  3. 我们测试一下,当限制数量为 2,上传 3 个文件时的结果,看一下返回给客户端的结果

  4. 再看一下后端程序有没有崩溃

  5. 此方法是并没有在官方文档中给出,所以具体实现是否一定稳定,还有待考究,如果大家有更好的方法,欢迎讨论

源码展示

  1. 当然,可能演示时为了区别,和上面实例代码会有一些变量名的差别,但是逻辑是一样的,大家放心阅读

  2. // 引入内置模块
    const path = require('path')
    // 引入第三方库
    const Koa = require('koa')
    const Router = require('koa-router')
    const multer = require('koa-multer')
    const Jimp = require('jimp')// 创建app实例
    const app = new Koa()app.use(async (ctx, next)=>{ctx.req.failFiles = []await next()
    })// 创建路由实例
    const uploadRouter = new Router({ prefix: '/upload' })// 文件配置
    const storage = multer.diskStorage({// 存储路径destination: (req, flie, cb) => {cb(null, './uploads')},// 设置文件名filename: (req, flie, cb) => {cb(null, Date.now() + path.extname(flie.originalname))}
    })// 限制上传的文件
    const limits = {fields: 10,fileSize: 104857600,files: 5
    }// 对上传的文件进行过滤
    const fileFilter = (req, file, cb) => {const type = path.extname(file.originalname)if (type !== '.png') {cb(null, true)} else {req.failFiles.push(file.originalname)cb(null, false)}
    }// multer 方法会返回一个函数,作为中间件
    const upload = multer({ fileFilter, storage, limits })// 对单文件上传时出现的错误进行捕获
    const capture = async (ctx, next) => {let err = await upload.single('file')(ctx, next).then(res => res).catch(err => err)if (err) {ctx.body = {msg: err.message}}
    }// 对多文件上传时出现的错误进行捕获
    const captures = async (ctx, next) => {let err = await upload.array('files')(ctx, next).then(res => res).catch(err => err)if (err) {ctx.body = {msg: err.message}}
    }// 对fields多文件上传时出现的错误进行捕获
    const capturesFields = async (ctx, next) => {let err = await upload.fields([{ name: 'avatar', maxCount: 1 },{ name: 'files', maxCount: 3 }])(ctx, next).then(res => res).catch(err => err)if (err) {ctx.body = {msg: err.message}}
    }// 获取图片不同大小
    const pictureResize = async (ctx, next) => {const files = ctx.req.filesfor (const item of files) {const type = path.extname(item.filename)const filename = item.filename.replace(type, '')const url = path.join(item.destination, filename)Jimp.read(item.path).then(res => {res.resize(1280, Jimp.AUTO).write(`${url}-large${type}`)res.resize(640, Jimp.AUTO).write(`${url}-middle${type}`)res.resize(320, Jimp.AUTO).write(`${url}-small${type}`)})}await next()
    }// 单文件上传接口
    uploadRouter.post('/', capture, (ctx, next) => {ctx.body = {flie: ctx.req.file}
    })// 多文件上传接口
    uploadRouter.post('/more', captures, pictureResize, (ctx, next) => {ctx.body = {text: ctx.req.body || '',fail: `${ctx.req.failFiles}等文件上传失败`,flies: ctx.req.files}
    })// 测试多文件上传接口之 fields
    uploadRouter.post('/fields', capturesFields, (ctx, next) => {ctx.body = {text: ctx.req.body || '',flies: ctx.req.files}
    })// 注册接口
    app.use(uploadRouter.routes())// 监听
    app.listen(8001, () => {console.log('koa server start success~')
    })

在 node 中使用 koa-multer 库上传文件详解相关推荐

  1. Teams中阻止上传文件--详解

    之前在另外一篇文章中谈到过如何阻止向Office 365中上传文件,但是针对于Teams的文件上传没有详细介绍过. 这里首先让大家知道两个概念: Teams本身不止是一个软件在独立运行,后面包含了很多 ...

  2. 微信小程序上传文件详解

    做微信小程序难免会遇到上传文件的问题.今天就给大家说一个简单的上传文件的例子吧 wxml代码 <button bindtap="upload">上传文件</but ...

  3. SFTP上传文件详解

    JSch是Java Secure Channel的缩写.JSch是一个SSH2的纯Java实现.它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等,当然你也可以集成它的功能到 ...

  4. linux的ftp轮询上传文件,Android中实现异步轮询上传文件

    前言 前段时间要求项目中需要实现一个刷卡考勤的功能,因为涉及到上传图片文件,为加快考勤的速度,封装了一个异步轮询上传文件的帮助类 效果 先上效果图 设计思路 数据库使用的框架是GreenDao,一个非 ...

  5. java中formfile,基于Struts FormFile上传文件

    基于Struts文件上传(FormFile)详解 Struts中FormFile用于文件进行上传 1.在jsp文件中进行定义 名字: 头像: 2.在Form表单中定义FormFile /* * Gen ...

  6. 七牛云存储Python SDK使用教程 - 上传策略详解

    本教程旨在介绍如何使用七牛的Python SDK来快速地进行文件上传,下载,处理,管理等工作. 前言 我们在上面的两节中了解到,客户端上传文件时,需要从业务服务器申请一个上传凭证(Upload Tok ...

  7. java formfile_基于Struts文件上传(FormFile)详解

    Struts中FormFile用于文件进行上传 1.在jsp文件中进行定义 名字: 头像: 2.在Form表单中定义FormFile /* * Generated by MyEclipse Strut ...

  8. Pikachu靶场之文件上传漏洞详解

    Pikachu靶场之文件上传漏洞详解 前言 文件上传漏洞简述 什么是文件上传漏洞? 文件上传的原理 文件上传漏洞有哪些危害 文件上传漏洞如何查找及判断 文件上传如何防御 文件上传漏洞绕过的方式有哪些 ...

  9. koa --- 使用koa-multer上传文件+elementUI

    核心代码 const upload = require('koa-multer') ({dest: './public/images'}); router.post('/upload', upload ...

最新文章

  1. 批处理(cmd)的学习记录
  2. 比较python类的两个instance(对象) 是否相等
  3. Android的CheckBox(多选框)
  4. 象棋名手手机版2019最新版_天天象棋2019版下载
  5. 使用Oraclize让智能合约调用外部数据
  6. weblogic 修改控制台console访问路径 url
  7. idea导出快捷键配置
  8. 软考网络工程师教程第五版(2018年最新版)
  9. 安卓 视频直播二:推流端代码
  10. windows无法完成系统配置。若要尝试恢复配置,请重新启动计算机。出现此情况怎么处理,Win7封装Windows无法完成系统配置解决方案...
  11. vscode快速生成HTML模板
  12. 【Pytorch】model.train() 和 model.eval() 原理与用法
  13. php 获取数组四分位,如何在JavaScript(或PHP)中获得数组的中位数和四分位数/百分位数?...
  14. adb命令查看手机设备
  15. POJO与Bean的区别
  16. 看漫画学python 怎么样_看着漫画学Python是种怎样的体验?
  17. 配置exchange2010邮箱和邮件大小限制
  18. 用 C# picturebox 控件画图
  19. Python下应用opencv 背景消除或图片减法
  20. centos 6 尝鲜纪实 - PH67A/P67A 主板安装

热门文章

  1. 泛微落户武汉浪潮协同软件公司
  2. 2021年化工自动化控制仪表考试及化工自动化控制仪表证考试
  3. Android主题颜色开发
  4. 不容错过的UMPC/MID/电子书方案
  5. css实现文字描边方法
  6. 项目一 家庭记账软件
  7. 有感于美国三大搜索引擎发起向海地捐款的号召
  8. 从三尺讲台到三尺空间,在线教育的“真互动”时代来了
  9. 【WINDOWS / DOS 批处理】if命令中的比较运算符
  10. 科普: 4G太慢? 你对网速有误解!