Node.js 模块化的操作,简单明了的代码帮助你明白后端的实现和前端之前的交互,及解决跨域等问题
Node.JS 模块化
1、require 导入模块const http=require('http')2、exports 导出模块db={name:'jine',age:22}
module.exports=db
内置模块使用
1、导包const fs=require('fs');2、调用unlink删除文件方法
fs.unlink('./tmp/hello.txt',(err)=>{if(err) throw err;console.log('已成功删除 /tmmp/hello')
})
-----------------------------------------------------------------------------------------
文件读取操作,例如const fs=require('fs')fs.readFile('./etc/passwd.txt','utf-8',(err,data)=>{console.log(err);//err是一个错误对象,没有就返回一个nullconsole.log(data);
})-----------------------------------------------------------------------------------------
文件写操作(如果没有文件会创建一个,若有会覆盖掉),例如:
const fs=require('fs')
const data=`我是一段文本内容,啦啦啦啦啦啦啦啦啦啦啦~~~`
fs.writeFile('./etc/content.txt',data,(err)=>{if(err==null){console.log("文件保存成功√")}else{console.log(err)}
})
获取文件或目录的路径
console.log(__dirname)
/*获取的是当前文件的所在目录的绝对路径 */
console.log(__filename)
/*获取的是当前文件的绝对路径*/
path 模块
为了避免少些斜杠而导致路径不对,可以引入path模块const path=require('path')
const filePath=path.join(__dirname,'test','file.txt')
console.log(filePath)
/*
c:\Users\Jine\Desktop\my_code_space\jine\Daily code practice\07、03\test\file.txt
*/
http 模块
//使用内置模块http创建一个服务器//1、导入http模块
const http=require('http')//2、创建一个服务器 (返回值代表当前服务器)
const server=http.createServer((request,response)=>{//3、设置返回给用户看的内容response.setHeader('Content-Type','text/html;charset=utf-8')/*setHeader 防止中文乱码,所以要设置响应头 */response.end("你好,世界~")
})//4、开启服务器
server.listen(1024,()=>{console.log("服务器开启~")
})/*开启服务器后,可以在本地浏览器中访问 127.0.0.1:1024 或 localhost:1024*/
静态资源服务器
//导入模块
const http=require("http")
const fs=require("fs")
const path=require('path')//创建服务器
const server=http.createServer((request,response)=>{//设置返回用户内容//获取用户请求哪一个资源let userRq=request.url//获取用户读取的资源对应的路径const filePath=path.join(__dirname,'web',userRq)//读取用户请求的资源文件fs.readFile(filePath,(err,data)=>{if(err==null){response.end(data)}else{response.end("404 not found")}})})server.listen(8888,()=>{console.log("服务器开启成功√")
})
URL 模块 (get)
"get 应用(数据存在url中,相对较小,req.url可以拿到)"
"get 安全性低,一般用于请求数据/获取数据例如://导入模块
const http=require('http')
const url=require('url')//创建服务器
const server=http.createServer((request,response)=>{/*通过request.url拿到前端传递过来的参数* 导入url模块,处理接受到的字符串* 调用parse方法,(参数1:要处理的url,参数2:如果写true便返回一个对象)* 返回的对象中有query属性,其也是一个对象,这个属性里面有get传递过来的参数*/let URL=url.parse(request.url,true)// console.log(URL.query)response.end(JSON.stringify(URL.query))/*若拿到了get信息对应的数据,便可返回字符串给前端 */
})//启动服务器
server.listen(8888,()=>{console.log("服务器已开启√")
})
querystring 模块(post)
"post 应用(数据存在请求体中,相对而言较大,传递到node.js会分小块传送)"
"post 一般用于提交数据"
"测试post接口软件 https://www.postman.com/downloads/"node.js如何接收:
他也是一小块一小块的接收.
1.首先有一个容器
2.给req对象一个data事件(这个事件会执行很多次)这个事件里面就把这些小块的数据拼接起来
3.给req对象一个end事件(这个事件只会执行一次)表示数据传递完了
4.处理传递过来的数据转为对象 queryString.parse();例如://导入模块
const http=require('http')
const querystring=require('querystring')
//创建服务器
const server=http.createServer((request,response)=>{//容器let postData=""//给request对象一个data事件//事件处理程序,chunk参数是这次传递过来的一小块内容request.on('data',(chunk)=>{//将每此传过来小块的内容放到容器中postData+=chunk})//数据传递完毕执行request.on('end',()=>{//打印容器console.log(postData)//解析容器中的数据,转为对象let postObj=querystring.parse(postData)console.log(postObj)})//可以根据post传来的参数,去数据库中判断是否正确//最后返回前端页面response.end("over")
})//启动服务器
server.listen(8888,()=>{console.log("服务器已开启√")
})
第三方模块
爬虫模块
1、新建文件夹,文件夹名字非中文,且不要和模块名一样
2、在cmd打开这个文件夹,然后输入 npm init -y (初始化)
3、去npm官网,搜索crawler
4、在cmd中运行 npm i crawler(下载爬虫模块)
5、使用爬虫,如下代码爬取元素内容,例如:
var Crawler = require("crawler");var c = new Crawler({maxConnections : 10,// This will be called for each crawled pagecallback : function (error, res, done) {if(error){console.log(error);}else{var $ = res.$;// $ is Cheerio by default//a lean implementation of core jQuery designed specifically for the serverconsole.log($("title").text());}done();}
});// Queue just one URL, with default callback
c.queue('http://www.baidu.com');-----------------------------------------------------------------------------------------
爬取图片或视频,例如:
var Crawler = require("crawler");
var fs = require('fs');var c = new Crawler({encoding:null,jQuery:false,// set false to suppress warning message.callback:function(err, res, done){if(err){console.error(err.stack);}else{fs.createWriteStream(res.options.filename).write(res.body);}done();}
});c.queue({uri:"https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=false&word=%E7%BE%8E%E5%A5%B3&step_word=&hs=0&pn=7&spn=0&di=35090&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&istype=0&ie=utf-8&oe=utf-8&in=&cl=2&lm=-1&st=undefined&cs=3186997546%2C1762170182&os=86097226%2C93959096&simid=3448572432%2C288122016&adpicid=0&lpn=0&ln=3390&fr=&fmq=1593780721779_R&fm=&ic=undefined&s=undefined&hd=undefined&latest=undefined©right=undefined&se=&sme=&tab=0&width=undefined&height=undefined&face=undefined&ist=&jit=&cg=girl&bdtype=0&oriquery=&objurl=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201309%2F03%2F20130903141830_Q4Wuc.jpeg&fromurl=ippr_z2C%24qAzdH3FAzdH3F4_z%26e3B17tpwg2_z%26e3Bv54AzdH3Frj5rsjAzdH3F4ks52AzdH3F8bnmndnllAzdH3F1jpwtsAzdH3F&gsm=7&rpstart=0&rpnum=0&islist=&querylist=&force=undefined",filename:"./mv.jpg",headers:{'User-Agent':'requests'}/*让服务器伪装成客户端,这样可以破解防爬虫 */
});
自动重启工具(nodemon)
在编写调试Node.js项目,修改代码后,需要频繁的手动close掉,然后再重新启动,非常繁琐。现在,我们可以使用nodemon这个工具,它的作用是监听代码文件的变动,当代码改变之后,自动重启。例如:nodemon app.js
package.json 和 package-lock.json
package.json
默认项目初始化json文件,用来描述项目的信息1.使用npm5之前的版本,是不会生成package-lock json这个文件的。2. npm5以后,包括npm5这个版本,才会生成package-lockjson文件3.当使用npm安装包的时候,npm都会生成或者更新package-lock.json文件。npm5以后的版本,在安装包的时候,不需要加--save (-S) 参数, 也会自动在package.json中保存依赖项(依赖包)。当安装包的时候,会自动创建或者更新package-lock json文件。package-lock.json文件内保存了node_ _modules中所有包的信息,包含这些包的名称、版本号、下载地址。带来好处是,如果重新npm install的时候,就无需逐个分析包的依赖项,因此会大大加快安装速度。从package-lock.json文件名来看,lock代表的是“锁定”的意思。它用来锁定当前开发使用的版本号,防止npm instal的时候自动更新到了更新版本。因为新版本有可能会更新老的api,导致之前的代码出错。原来的package.json文件只能锁定大版本,也就是版本号的第一位,并不能锁定后面的小版本,你每次npm install
都是拉取的该大版本下的最新的版本,为了稳定性考虑我们几乎是不敢随意升级依赖包的,这将导致多出来很多工
作量,测试/适配等,所以package-lock.jison文件出来了,当你每次安装一个依赖的时候就锁定在你安装的这个版本
express 模块
1、安装前,先清空缓存,否则会报错。 (npm cache clean -f)
2、安装(npm i express)使用如下,例如://导入模块
const express = require('express')//调用express()方法创建服务器
const app = express()//设置返回用户看的内容
app.get('/', function (req, res) {//若用内置模块http创建的服务器,返回内容用res.end() 响应
//此express模块创建的服务器,用res.send() 响应
//使用express模块返回中文时不用给响应头中加中文转义,默认会转义res.send(`<h1>Hello World~</h1><p>你好呀</p>`)
})//开启服务器
app.listen(8888,()=>{console.log("服务器开启成功√")
})
创建静态资源服务器
访问express中文网的静态文件的创建(https://www.expressjs.com.cn/starter/static-files.html)例如://导入模块
const express = require('express')//调用express()方法创建服务器
const app = express()//通过如下代码,便可将web目录下的图片、css文件、JavaScript文件对外开放访问
app.use(express.static('web'))//开启服务器
app.listen(8888,()=>{console.log("服务器开启成功√")
})
实现简单的get 接口
/*** 接口:得到一条随机笑话* 接口地址:/joke* 请求方式:get * 参数:无* 返回:一条笑话*///导包const express = require('express')//创建服务器const app = express()//写接口app.get('/joke',(req,res)=>{//实际开发是从数据库或其他数据源获取let arr=['世界是你的','程序员会找到女朋友','你的代码永不报错']let index=Math.floor(Math.random()*3); //0,1,2//返回笑话res.send(`<h1>${arr[index]}</h1>`)})//开启服务器app.listen(8888,()=>{console.log("服务器已开启√")})
实现带有参数的get接口
/*** 接口:查询英雄对应的外号* 接口地址:/name* 请求方式:get * 参数:heroName* 返回:英雄外号*///导包const express = require('express')//创建服务器const app = express()//写接口app.get('/name',(req,res)=>{//要接受前端,传递过来的参数let hero=''//req.query返回的是一个对象//herName便是get请求的参数switch(req.query.heroName){case '提莫':hero='迅捷斥候'break;case '李青':hero="盲僧"break;case '阿狸':hero="九尾妖狐"break;case '猴子':hero="齐天大圣"break; default:hero="该英雄查询不到"; break; }res.send(`<h1 style=color:red;>${hero}</h1>`) })//开启服务器app.listen(8888,()=>{console.log("服务器已开启√")})
实现返回json数据的接口
/*** 接口:返回一个食物* 接口地址:/food* 请求方式:get* 请求参数:无* 返回值:json*///导包
const express = require('express')//创建服务器
const app = express()//写接口
app.get('/food',(req,res)=>{//1、express模块写法:直接写成对象,这样会自动返回json格式res.send({foodName:'宫保鸡丁',price:22,description:'肉大,菜香,多吃不腻'})// //2、原生内置模块写法:使用内置模块setHeader设置
// res.setHeader('Conten-Type','application/json');
// res.send(`
// foodName:'宫保鸡丁',
// price:22,
// description:'肉大,菜香,多吃不腻'
// `)
})//开启服务器
app.listen(8888,()=>{console.log("服务器已开启√")
})
实现简单的post 接口
/*** 接口:返回默认字符串* 接口地址:/str* 请求方式:post* 参数:无* 返回:这是一个post接口*///导包const express = require('express')//创建服务器const app = express()//写接口app.post('/str',(req,res)=>{res.send(`<h1>这是一个post接口</h1>`)})//开启服务器app.listen(8888,()=>{console.log("服务器已开启√")})
实现带有参数的post接口
导入第三方模块(npm i body-parser)/*** 接口:用户登录* 接口地址:/login* 请求方式:post* 参数:username password* 返回:登录成功/登录失败*///需要使用第三方模块body-parser获取post传递的过来的参数
//安装模块(npm i body-parser)//导包const express = require('express')const bodyParser=require('body-parser')//创建服务器const app = express()//parse application/ x-www-form-urlencoded//将post请求体中的格式转换为urlencoded格式app.use(bodyParser.urlencoded({extended:false}))//写接口app.post('/login',(req,res)=>{//实际开发根据数据库中的数据进行判断//req.body 返回的是post对象,username和password是自定义的参数if(req.body.username=='admin' && req.body.password=='88888888'){res.send({code:200,msg:"登录成功"})}else{res.send({code:400,msg:'账户密码不正确'})}})//开启服务器app.listen(8888,()=>{console.log("服务器已开启√")})
实现post方式传文本参数的接口
导入第三方模块(npm i multer)/*** 接口:用户登录* 接口地址:/register* 请求方式:post* 参数:username password usericon(用户头像/图片文件)* 返回:注册成功/注册失败*///需要使用第三方模块multer接受post传递过来的文本参数
//安装模块(npm i multerr)//导包const express = require('express')const multer=require('multer')//用包:创建一个uploads文件夹,接受传递来的文本let upload=multer({dest:'uploads/'})//创建服务器const app = express()//写接口app.post('/register',upload.single('usericon'),(req,res,next)=>{// req.file is the usericon file //传过来的文件参数名用usericon// req. body will hold the text fields, if there were any//一起传过来的文件保存在req.body中//记录了传递过来的文件的一些信息console.log(req.file)//记录了传递过来的post对象和其中的参数console.log(req.body)})//开启服务器app.listen(8888,()=>{console.log("服务器已开启√")})
注册路由
后端路由
注册路由:说白了就是写接口(API)将多个接口放入一个文件中,只开启一次服务器例如://后端路由
//注册路由:说白了就是写接口(API)//导包
const express = require('express')//创建服务器
const app = express()//注册路由//1、注册接口
app.post('/register',(req,res)=>{//逻辑...res.send("注册成功")
})//2、登录接口
app.post('/login',(req,res)=>{//逻辑...res.send("登录成功")
})//3、获取所有英雄接口
app.get('/getAllHero',(req,res)=>{//逻辑...res.send("获取成功")
})
自定义模块
自定义模块(文件名为:'自定义模块.js'),如下:let db={name:'jine',age:'22',fun(){console.log("我是一个方法")},funny(){console.log('我也是一个方法~~')}
}//导出,提供接口
module.exports=db-----------------------------------------------------
这样便可以使用我们自定义的模块如下://导包
const myMoudle=require('./自定义模块.js')console.log(myMoudle.name)
console.log(myMoudle.age)
myMoudle.fun()
myMoudle.funny()
其他补充
服务器重定向
服务器主动修改浏览器地址栏//导包
const express=require("express")//创建服务器
const app=express()//如果访问本服务器中找不到输入的这个页面,就会自动重定向跳转到指定页面
app.use((req,res)=>{//设置302响应头//结束响应res.writeHead(302,{Location:'https://blog.3xnb.com'});res.end("ok")
})//启动服务器
app.listen(8888,()=>{console.log("服务器已开启√")
})
中间件
服务器开启之后和路由器响应之前执行的一个函数
这个函数可以操作req,res
next() 执行下一个中间件例如://导包
const express=require("express")
const bodyParser=require("body-parser")//创建服务器
const app=express()//中间件
//服务器开启之后和路由器响应之前执行的一个函数
//这个函数可以操作req,res
//next() 执行下一个中间件
app.use((req,res,next)=>{console.log("LOGGED")next()
})//API
app.get('/hello',(req,res)=>{res.end("Hello World~")
})//启动服务器
app.listen(8888,()=>{console.log("服务器已开启√")
})
跨域
报错类型:'Access-Contril-Allow-Origin'-----------------------------------------------------------------------------------------
什么是跨域?浏览器使用ajax时,如果请求的接口地址和当前打开页面的地址不同源称之为跨域同源:协议,地址,端口都相同(反之有一个不满足就为不同源)-----------------------------------------------------------------------------------------
出于安全考虑,浏览器不允许,页面向不同源的接口请求数据,因为如果接口和网页不同源,浏览器默认认为是俩个不同的服务器。
解决跨域
跨域是前端工作中不可避免的问题:我们经常会出现请求不同源接口的情况,为了能够获取数据,解决跨域的问题方案也有很多,但是常用的就两种■第一种: CORS(后端操作)
目前的主流方案,也是最简单的方案,直接让后端设置响应头,允许资源共享就ok.跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。例如:app.get('/login',(req,res)=>{//设置响应头,允许资源被访问/共享res.setHeader('Access-Control-Allow-Origin','*')/*表示所有请求路径都可以请求这个接口*/
})解决跨域问题,设置响应头的代码,可以在中间件中写,这样可以在所有接口中都会应用到(只在中间件写一次就可以了,不用再多个接口中去写,因为中间件是在服务器开启之后和路由响应之前执行的一个函数)例如:app.use((req,res,next)=>{//在中间件中设置响应头,允许资源被所有接口访问/共享res.setHeader('Access-Control-Allow-Origin','*')next()
})-----------------------------------------------------------------------------------------或者还可以安装第三方模块(npm i cors)例如://导入包
const cors=require('cors')
//使用
app.use(cors());
/*直接使用这个模块,便可省略在中间件中写的解决跨域问题的代码*/
■第二种:JSONP(前后端配合)
曾经的跨域杀手,专治各种跨域问题。现在慢慢的淡出历史舞台
PS:面试官特别喜欢问这个,因为这个有一定的技术难度,也能体现一个人的实际开发经验
jsonp是前后端来配合使用的.
使用原理:通过动态创建script标签通过script标签的src请求没有跨域限制来获取资源补充知识:
浏览器页面上使用ajax发请求,当前页面地址和ajax请求的地址不同源,才会有跨域限制
但script、img、link标签中的src属性发的请求都没有跨域限制使用jsonp原理:
这样便可以用script标签中的src来操作,去访问后端地址的接口时,前后端规定好使用的get请求传入的参数名便可,这样也没有了跨域限制-----------------------------------------------------------------------------------------
前端页面,例如:<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>JSONP</title>
</head>
<body><script>function print(obj){alert(obj.user)alert(obj.data)}</script><!-- 前端页面:访问后端API,传入后端指定get参数,后端处理返回后,接受到返回的调用和实参对象来使用 --><script src="http://127.0.0.1:8888/getAll?fun=print"></script>
</body>
</html>-----------------------------------------------------------------------------------------
后端页面,例如://导包
const express=require("express")//创建服务器
const app=express()app.get('/getAll',(req,res)=>{//接受到前端传入的get参数,当作函数调用这个参数,并传入实参对象,最后返回前端res.send(`${req.query.fun}({"user":"我是jine","data":"成功调用√"})`)
})//启用服务器
app.listen(8888,()=>{console.log("服务器成功启动√")
})-----------------------------------------------------------------------------------------或者还可以直接在Ajax请求时,加上 dataType:'jsonp'例如:<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>点击哟</title><script src="https://jine.oss-cn-beijing.aliyuncs.com/API/jquery-3.5.0.js"></script><script>$(()=>{let $btn= $('button')$btn.on("click",()=>{$.ajax({url:'http://127.0.0.1:8888/getAll',dataType:'jsonp',success:(backData)=>{alert(backData.user)alert(backData.data)}})// 如果访问的接口支持jsonp,那么发送ajax便可以使用jsonp// ajax加 dataType:'jsonp' 便可使用// 加的原理是:自动创建一个script标签,用他的src属性去请求服务器//注意:后端中的参数必须是callback,因为使用ajax开启jsonp,src传递的参数是callback,所以要一致})})</script>
</head>
<body><button>点击我哟~</button>
</body>
</html>"注意:后端中的参数必须是callback,因为使用ajax开启jsonp,src传递的参数是callback,所以要一致"
聚合数据(API)
百度搜索聚合数据,进入官方
便可调用别人提供已经写好的API
只要使用的接口支持jsonp,那么可以用ajax去请求
Node.js 模块化的操作,简单明了的代码帮助你明白后端的实现和前端之前的交互,及解决跨域等问题相关推荐
- Node.js模块化开发(非常详细,满满的干货)
下面是对Node.js模块化开发的整理,西洼港可以帮助到有需要的小伙伴~ 文章目录 Node.js模块化开发 JavaScript开发弊端 Node.js模块化开发 模块成员导出的export方法 模 ...
- Node.js模块化开发
Node.js模块化开发 一.Node.js模块化开发 二.系统模块 1.什么是系统模块 2.系统模块fs文件操作 3.系统模块path路径操作 三.第三方模块 1.什么是第三方模块 2.获取第三方模 ...
- 模块加载及第三方包:Node.js模块化开发、系统模块、第三方模块、package.json文件、Node.js中模块的加载机制、开发环境与生产环境、cookie与session
1.Node.js模块化开发 1.1 JavaScript开发弊端 JavaScript 在使用时存在两大问题,文件依赖和命名冲突. 1.2 软件中的模块化开发 一个功能就是一个模块,多个模块可以组成 ...
- [Node.js] 模块化 -- path路径模块
路径问题 nodejs中相对路径,相对的是运行这个node文件的小黑框的路径而言的. 相关变量 __dirname 获取的是当前这个文件所在的这个文件夹的绝对路径 __filename 拿到的是当前这 ...
- Node.js基础汇总(一):什么是Node.js,创建最简单的Node.js应用,NPM介绍,安装express模块
目录 1. 什么是Node.js? 1.1 JavaScript是什么? 1.2 Node.js 是什么? 1.3 Node.js的原理 2. 创建最简单的Node.js应用 2.1 代码示例 2.2 ...
- Node.js SQL数据库操作 (上)(操作MySQL数据库及 数据库连接池)
文章目录 Node.js MySQL驱动 操作 MySQL 数据库 连接 MySQL 数据库 增删改查操作 防止 SQL 注入攻击 数据库连接池操作 Node.js MySQL驱动 Node.js的原 ...
- [Node.js] 模块化 -- 中间件和跨域
IP地址和域名 中间件 什么是中间件 是服务器开启之后和路由响应之前执行的一个函数 编写用于Express应用程序的中间件 中间件功能执行的任务 执行任何代码. 对请求和响应对象进行更改. 结束请求- ...
- node中怎样将css导入到html,CSS无法使用Node.js加载到我的HTML代码中
我想通过Node.js使用express()函数在localhost:3000中将CSS添加到我的HTML中. 不幸的是,有些奇怪.我一步一步按照教程中的步骤,但仍然无法加载我的CSS.我的style ...
- 【node.js从入门到精通】编写接口,使用CROS解决跨域问题,jsonp的接口
目录 编辑 前言 1. get接口 2.post接口 2.跨域问题 1.CROS是什么 2.使用CROS解决跨域问题 3.CORS请求的分类 4.编写jsonp的接口 写在最后 前言 接口是前后端的 ...
最新文章
- chrome浏览器本地文件支持ajax请求的解决方法
- 数据结构源码笔记(C语言):二叉排序树的基本操作算法
- 数据中心安全的六条黄金规则
- JavaScript变量和对象参数传值问题
- SGU 294 He's Circles (polay计数)
- Java死锁故障排除和解决
- [html] 写一个垂直的三栏布局,第一栏固定顶部,中间铺满,第三栏固定底部
- Linux 建立文件夹的链接
- 两次秒售罄的小米10,还能火爆多久?
- Faster RCNN 网络分析及维度分析
- C51自动贪吃蛇程序
- AAAI2020-一种生成对抗网络的蒸馏方法(Distilling portable Generative Adversarial Networks for Image Translation)
- 支持向量回归(Support Vector Regression)
- CorelDRAW常用工具之橡皮擦工具
- 高德地图—js.api
- 解决:【安全警报】该站点安全证书的吊销信息不可用,是否继续?
- 活动预告|CoodeWisdom 软件智能化开发与运维学术报告系列 第4期(陈鹏飞 中山大学)...
- java highchart统计图_java+highchart实现分类下钻柱形图
- 数据库如何备份与恢复
- 京瓷1025打印机打印有底灰简单处理