Nodejs学习---总结篇
server端和前端的区别
nodejs和JavaScript的区别
node引入/导出语法
http模块
fs模块
path模块
url模块
querystring模块
nodejs处理http请求
express
express-router
express中间件
redis存储session
nginx反向代理
morgan日志
安全
server端和前端的区别
- 服务的稳定性
- 需要考虑内存和cpu的优化
- 客户端独占一个浏览器,内存和cpu都不是问题
- server端承载很多请求,内存和cpu都是稀缺资源
- stream写日志是比较节省内存的(优化),redis存储session(扩展)
- 日志记录
- server端要记录日志,存储日志,分析日志,前端不用关心
- 安全
- server端要随时准备接受各种恶意攻击:越权操作,数据库攻击
- 预防xss攻击和sql注入
- 集群和服务拆分
- 产品发展速度快,承载流量大
nodejs和JavaScript的区别
- javascript
- 使用ESMAScript语法规范,外加web API
- web API : BOM DOM,事件绑定,ajax等
- nodejs
- 使用ESMAScript语法规范,外加nodejs API
- nodejs API:处理http请求,处理文件等
node引入/导出语法
- 引入模块
require('模块名称')
- 带路径的: 一般我们自己封装的模块
require('../xxx/yyy')
,js文件后缀名可以省略不写。 路径还可以写到文件夹的那层,但文件夹下一定要有一个index.js
系统会默认找index.js
require('../xxx/文件夹名称') === > require('../xxx/文件夹名称/index.js')
- 不带路径的:
require('koa')
其实就是找node_modules
文件夹 系统node_modules
- 带路径的: 一般我们自己封装的模块
- 导出模块语法
module.exports /exports
- 每一个Nodejs的执行文件都会自动地创建一个
module
对象,同时module.exports
会创建一个叫exports
的属性,初始值为空对象{}
。exports
和module.exports
指向同一个内存,但require()
返回的是module.exports
而不是exports
module.exports
可以直接等于一个对象 但exports
只能给其赋值新属性 不能直接等于一个新对象
- 每一个Nodejs的执行文件都会自动地创建一个
//module.exports的形式
module.exports = {str,arr,targetObj
}
//exports的形式
exports.str = str;
exports.arr = arr;
exports.targetObj = targetObj;
http模块
nodejs
的运用基础是起码要创建一个服务,在该服务的基础上处理业务操作,对于创建一个服务器最原始的莫过于是使用http
模块。
http模块主要用于创建http server服务,其中封装了高效的http服务器和http客户端
http.server是一个基于事件的HTTP服务器,内部是由c++实现的,接口由JavaScript封装
http.request是一个HTTP客户端工具。用户向服务器发送数据。
var http = require('http');
var server = http.createServer((req,res)=>{//解决跨域问题res.setHeader('Access-Control-Allow-Origin',"*")res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");//设置 HTTP 头部,状态码是 200,文件类型是 html,字符集是 utf-8res.writeHead(200,{"Content-Type":"text/html;charset='utf-8'"});res.write('aaa');/*如果没有res.end(),服务器会一直等待,运转到一定的时间,页面会返回一个错误。提示服务器运行时间超长。*/res.end(); // 结束下你的请求
});
server.listen(8080)
var http = require('http');
引入模块http.createServer(callback)
request:获取客户端发送的请求数据的信息(请求的地址,请求的方式等),response:返给客户端的相应数据server.listen(post,host,callback)
host可以不写 默认为localhost/127.0.0.1
fs模块
异步和同步
Node.js 文件系统(fs 模块
)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile()
和同步的 fs.readFileSync()。
异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。
建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。
// 属于异步操作 不会耽误其他的执行 fs.writeFile(path,data,callback);fs.readFile(path,callback);
// 同步形式也有 很少用 太慢了fs.writeFileSync();fs.readFileSync();
var fs = require('fs');
//异步获取
fs.readFile('input.txt',(err,data)=>{if(err){return console.error(err)}// data返回的是原始的二进制数据(Buffer) 浏览器是可以解析的 自己看的时候需要toString()console.log('异步读取:'+data.toString());
})
var fs = require('fs');
//异步写入
fs.writeFile('./a.txt','xxasxssx',(err)=>{if(err){console.log('写入失败',err);}else{console.log('写入成功',err);}
});
var fs = require('fs');
//同步获取
var data = fs.readFileSync('input.txt');
console.log("同步读取: " + data.toString());
var fs = require('fs');
//同步写入
var data = fs.writeFileSync('input.txt');
console.log("同步读取: " + data.toString());
fs.writeFile(path,data,callback)
; //写入文件 路径,要写入的数据,回调函数 (接受一个err参数 )
写入一旦成功 即使没有这个文件 也会在目录中创建这个文件 并把数据写入到文件中 回调中的 err 返回为 null
写入失败 回调中的err会返回一个错误对象 。
fs.readFile(path,callback)
; // 用来读文件 不需要data 路径,回调函数(接受err,data参数 data是读取到的数据)
data返回的是原始的二进制数据(Buffer) 浏览器是可以解析的 只是你自己看的时候看不懂。通常我们可以data.toString()转为字符串来查看数据。
path模块
path
模块提供用于处理文件路径和目录路径的实用工具。
const path = require('path');let str = '/root/a/b/1.txt';//path.dirname(str) 获取目录名称 /root/a/b
console.log(path.dirname(str)); //path.extname(str) 获取扩展名 .txt
console.log(path.extname(str));//path.basename(str) 获取文件名 1.txt
console.log(path.basename(str));//path.resolve(str) 对路径的解析 类似于命令行的操纵 \root\a\b\c\d\e
console.log(path.resolve('/root/a/b','c','d','e'));
// 常用的是解析当前的绝对路径
console.log(path.resolve(__dirname,'build'));
url模块
url 模块用于处理与解析 URL。
url.parse(url)
这个方法可以将一个url的字符串解析并返回一个url的对象
const url = require('url');url.parse("http://user:pass@host.com:8080/p/a/t/h?query=string#hash");
/*
返回值:
{protocol: 'http:',slashes: true,auth: 'user:pass',host: 'host.com:8080',port: '8080',hostname: 'host.com',hash: '#hash',search: '?query=string',query: 'query=string',pathname: '/p/a/t/h',path: '/p/a/t/h?query=string',href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'}
没有设置第二个参数为true时,query属性为一个字符串类型
*/
url.parse(url,true);
url.parse("http://user:pass@host.com:8080/p/a/t/h?query=string#hash",true);
/*
返回值:{protocol: 'http:',slashes: true,auth: 'user:pass',host: 'host.com:8080',port: '8080',hostname: 'host.com',hash: '#hash',search: '?query=string',query: { query: 'string' }, // 第二个参数 为true时 pathname: '/p/a/t/h',path: '/p/a/t/h?query=string',href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'}
返回的url对象中,query属性为一个对象
*/
url.format(urlObj)
这个方法是将传入的url对象编程一个url字符串并返回。
url.format({protocol:"http:",host:"182.163.0:60",port:"60"
});
/*
返回值:
'http://182.163.0:60'
*/
url.resolve(from,to)
resolve这个方法返回一个格式为"from/to"的字符串
url.resolve("http://whitemu.com","gulu");
/*
返回值:
'http://whitemu.com/gulu'
*/
querystring模块
从字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析。
querystring.parse(str)
将字符串解析为一个对象。
querystring.stringify(strObj)
将字符串对象解析为一个字符串。
const querystring = require('querystring');console.log(querystring.parse('a=12&b=11&c=11')); //{ a: '12', b: '11', c: '11' }
console.log(querystring.stringify({ a: '12', b: '11', c: '11' })) //a=12&b=11&c=11
querystring.escape(str)
escape可使传入的字符串进行编码
querystring.escape("name=慕白");
/*
return:
'name%3D%E6%85%95%E7%99%BD'
*/
querystring.unescape(str)
unescape方法可将含有%的字符串进行解码
querystring.unescape('name%3D%E6%85%95%E7%99%BD');
/*
return:
'name=慕白'
*/
nodejs处理http请求
http请求概述
- DNS解析,建立TCP连接,发送http请求。
- server接收到http请求,处理,并返回。
- 客户端接收到返回数据,处理数据(如渲染页面,js)
nodejs处理http请求
- get请求和
querystring
- post请求和
postdata
- 路由
nodejs处理get请求
- get请求,即客户端要向server端获取数据
- 通过
querystring
来传递数据,如a.html?a=100&b=200 - 浏览器直接访问,就发送get请求。
const http = require('http');
const querystring = require('querystring');const server = http.createServer((req,res)=>{console.log('method',req.method); //getconst url = req.url;console.log('url',url);req.query = querystring.parse(url.split('?')[1]);console.log('query',req.query);res.end(JSON.stringify(req.query));
})server.listen(8080,()=>{console.log(`server is running `)
});
打印如下:
method GET
url /api/list?author=111
query [Object: null prototype] { author: '111' }
method GET
url /favicon.ico
query [Object: null prototype] {}
nodejs处理post请求
- post请求,即客户端要向server端传递数据
- 通过
postdata
来传递数据 - 浏览器无法直接模拟,需要手写js,或者用postman
const http = require('http');const server = http.createServer((req,res)=>{if(req.method == 'POST'){console.log('req content-type',req.headers['content-type']);let postData = '';req.on('data',chunk =>{postData+=chunk.toString();})req.on('end',() =>{console.log('postData:',postData);console.log('postData:',typeof postData); //stringreq.body = JSON.parse(postData); //将post获取到的参数 放到 req.body上res.end('hello world')})}
});server.listen(8000,()=>{console.log('ok')
})
express
安装脚手架
npm i express-generator -g
创建项目
express blog_express
cd blog-express
npm i
npm start
配置package.json
npm i nodemon cross-env -D
"scripts": {"start": "node ./bin/www", "dev":"cross-env NODE_ENV=development nodemon ./bin/www"},
介绍app.js
var createError = require('http-errors'); 对错误页面的处理
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser'); 对cookie的解析 通过req.cookies就可以直接访问cookie
var logger = require('morgan'); 记录日志var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');var app = express();// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');app.use(logger('dev')); 使用logger日志
app.use(express.json()); 可以获取到post json的数据 req.body获取
app.use(express.urlencoded({ extended: false })); 获取post除了json之外格式的数据 都是绑定到req.body上
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter);
app.use('/users', usersRouter);// catch 404 and forward to error handler
app.use(function(req, res, next) {next(createError(404));
});// error handler
app.use(function(err, req, res, next) {// set locals, only providing error in developmentres.locals.message = err.message;res.locals.error = req.app.get('env') === 'development' ? err : {};// render the error pageres.status(err.status || 500);res.render('error');
});module.exports = app;
res相关
res.send() (*****) // 万能方法 可以响应JSON 字符串 HTML script
res.json() // 主要给前端响应 JSON 数据
res.jsonp() // 主要给前端响应 JSON 数据(针对跨域请求)
数据和模板在后端合并渲染 生成HTML 返回给前端
res.render('ejs模板文件', {JSON对象格式的数据})
res.download('要下载的文件的路径', '标题')
res.redirect("要跳转到的新的网址url")
res.status(状态码404).render("ejs模板名字", {JSON对象格式的数据})
req相关
接收 get 方式的请求的参数,req.query.参数的key值
接收 post 方式的请求的参数,req.body.参数的key值req.ip 获取浏览器的ip地址
express-router
//app.js
var blogRouter= require('./routes/blog.js');
app.use('/api/blog',blogRouter);
//routes---->blog.js
var express = require('express');
var router = express.Router();router.get('/list', function (req, res, next) {const { username, password } = req.query;res.json({errno: 0,data: {username,password}})
})
router.post('/detail', function (req, res, next) {const { username, password } = req.body;res.json({errno: 0,data: {username,password}})
})module.exports = router;
post
请求的数据在req.body
上get
请求的数据在req.query
上
express-中间件
在 Node.js 中被广泛使用,它泛指一种特定的设计模式、一系列的处理单元、过滤器和处理程序,以函数的形式存在,连接在一起,形成一个异步队列,来完成对任何数据的预处理和后处理。
其实中间件就是请求req
和响应res
之间的一个应用,请求浏览器向服务器发送一个请求后,服务器直接通过request
定位属性的方式得到通过request
携带过去的数据,就是用户输入的数据和浏览器本身的数据信息,这中间就一定有一个函数将这些数据分类做了处理,最后让request
对象调用使用,这个或者多个的处理函数就是我们所说得中间件。
原理分析
- app.use用来注册中间件,先收集起来。
- 遇到http请求,根据path和method判断触发那些。
- 实现next机制,即上一个通过next触发下一个
实现
- 创建存放所有路由的对象
(all,get,post.。。。)
,后期对不同的请求处理都会放到这里 - 声明对应处理的函数
use(),get(),post()
,register
函数统一处理path
信息,返回{path:'xxx',stack:['每个path对应中间件函数数组']}
use(),get(),post()
,将自己的info
信息存储到this.routes
中listen
监听端口callback
是createServer
回调,获取当前url
的路由和method
match
返回当前url
所需要执行的所有中间件集合
handle
核心方法 实现next()
调用各个中间件
const http = require('http');
const slice = Array.prototype.slice;class LikeExpress{constructor(){// 静态属性this.routes = {all:[],get:[],post:[]}}// 原型上定义的函数//中间件公用的地方 定义在register里面register(path){const info = {};if(typeof path === 'string'){//如果第一个参数是路由的话info.path = path;// stack 以数组的形式存放路由后面的中间件info.stack = slice.call(arguments,1);}else{// 如果是app.use((req,res,next)=>{}) 没有第一个参数的形式info.path = '/';info.stack = slice.call(arguments,0);}console.log(`info is ${info}`);return info;}use(){const info = this.register.apply(this,arguments);this.routes.all.push(info);}get(){const info = this.register.apply(this,arguments);this.routes.get.push(info);}post(){const info = this.register.apply(this,arguments);this.routes.post.push(info);}// 监听事件listen(...args){const server = http.createServer(this.callback());server.listen(...args);}// 定义server的回调函数callback(){return (req,res)=>{// 定义res.json的使用res.json = (data)=>{res.setHeader('Content-type','application/json');res.end(JSON.stringify(data))}const url = req.url;const method = req.method.toLowerCase();const resultList = this.match(method,url);this.handle(req, res, resultList)}}// match 当前url所需要执行的所以中间件集合match(method,url){let stack = [];if(url === '/favicon.ico'){return stack;}// 获取routeslet curRoutes =[];// 当前路由匹配到的中间件:所有的 all 以及当前url的method对应所有中间件curRoutes = curRoutes.concat(this.routes.all);curRoutes = curRoutes.concat(this.routes[method]);curRoutes.forEach(routeInfo =>{if(url.indexOf(routeInfo.path) === 0){// url === '/api/get-cookie' 且 routeInfo.path === '/'// url === '/api/get-cookie' 且 routeInfo.path === '/api'// url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie'// 当前路由在curRoutes 中匹配对应自己的所有中间件stack = stack.concat(routeInfo.stack)}})return stack}// 核心的 next机制handle(req,res,stack){const next = ()=>{const middleware = stack.shift();if(middleware){middleware(req, res, next);}}next();}
}module.exports = () => {return new LikeExpress()
}
调用
const express = require('./like-express2');const app = express();app.use((req,res,next)=>{console.log('请求开始....',req.method,req.url);next();
})app.use((req,res,next)=>{console.log('处理 cookie');req.cookie = {userId:'abc123'}next();
})app.use('/api',(req,res,next)=>{console.log('处理 /api 路由');next();
})app.get('/api',(req,res,next)=>{console.log('get 处理 /api 路由');next();
})// 模拟登录验证function loginCheck(req,res,next){setTimeout(() => {console.log('模拟登录成功'); next();});
}app.get('/api/get-cookie',loginCheck,(req,res,next)=>{console.log('get 处理 /api/get-cookie 路由');res.json({errno:0,data:req.cookie})
})app.listen(8000,()=>{console.log(`server listen running http://localhost:8000`);
})//访问 http://localhost:8000/api/get-cookie//请求开始.... GET /api/get-cookie
//处理 cookie
//处理 /api 路由
//get 处理 /api 路由
//模拟登录成功
//get 处理 /api/get-cookie 路由
redis存储session
为什么session适合用redis?
- session访问频繁,对性能要求极高
- session不用担心断电丢失数据的问题
- session数据量不会很大(相比于mysql中存储的数据)
为什么网站数据不适合raduis
- 操作频率不是太高(相比于session操作)
- 断电不能丢失,必须保留
- 数据量太大,内存成本太高
redis默认端口和地址
REDIS_CONF = {port:6379,host:'127.0.0.1'}
demo
const redis = require('redis');// 创建客户端const redisClient = redis.createClient(6379,'127.0.0.1');redisClient.on('error',err =>{console.log('err',err);})// 测试
redisClient.set('myname','zhangsan2',redis.print);
redisClient.get('myname',(err,val) => {if(err){console.log('err2',err);return; }console.log('val is',val);// 退出redisClient.quit();
});
封装demo
const redis = require('redis');
const { REDIS_CONF } = require('../conf/db');// 创建客户端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host);redisClient.on('error', err => {console.log('err', err);
})function set(key, val) {if (typeof val == 'object') {val = JSON.stringify(val);}redisClient.set(key, val, redis.print);
}
function get(key) {const promise = new Promise((resolve, reject) => {return redisClient.get(key, (err, val) => {if (err) {reject(err)return;}// resolve(val)if(val == null){resolve(null)}try{resolve(JSON.parse(val))}catch(ex){resolve(val)}// 退出// redisClient.quit();});})return promise
}
module.exports = {set,get}
nginx反向代理
nginx的配置
C:\nginx\conf --> nginx.conf
server {listen 8080;server_name localhost;#charset koi8-r;#access_log logs/host.access.log main;# location / { #root html;#index index.html index.htm;#}location /api/ {// nodejs 接口访问地址proxy_pass http://localhost:8000; proxy_set_header Host $host; }location / { //项目访问地址proxy_pass http://localhost:5500; }
proxy_pass
设置代理地址
proxy_set_header
设置头部信息
网站输入localhost:8080
—代理到localhost:5500
,当访问接口/api
的时候 请求的是 http://localhost:8000;
上的接口.
morgan日志
' dev 开发模式 combined线上模式 'const ENV = process.env.NODE_ENV
if(ENV !== 'production'){// 开发环境app.use(logger('dev',{stream:process.stdout //默认值 }));
}else{// 线上环境const logFileName = path.join(__dirname,'logs','access.log');const writeStream = fs.createWriteStream(logFileName,{flags:'a'})app.use(logger('combined',{stream:writeStream}));
}
安全
防止sql注入
主要通过mysql.escape
,对代码中特殊字符等进行转义处理。
const mysql = require('mysql');
const escape = mysql.escape;const login = (username,password)=>{username = escape(username); 防止sql注入,sql语句中不需要加单引号了password = escape(password);const sql = `select username,realname from users where username=${username} and password=${password}`return exec(sql).then((data)=>{return data[0] || {}})
}
防止xss攻击(脚本攻击)
require('xss')
,对<>标签等进行转义;比如<转为<,这样就不会形成闭合的标签了
const xss = require('xss');const newBlog = (blogData = {}) => {const title = xss(blogData.title);;
密码加密
密码加密后即使窃取到了用户信息,密码也是加密处理的同样无法登录,我这里是通过require('crypto')
;来对密码进行加密处理。
const crypto = require('crypto');// 密匙const SECRET_KEY = 'wjas_1234#';// md5加密
function md5(content){let md5 = crypto.createHash('md5');return md5.update(content).digest('hex'); // digest('hex') 是转为16进制
}// 加密函数
function genPassword(password){const str = `password=${password}&key=${SECRET_KEY}`return md5(str)
}module.exports = { genPassword
} /const {genPassword} = require('../utils/cryp');const login = (username,password)=>{username = escape(username);password = genPassword(password);加密处理password = escape(password);const sql = `select username,realname from users where username=${username} and password=${password}`return exec(sql).then((data)=>{return data[0] || {}})
}
Nodejs学习---总结篇相关推荐
- NodeJS学习笔记: RESTful —— 为本系列做个小结
前言 本人不是技术专家,该笔记只是从使用语言进行开发的层面上记录一些体会,不包含也不想尝试从源码或者更深的层次去讨论语言本身的优劣.文章内容是笔者的个人感悟,既不保证正确性,也不保证别人能看懂. 这是 ...
- Nodejs学习路线图
Nodejs学习路线图 从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发.Nodejs框架是基于V8的引擎,是目前速度最快的Javascri ...
- MongoDB学习第一篇 --- Mac下使用HomeBrew安装MongoDB
2019独角兽企业重金招聘Python工程师标准>>> MongoDB学习第一篇 --- Mac下使用HomeBrew安装MongoDB 0.确保mac已经安装了HomeBrew ( ...
- 深度学习实战篇-基于RNN的中文分词探索
深度学习实战篇-基于RNN的中文分词探索 近年来,深度学习在人工智能的多个领域取得了显著成绩.微软使用的152层深度神经网络在ImageNet的比赛上斩获多项第一,同时在图像识别中超过了人类的识别水平 ...
- JNI学习开始篇 基础知识 数据映射及学习资料收集
JNI学习开始篇 基础知识 数据映射及学习资料收集 JNI介绍 JNI(Java Native Interface) ,Java本地接口. 用Java去调用其他语言编写的程序,比如C或C++. JNI ...
- 好程序员web前端分享Nodejs学习笔记之Stream模块
好程序员web前端分享Nodejs学习笔记之Stream模块 一,开篇分析 流是一个抽象接口,被 Node 中的很多对象所实现.比如对一个 HTTP 服务器的请求是一个流,stdout 也是一个流.流 ...
- java helloworld代码_java学习应用篇|逃不掉的HelloWorld
本文知识点 1.表白不是发起进攻的冲锋号,而是吹响胜利的号角 2.除了爱情不讲道理,公理也不讲道理 3.这世界,离了javac,也是可以运行的! 4.Hello,寺水 写程序并不是写代码 看前面啰啰嗦 ...
- Mongodb学习(安装篇): 在centos下的安装
安装篇 ###下载解压文件 [root@192 lamp]# wget http://fastdl.mongodb.org/linux/mongodb-linux-i686- 2.2.2.tgz ## ...
- [Django]模型学习记录篇--基础
模型学习记录篇,仅仅自己学习时做的记录!!! 实现模型变更的三个步骤: 修改你的模型(在models.py文件中). 运行python manage.py makemigrations ,为这些修改创 ...
最新文章
- JAVA中的接口和抽象类的区别
- CSDN如何删除自己不用的分类(亲测有效!)
- mysql联合索引查找过程_(MYSQL)回表查询原理,利用联合索引实现索引覆盖
- linux+网卡驱动社区,Linux下如何确定网卡所使用的驱动程序
- Jenkins邮件配置,实现邮件发送策略(可实现每个Job对应不同的发送邮箱)
- [tarjan][树形dp] 洛谷 P2515 软件安装
- Socket编程实现简易聊天室
- E. Sign on Fence(整体二分 + 线段树维护区间最大连续 1 的个数)
- 小学生在家自学python_小学生都能学会的python(函数)
- 【转载】向量空间模型VSM及余弦计算
- 遇到了消息堆积,但是问题不大
- 将Maven项目发布到Nexus私服
- jquery中方法扩展 ($.fn $.extend) 学习笔记
- python设计模式2-工厂方法模式
- PHP错误提示的关闭方法详解
- kdchxue讲解V9父栏目调用子栏目的办法
- 牛客高级项目课(仿牛客网)笔记
- android 仿微信账单生成器手机版式,2020微信年度账单生成器
- python getsize函数,Python getsize函数
- 定点数一位乘法之Booth(布斯)算法
热门文章
- UIUC计算机的录取率,UIUC计算机专业全奖Offer申请总结
- 广东网络培训python
- 解决java.lang.IllegalArgumentException: Invalid column index (256). Allowable column range for BIFF8
- 多功能网页幻灯片jQuery Cycle
- 爬取女友淘宝已购买的宝贝数据,发现了她特殊的秘密...
- 统一内容安全技术厂商天空卫士完成1.5亿A轮融资
- Fidder Everywhere 下载和安装教程
- [源码和文档分享]基于VC++的MFC类库实现的住房贷款计算器
- 2021-第五届世界智能大会-「津门杯」国际网络安全创新大赛-Web-hate_php
- 我是色盲我怕谁(原创杂文)