• Express 4.x Node.js的Web框架


    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处。LaplaceDemon/SJQ。

    http://www.cnblogs.com/shijiaqi1066/p/3821150.html

    本文使用node.js v0.10.28 + express 4.2.0

    1 Express概述

    Express 是一个简洁而灵活的node.js的MVC Web应用框架,提供一系列强大特性创建各种Web应用。

    Express 不对 node.js 已有的特性进行二次抽象,我们只是在它之上扩展了Web应用所需的功能。

    Expressd底层由Node.js的HTTP模块实现。

    1.1 express 4.x 安装

    express 4.x与之前的版本有了许多的变化,书里和网上的很多方法都不再适用。学习需要更多的参考官方文档。

    若需要用express 3.x版本,直接使用nmp 中的@字符确定版本,指令如下:

    npm install -g express-generator@3

    若需要使用4.x,注意的问题在4.x版本express 已经把命令行工具分离出来。

    现在全局安装只需要安装这个命令行工具就可以,指令如下:

    npm install -g express-generator

    wps_clip_image-23406

    1.2 创建express工程

    使用express命令

    express [options]

    选项:

      -h, --help         输出使用信息

      -V, --version      输出版本号

      -e, --ejs          添加ejs的支持(默认使用jade)

      -H, --hogan       添加hogan.js引擎的支持

      -c, --css          添加css(less 或stylus 或compass)的支持

      -f, --force         强迫接受非空目录

    wps_clip_image-23155

    1.2.1 创建hello-express的工程

    使用命令:express hello-express ,默认创建基于jade的项目。

    wps_clip_image-32696

    使用-e参数创建基于ejs的项目。使用命令:express -e hello-express

    wps_clip_image-14881

    在当前目录下会创建新的目录。

    wps_clip_image-6317

    目录内容如下所示:

    wps_clip_image-28261

    进入到工程目录

    wps_clip_image-2331

    1.2.2 安装express及依赖

    使用命令:npm install

    wps_clip_image-21901

    该过程会自动下载网络上的依赖包,请耐心等待命令结束。创建包后,可查看目下的package.json文件:

    wps_clip_image-2537

    1.2.3 启动工程

    使用命令:npm start

    wps_clip_image-4466

    注意:该实例无法以 node app.js 为启动方式,而是用指令 npm start 作为启动。原因如下所述。

    npm执行的时候会读取当前目录的package.json文件,这个也就是我上面那个bug出现的原因

    执行npm start其实是执行package.json中的script对应的对象中的start属性所对应的命令行。

    wps_clip_image-30700

    也就是执行bin/www文件,该文件无后缀名。使用文本工具打开,可以看到其中的脚本。npm start命令,就是执行的该脚本程序。

    wps_clip_image-19564

    express默认端口为3000。浏览器访问 http://127.0.0.1:3000/ 。显示页面Welcome to Express,即安装成功。

    wps_clip_image-31123

    1.3 目录解释

    如果浏览这个子目录,就会发现express自动生成了以下的子目录和文件。

    • node_modules子目录:用于安装本地模块。
    • public子目录:用于存放用户可以下载到的文件,比如图片、脚本、样式表等。
    • routes子目录:用于存放路由文件。
    • views子目录:用于存放网页的模板。
    • app.js文件:应用程序的启动脚本。
    • package.json文件:项目的配置文件。

    当对express工程目录和package.json文件熟悉后,可以手动创建工程,而不用依赖express命令。

    1.4 解析express命令生成工程的代码

    bin/www

    #!/usr/bin/env node
    
    var debug = require('debug')('hello-express');
    var app = require('../app');    //引入app模块
    
    app.set('port', process.env.PORT || 3000);     //设置’port’
    
    var server = app.listen(app.get('port'), function() {     //获取’port’,并监听。
      debug('Express server listening on port ' + server.address().port);
    
    });

    app.js

    var express = require('express');    //引入express
    var path = require('path');         //引入path
    var favicon = require('static-favicon');    //引入static-favicon
    var logger = require('morgan');    //引入morgan
    var cookieParser = require('cookie-parser');    //引入cookie-parser
    var bodyParser = require('body-parser');    //引入body-parser
    
    var routes = require('./routes/index');    //引入路由index
    var users = require('./routes/users');    //引入路由users
    
    var app = express();             //创建一个app
    
    // 设置视图引擎。
    app.set('views', path.join(__dirname, 'views'));    //设置视图文件所在位置。
    app.set('view engine', 'ejs');                    //设置视图引擎。
    
    app.use(favicon());
    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded());
    app.use(cookieParser());
    
    app.use(express.static(path.join(__dirname, 'public')));    //指定静态文件所在位置
    
    /*
    
    请求时,服务器端就到public目录寻找这个文件。比如,浏览器发出如下的样式表请求:
    
    <link href="/bootstrap/css/bootstrap.css" rel="stylesheet">
    
    服务器端就到public/bootstrap/css/目录中寻找bootstrap.css文件。
    
    */
    
    app.use('/', routes);    //对’/’设置路由。
    app.use('/users', users);    //对’/user’设置路由。
    
    //捕获404并对跳转错误进行处理。
    app.use(function(req, res, next) {
        var err = new Error('Not Found');
        err.status = 404;
        next(err);
    });
    
    // 开发环境错误处理。
    if (app.get('env') === 'development') {
        app.use(function(err, req, res, next) {
            res.status(err.status || 500);
            res.render('error', {        //渲染error.ejs。
                message: err.message,
                error: err
            });
        });
    }
    
    // 生产环境错误处理。
    // 没有堆栈跟踪泄露给用户。
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: {}
        });
    });
    
    module.exports = app;   //将app设为模块。

    views/index.ejs

    <!DOCTYPE html>
    <html>
      <head>
        <title><%= title %></title>
        <link rel='stylesheet' href='/stylesheets/style.css' /> <!--由于已经设置了静态文件位置,服务器会自动在public目录中寻找。-->
      </head>
      <body>
        <h1><%= title %></h1>
        <p>Welcome to <%= title %></p>
      </body>
    </html>

    views/error.ejs

    <h1><%= message %></h1>
    <h2><%= error.status %></h2>
    <pre><%= error.stack %></pre>

    routes/index.js

    var express = require('express');
    var router = express.Router();    //获取路由对象。
    
    /* GET home page. */
    router.get('/', function(req, res) {
      res.render('index', { title: 'Express' });    //页面渲染index.ejs。
    });
    
    module.exports = router;    //设置为模块。

    routes/users.js

    var express = require('express');
    var router = express.Router();    //获取路由对象。
    
    /* GET users listing. */
    router.get('/', function(req, res) {
      res.send('respond with a resource');    //respones直接返回字符串。
    });
    
    module.exports = router;    //将本路由设置为模块。

    2 Express基本组件

    2.1 Express的中间件

    Express中的中间件(middleware)类似于JavaEE中拦截器的概念。

    Express中的中间件由函数表示:

    function uselessMiddleware(req, res, next) {
        next();
    }

    每个中间件都可以对HTTP请求(request对象)做出回应。调用next方法会将request对象再传给下一个中间件。next方法如果带有参数,则代表抛出一个错误,参数为错误文本。

    function uselessMiddleware(req, res, next) {
        next('出错了!');
    }

    2.2 user()方法

    user方法用于将路径与中间件函数或路由对象绑定起来。默认路径为”/”。

    app.use([path], function)

    var express = require('express');
    
    var app = express();
    
    // simple logger
    app.use(function(req, res, next){
      console.log('%s %s', req.method, req.url);
      next();
    });
    
    // respond
    app.use(function(req, res, next){
      res.send('Hello World');
    });
    
    app.listen(3000);

    2.3 静态资源

    静态资源的获取是use()函数的典型应用。express.static()方法为静态资源指定目录。use()方法用于绑定拦截http请求,若请求的静态资源存在,则返回静态资源文件。

    例:js、css、图片分别位于工程路径的public目录中。

    // GET /static/javascripts/jquery.js
    // GET /static/style.css
    // GET /static/favicon.ico
    app.use(express.static(__dirname + '/public'));

    注意:

    对不同url使用use()函数,各use()函数顺序,先拦截到http请求的中间件函数会先被执行。而且,use()方法与路由方法的顺序也需要注意。

    一般的,use()方法位于路由方法之前。

    例:当请求"GET /javascripts/jquery.js" 时,会先检查 "./public/javascripts/jquery.js",若不存在,随后的中间件会检查 "./files/javascripts/jquery.js"。

    app.use(express.static('public'));
    app.use(express.static('files'));

    2.4 异常处理

    定义错误处理的中间件跟定义普通的中间件没有什么区别,仅仅是参数必须定义为4个,定义如下 (err, req, res, next)

    app.use(function(err, req, res, next){
      console.error(err.stack);
      res.send(500, 'Something broke!');
    });

    2.5 请求路由

    2.5.1 路由方法

    app.VERB(url, callback(req, res))

    app.VERB(url, callback1(req, res),callback2(req, res),......,callbackn(req, res))

    app.VERB() 方法为Express提供路由方法,VERB 是指某一个HTTP 动作,比如,app.post()。

    app.VERB() 方法可以提供多个callbacks,多个callbacks会被平等对待,它们的行为跟中间件类似,不同的是,若某一个callback执行了next('route'),此后的callback就被忽略。这种情形会应用在当满足一个路由前缀,但是不需要处理这个路由,于是把它向后传递。

    2.5.2 Router 路由

    Express 4.x使用Router组件对URL进行响应处理。在Express 4.x 应该尽量多使用 express.Router 来代替app.VERB方式的路由。

    express.Router使得整个控制器变成了一个不依赖于任何外部实例的独立模块,更有利于模块的拆分,同时对于测试也更加友好。

    express.Router可以认为是一个微型的只用来处理中间件与控制器的app,它拥有和 app 类似的方法,例如 get、post、all、use 等等。

    app.js

    app.use(require('./controllers/book'));

    controllers/book.js

    var router = require('express').Rouer(); // 新建一个 router
    
    var A = require('../middlewares/A');
    var B = require('../middlewares/B');
    var C = require('../middlewares/C');
    
    // 在 router 上装备控制器与中间件
    router.get('/books', A, B, C, function (req, res) {
        var retA = req.A; // 中间件 A 的输出结果
        var retB = req.B; // 中间件 B 的输出结果
        var retC = req.C; // 中间件 C 的输出结果
        // ... 其余程序逻辑
    });
    
    // ...
    // 返回 router 供 app 使用
    module.exports = router;

    通过 express.Router,控制器与中间件的代码紧密的联系在一起,并且避免了传递 app 的潜在风险。同时,一个 router 就是一个完整的功能模块,不需要任何装配就可以执行。这一点对于单元测试来说非常简单。

    2.5.3 中间件重用

    express.Router 可以认为是一个迷你的 app,它拥有一个独立的中间件队列。这个特性可以用来共享一些常用的中间件,例如:

    例:parseBook方法是个中间件方法。

    var bookRouter = express.Router();
    
    app.use('/books', bookRouter);
    
    //路由指定中间件
    bookRouter.use(parseBook);
    
    //如下三个控制器都会经过 parseBook 中间件。
    bookRouter.get('/books/:bookId', viewBook);
    bookRouter.get('/books/:bookId/edit', editBook);
    bookRouter.get('/books/:bookId/move', moveBook);
    
    app.get('/other_link', otherController); // 不会经过 parseBook 中间件。

    例:Restful的Router。

    var bookRouter = express.Router();
    bookRouter
        .route('/books/:bookId?')
        .get(function (req, res) {
            // ...
        })
        .put(function (req, res) {
            // ...
        })
        .post(function (req, res) {
            // ...
        })
        .delete(function (req, res) {
            // ...
        })

    app.route

    假定app是Express的实例对象,Express 4.0为该对象提供了一个route属性。app.route实际上是express.Router()的缩写形式,直接挂载到根路径。

    app.route('/login')
        .get(function(req, res) {
            res.send('this is the login form');
        })
        .post(function(req, res) {
            console.log('processing');
            res.send('processing the login form!');
        });

    2.6 request对象

    使用req.query解析http查询字符串

    // GET /search?q=tobi+ferret
    req.query.q
    // => "tobi ferret"

    // GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse req.query.order // => "desc" req.query.shoe.color // => "blue" req.query.shoe.type // => "converse"

    使用req.params获取url中的参数

    app.get('/blog/:year/:month/:day/:title', function (req, res) {
      var fileName = './blogs/' + 
    req.params.year + '-' + req.params.month + '-' + req.params.day + '-' + req.params.title + '.md';
    fs.readFile(fileName,
    'utf-8', function (err, data) { if (err) { res.send(err); } res.send(data); }); });

    获取请求中参数的通用方法

    req.param(name)

    // ?name=tobi
    req.param('name')
    // => "tobi"
    
    // POST name=tobi
    req.param('name')
    // => "tobi"
    
    // /user/tobi for /user/:name
    req.param('name')
    // => "tobi"

    request.ip

    request.ip属性用于获得HTTP请求的IP地址。

    request.files

    request.files用于获取上传的文件。

    2.7 response对象

    response.redirect方法

    response.redirect方法用于网址的重定向。

    response.redirect("/hello/anime");
    response.redirect("http://www.example.com");
    response.redirect(301, "http://www.example.com");

    response.sendFile方法

    response.sendFile方法用于发送文件。

    response.sendFile("/path/to/anime.mp4");

    response.render方法

    response.render方法用于渲染网页模板。

    app.get("/", function(request, response) {
      response.render("index", { message: "Hello World" });    //将message变量传入index模板,渲染成HTML网页。
    });

    2.8 使用EJS模板

    EJS是一个JavaScript模板库,用来从JSON数据中生成HTML字符串。EJS的优点是将会带给你明确、维护性良好的HTML代码结构。EJS模板以”.ejs”为后缀。

    2.8.1 基本应用

    数据

    {
        title: 'Cleaning Supplies',
        supplies: ['mop', 'broom', 'duster']
    }

    模板

    <h1><%= title %></h1>
    <ul>
    <% for(var i=0; i<supplies.length; i++) {%>
        <li><%= supplies[i] %></li>
    <% } %>
    </ul>

    用数据渲染模板,WEB的显示结果:

    wps_clip_image-27354

    2.8.2 Include功能

    EJS提供了模版包含(include)的功能,可以include不同的页面部件,组合在一起。

    其语法为:<% include foot.ejs %>

    2.8.3 其他

    EJS实际可以不依赖Express而独立使用的。甚至在浏览器中也有很好的应用。

    3 Express所需要的工具组件

    Express 4.x 的版本做了很多改进,将很多功能分离出去,以第三方组件的形式为Express提供了更多的功能。同时Express也会有更好的扩展性。

    3.1 body-parser

    body-parser是Node.js的body解析中间件。用于解析HTTP报文body部分的数据。

    body-parser无法处理multipart的body,对于multipart,可能需要使用如下模块:

    · busboyconnect-busboy

    · multipartyconnect-multiparty

    · formidable

    · multer

    其他body解析器:

    · body

    · co-body

    API示例

    var express    = require('express')
    var bodyParser = require('body-parser')
    
    var app = express()
    // 解析application/x-www-form-urlencoded app.use(bodyParser.urlencoded())
    // 解析application/json app.use(bodyParser.json()) // 解析application/vnd.api+json 作为json app.use(bodyParser.json({ type: 'application/vnd.api+json' })) app.use(function (req, res, next) { console.log(req.body) // populated! next() })

    bodyParser.json(options)

    返回一个解析json的中间件。该解析器可以接受Unicode编码的请求体,可以作自动解压缩gzip,或解码。

    选项:

    • strict - 仅解析对象和数组。(default: true)
    • inflate - 设置是否解压被压缩的body (default: true)
    • limit - request的body的最大长度。 (default: <100kb>)
    • reviver - 传递给JSON.parse()
    • type - 解析的类型。(default: json)
    • verify - 判定body内容的函数。

    type参数将直接传递给 type-is 库。该值可以是一个扩展名(如:json),一个mime类型(如:application/json),或者一个带通配符的mime(如:*/json)

    若提供verify 参数,其函数形式为verify(req, res, buf, encoding),buf是Buffer类型,是请求体。encoding 是请求的编码。解析会被抛出的错误而中止。

    reviver 参数会被直接传递给 JSON.parse方法获取其第二个参数。可以在MDN 的文档中找到更多相关资料。

    bodyParser.raw(options)

    返回的中间件,中间件将body作为Buffer。该解析器可以作自动解压缩gzip或者解码。

    选项:

    • inflate - 同bodyParser.json (default: true)
    • limit - 同bodyParser.json (default: <100kb>)
    • type - 同bodyParser.json (default: application/octet-stream)
    • verify - 同bodyParser.json

    type参数将直接传递给 type-is 库。该值可以是一个扩展名(如:bin),一个mime类型(如:application/octet-stream),或者一个带通配符的mime(如:application/*)

    bodyParser.text(options)

    返回的中间件,中间件将body作为字符串。该解析器可以作自动解压缩gzip或者解码。

    选项:

    • defaultCharset - 若没有指定content-type,该值为默认字符集。 (default: utf-8)
    • inflate - 同bodyParser.json。 (default: true)
    • limit - 同bodyParser.json。 (default: <100kb>)
    • type - 同bodyParser.json。 (default: text/plain)
    • verify - 同bodyParser.json。

    type参数将直接传递给 type-is 库。该值可以是一个扩展名(如:txt),一个mime类型(如:text/plain),或者一个带通配符的mime(如:text/*)

    bodyParser.urlencoded(options)

    返回一个拥有解析urlencoded 请求体的中间件。该解析器可以接受Unicode编码的请求体,该可以作自动解压缩gzip,或解码。

    选项:

    • extended - 使用qs模块解析扩展的语法。(default: true)
    • inflate - 同bodyParser.json。  (default: true)
    • limit - 同bodyParser.json。  (default: <100kb>)
    • type - 同bodyParser.json。 (default: urlencoded)
    • verify - 同bodyParser.json。

    extended 参数允许选择解析urlencoded 数据的库,当为false时,使用querystring 库解析,当为true时,使用qs库解析。“扩展”的语法允许富对象和数组编码为urlencoded 格式,允许类JSON表达式。更多信息请参考qs库

    type参数将直接传递给 type-is 库。该值可以是一个扩展名(如:urlencoded),一个mime类型(如:application/x-www-form-urlencoded),或者一个带通配符的mime(如: */x-www-form-urlencoded)

    req.body

    HTTP中报文体的对象。

    例:

    var express = require('express');
    
    var app = express();
    
    app.use(express.bodyParser());
    
    app.all('/', function(req, res){
        res.send(req.body.title + req.body.text);
    });
    
    app.listen(3000);

    3.2 cookie-parser

    cookieParser中间件,用于分析和处理req.cookies的cookie数据。

    3.3 express-session

    express-session是一个为Express提供的 session中间件。

    var express = require('express')
    var session = require('express-session')
    var app = express() app.use(session({secret: 'keyboard cat'}))

    session(options)

    为session设置存储选项。session ID存储于cookie,其他数据不会被存储在cookie中。

    • name - cookie的名称(默认: 'connect.sid')
    • store - session的存储实例。
    • secret - session cookie通过secret 加密。
    • cookie - session相关cookie的设置项。(默认: { path: '/', httpOnly: true, secure: false, maxAge: null })
    • genid - session ID的生成函数。 (默认: 使用uid2库。)
    • rolling - 强制对每个response的cookie设置过期日期。(默认:false)
    • resave - 强制session为被保存的。(默认:true)
    • proxy - 信任反向代理,当时也secure cookies时候,当该项为true,则"x-forwarded-proto"报文头将会被使用。当设置为false时,所有的报文头都会被忽略。若不设置,则使用express的"trust proxy"设置。(默认:undefined)
    • saveUninitialized - 强制session以"uninitialized"被保存到仓库。会话是未初始化的时不能被修改。这对实现登录会话是非常有用的。(默认:true)
    • unset - 通过该选项,删除req.session或将其设为null后,设置req.session的结局。若为”keep”,则session将会在仓库中维持着,但无法被修改;若为”destory”则将销毁仓库中的session。(默认:'keep')

    options.genid

    对新session生成session ID。提供一个函数。函数返回一个字符串作为session ID。函数有一个req参数,在生成ID时,可以使用一些值附加到req上。

    注意:session ID需要唯一。

    例:

    app.use(session({
      genid: function(req) {
        return genuuid(); // use UUIDs for session IDs
      },
      secret: 'keyboard cat'
    }))

    Cookie选项

    请注意 secure: true 是一个被推荐的选项。然而,这需要https的支持。HTTPS需要安全的Cookie。如果secure 被设置了,若使用HTTP访问网站程序时,则cookie将不会被设置。如果你的node.js在代理之后,且使用了 secure: true ,被需要设置,则需要在express中设置"trust proxy" 。

    var app = express()
    
    app.set('trust proxy', 1) // trust first proxy
    app.use(session({ secret: 'keyboard cat' , cookie: { secure: true } }))

    在生产环境下,使用安全cookie。可以为了开发测试,如下是一个的设置基于NODE_ENV的例子:

    var app = express()
    
    var sess = {
      secret: 'keyboard cat'
      cookie: {}
    }
    
    if (app.get('env') === 'production') {
      app.set('trust proxy', 1) // trust first proxy
      sess.cookie.secure = true // serve secure cookies
    }

    app.use(session(sess))

    cookie.maxAge默认是null的,即expires没有配置,所以cookie用于浏览器的会话cookie。当时用户关闭浏览器,会话cookie就会被移除。

    req.session

    存储或访问session数据。

    示例:

    app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
    app.use(function(req, res, next) {
      var sess = req.session
      if (sess.views) {
        sess.views++
        res.setHeader('Content-Type', 'text/html')
        res.write('<p>views: ' + sess.views + '</p>')
        res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>')
        res.end()
      } else {
        sess.views = 1
        res.end('welcome to the session demo. refresh!')
      }
    })

    session.regenerate()

    调用该方法重新生成session,一旦完成一个SID,Session实例将会在req.session中被初始化。

    req.session.regenerate(function(err) {
      // 此处将使用一个新的session。
    })

    session.destroy()

    销毁session,移除req.session,将会重新生成下一个request。

    req.session.destroy(function(err) {
      // 此处无法使用session
    })

    session.reload()

    重新加载session数据。

    req.session.reload(function(err) {
      // session升级
    })

    session.save()

    req.session.save(function(err) {
      // session被保存
    })

    session.touch()

    更新maxAge属性。该方法不需要调用,session中间件会自动做这件事情。

    req.session.cookie

    每个session必须有一个唯一的cookie对象对应。

    • req.session.cookie.expires  用于cookie的过期设置。若req.session.cookie.expires设置为false,则使得cookie仅保持用户代理。
    • req.session.cookie.maxAge  为session.cookie的剩余时间,单位为毫秒。

    例:设置并查看cookie.maxAge的时间

    var hour = 3600000
    req.session.cookie.expires = new Date(Date.now() + hour)
    req.session.cookie.maxAge = hour

    30秒之后,req.session.cookie.maxAge返回剩余时间。

    req.session.cookie.maxAge // => 30000

    Session仓库的实现

    每个session仓库必须实现如下方法:

    • .get(sid, callback)
    • .set(sid, session, callback)
    • .destroy(sid, callback)

    推荐实现也实现如下方法:

    • .length(callback)
    • .clear(callback)

    例:以下是一个redis的实现。

    https://github.com/visionmedia/connect-redis

    Express官方资料:

    http://expressjs.com/

    https://www.npmjs.org/package/express

    https://github.com/visionmedia/expressjs.com

    https://github.com/visionmedia/express#quick-start

    body-parser相关参考资料:

    https://github.com/expressjs/body-parser

    https://www.npmjs.org/package/body-parser

    cookie-parser相关参考资料:

    https://github.com/expressjs/cookie-parser

    https://www.npmjs.org/package/cookie-parser

    express-session相关参考资料:

    https://github.com/expressjs/session

    https://www.npmjs.org/package/express-session

    关于expressjs更多的组件请在如下地址搜索:

    https://github.com/expressjs

    https://www.npmjs.org/

    感谢如下博客作者为我们提供的学习资料:

    http://javascript.ruanyifeng.com/nodejs/express.html

    http://www.cnblogs.com/moonpanda/p/3669735.html

    http://blog.segmentfault.com/nark/1190000000488358

    http://lostjs.com/2014/04/24/upgrade-express-4/

    https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x

    http://www.cnblogs.com/yuanzm/p/3770986.html

    http://my.oschina.net/tangdu/blog/282207

    http://www.cnblogs.com/ahl5esoft/p/3769781.html

    http://www.90it.net/topic/web/node

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处。LaplaceDemon/SJQ。

    http://www.cnblogs.com/shijiaqi1066/p/3821150.html

  • 相关阅读:
    spark 集合交集差集运算
    Scala学习笔记1(安装)
    shell脚本调用spark-sql
    R语言中判断是否是整数。以及读写excel
    el-table的type="selection"的使用
    el-mement表单校验-校验失败时自动聚焦到失败的input框
    "org.eclipse.wst.validation" has been removed 导入maven 项目出错。
    java compiler level does not match the version of the installed java project facet 解决方案
    Referenced file contains errors (http://www.springframework.org/schema/context). For more information, right click on the message in the Problems
    编译异常 Caused by: java.lang.UnsupportedClassVersionError:
  • 原文地址:https://www.cnblogs.com/shijiaqi1066/p/3821150.html
Copyright © 2020-2023  润新知