作者: admin

  • npm安装模块过程以及如何离线安装

    npm 笔记

    一、从 npm install 说起

    npm install 命令用来安装模块到node_modules目录。

    
    $ npm install <packageName>
    

    安装之前,npm install会先检查,node_modules目录之中是否已经存在指定模块。如果存在,就不再重新安装了,即使远程仓库已经有了一个新版本,也是如此。

    如果你希望,一个模块不管是否安装过,npm 都要强制重新安装,可以使用-f--force参数。

    
    $ npm install <packageName> --force
    

    二、npm update

    如果想更新已安装模块,就要用到npm update命令。

    
    $ npm update <packageName>
    

    它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。

    三、registry

    npm update命令怎么知道每个模块的最新版本呢?

    答案是 npm 模块仓库提供了一个查询服务,叫做 registry 。以 npmjs.org 为例,它的查询服务网址是 https://registry.npmjs.org/ 。

    这个网址后面跟上模块名,就会得到一个 JSON 对象,里面是该模块所有版本的信息。比如,访问 https://registry.npmjs.org/react,就会看到 react 模块所有版本的信息。

    它跟下面命令的效果是一样的。

    
    $ npm view react
    
    # npm view 的别名
    $ npm info react
    $ npm show react
    $ npm v react
    

    registry 网址的模块名后面,还可以跟上版本号或者标签,用来查询某个具体版本的信息。比如, 访问 https://registry.npmjs.org/react/v0.14.6 ,就可以看到 React 的 0.14.6 版。

    返回的 JSON 对象里面,有一个dist.tarball属性,是该版本压缩包的网址。

    
    dist: {
      shasum: '2a57c2cf8747b483759ad8de0fa47fb0c5cf5c6a',
      tarball: 'http://registry.npmjs.org/react/-/react-0.14.6.tgz' 
    },
    

    到这个网址下载压缩包,在本地解压,就得到了模块的源码。npm installnpm update命令,都是通过这种方式安装模块的。

    四、缓存目录

    npm installnpm update命令,从 registry 下载压缩包之后,都存放在本地的缓存目录。

    这个缓存目录,在 Linux 或 Mac 默认是用户主目录下的.npm目录,在 Windows 默认是%AppData%/npm-cache。通过配置命令,可以查看这个目录的具体位置。

    
    $ npm config get cache
    $HOME/.npm
    

    你最好浏览一下这个目录。

    
    $ ls ~/.npm 
    # 或者
    $ npm cache ls
    

    你会看到里面存放着大量的模块,储存结构是{cache}/{name}/{version}

    
    $ npm cache ls react
    ~/.npm/react/react/0.14.6/
    ~/.npm/react/react/0.14.6/package.tgz
    ~/.npm/react/react/0.14.6/package/
    ~/.npm/react/react/0.14.6/package/package.json
    

    每个模块的每个版本,都有一个自己的子目录,里面是代码的压缩包package.tgz文件,以及一个描述文件package/package.json

    除此之外,还会生成一个{cache}/{hostname}/{path}/.cache.json文件。比如,从 npm 官方仓库下载 react 模块的时候,就会生成registry.npmjs.org/react/.cache.json文件。

    这个文件保存的是,所有版本的信息,以及该模块最近修改的时间和最新一次请求时服务器返回的 ETag 。

    
    {
      "time":{
        "modified":"2016-01-06T23:52:45.571Z",
        // ...
      },
      "_etag":"\"7S37I0775YLURCFIO8N85FO0F\""
    }
    

    对于一些不是很关键的操作(比如npm searchnpm view),npm会先查看.cache.json里面的模块最近更新时间,跟当前时间的差距,是不是在可接受的范围之内。如果是的,就不再向远程仓库发出请求,而是直接返回.cache.json的数据。

    .npm目录保存着大量文件,清空它的命令如下。

    
    $ rm -rf ~/.npm/*
    # 或者
    $ npm cache clean
    

    五、模块的安装过程

    总结一下,Node模块的安装过程是这样的。

    1. 发出npm install命令
    2. npm 向 registry 查询模块压缩包的网址
    3. 下载压缩包,存放在~/.npm目录
    4. 解压压缩包到当前项目的node_modules目录

    注意,一个模块安装以后,本地其实保存了两份。一份是~/.npm目录下的压缩包,另一份是node_modules目录下解压后的代码。

    但是,运行npm install的时候,只会检查node_modules目录,而不会检查~/.npm目录。也就是说,如果一个模块在~/.npm下有压缩包,但是没有安装在node_modules目录中,npm 依然会从远程仓库下载一次新的压缩包。

    这种行为固然可以保证总是取得最新的代码,但有时并不是我们想要的。最大的问题是,它会极大地影响安装速度。即使某个模块的压缩包就在缓存目录中,也要去远程仓库下载,这怎么可能不慢呢?

    另外,有些场合没有网络(比如飞机上),但是你想安装的模块,明明就在缓存目录之中,这时也无法安装。

    六、--cache-min 参数

    为了解决这些问题,npm 提供了一个--cache-min参数,用于从缓存目录安装模块。

    --cache-min参数指定一个时间(单位为分钟),只有超过这个时间的模块,才会从 registry 下载。

    
    $ npm install --cache-min 9999999 <package-name>
    

    上面命令指定,只有超过999999分钟的模块,才从 registry 下载。实际上就是指定,所有模块都从缓存安装,这样就大大加快了下载速度。

    它还有另一种写法。

    
    $ npm install --cache-min Infinity <package-name>
    

    但是,这并不等于离线模式,这时仍然需要网络连接。因为现在的--cache-min实现有一些问题。

    (1)如果指定模块不在缓存目录,那么 npm 会连接 registry,下载最新版本。这没有问题,但是如果指定模块在缓存目录之中,npm 也会连接 registry,发出指定模块的 etag ,服务器返回状态码304,表示不需要重新下载压缩包。

    (2)如果某个模块已经在缓存之中,但是版本低于要求,npm会直接报错,而不是去 registry 下载最新版本。

    npm 团队知道存在这些问题,正在重写 cache。并且,将来会提供一个--offline参数,使得 npm 可以在离线情况下使用。

    不过,这些改进没有日程表。所以,当前使用--cache-min改进安装速度,是有问题的。

    七、离线安装的解决方案

    社区已经为npm的离线使用,提出了几种解决方案。它们可以大大加快模块安装的速度。

    解决方案大致分成三类。

    第一类,Registry 代理。

    上面三个模块的用法很类似,都是在本机起一个 Registry 服务,所有npm install命令都要通过这个服务代理。

    
    # npm-proxy-cache
    $ npm --proxy http://localhost:8080 \
      --https-proxy http://localhost:8080 \
      --strict-ssl false \
      install
    
    # local-npm
    $ npm set registry http://127.0.0.1:5080
    
    # npm-lazy
    $ npm --registry http://localhost:8080/ install socket.io
    

    有了本机的Registry服务,就能完全实现缓存安装,可以实现离线使用。

    第二类,npm install替代。

    如果能够改变npm install的行为,就能实现缓存安装。npm-cache 工具就是这个思路。凡是使用npm install的地方,都可以使用npm-cache替代。

    
    $ npm-cache install
    

    第三类,node_modules作为缓存目录。

    这个方案的思路是,不使用.npm缓存,而是使用项目的node_modules目录作为缓存。

    上面两个工具,都能将项目的node_modules目录打成一个压缩包,以后安装的时候,就从这个压缩包之中取出文件。

    (完)

    http://www.ruanyifeng.com/blog/2016/01/npm-install.html 

     

  • node.js制作可跨越平台的桌面应用程序——electron

    软件 node 软件 node.js
    electorn

    可跨平台的桌面程序,这是基于谷歌浏览器内核和Node.js下运行的。

    目前常见的有 NW、heX、Electron。今天,就来简单的上手一下 Electron。

    由于内置的 Chromium 内核 和 Node, 因此我们不需要关心前端的兼容问题,你甚至可以写 -webkit- only 的代码; 也不需要关心一些需要编译的 Node 模块兼容问题,因为 Node 版本是固定的。因此,用 Electron 来编写跨平台应用程序是非常合适的。

    electron最后打包生成的程序也分为:win、linux和mac平台的

    直接上干货,这里我是以mac平台操作的:

    快速运行hello world程序

    npm install electron@1.4.13 -g
    electron -v  //查看是否安装成功,这里我用的是1.4.13

     

    下载一个hello world程序

    git clone https://github.com/electron/electron-quick-start  //下载完成后会多出一个electron-quick-start目录
    cd electron-quick-start //进入到此目录中
    npm install 
    electron .

    这时候会弹出一个程序说明已经成功了

    或者运行

    electron

    之后会弹出一个窗口把我们写的程序文件目录直接拖进去一样也能运行(例如我下面写的myapp目录直接拖进去)

     

     

     打包程序

    之后打包我们的程序让我们的程序更像一个桌面应用,(打包后缀例如是:.app .exe的可执行程序)

    首先下载打包工具:

    npm install electron-packager@8.4.0 -g

    进入到我们刚刚的electron-quick-start目录下把 package.json  index.html  main.js这3个文件重新放在一个独立的目录下,目录例如是myapp。

    进入到myapp目录中运行:

    electron-packager . myapp --platform=darwin --arch=x64 --version=1.4.13

    在命令运行的过程中会自动下载你设置版本号的编译工具,我这里用的是1.4.13如果当前没有electron目录它就会自动下载一个electron-v1.4.13-linux-ia32.zip的文件,由于是国内根本就下载不了还是手动下载吧

    下载地址是:https://github.com/electron/electron/releases

    把下载好的文件electron-v1.4.13-linux-ia32.zip解压后改名字为electron,在执行一次

    electron-packager . myapp --platform=darwin --arch=x64 --version=1.4.13

    就会在当前目录下生成一个myapp-darwin-x64目录,进入到此目录后你就会看到一个myapp程序直接点击就可以运行了。

     

    进一步打包成dmg

    1,首先要保证已经打包成功了后缀是app的程序

    2,打开“磁盘工具”—“文件”—“新建映像”—“来自文件夹的映像”,选择并进入到你要打包成dmg的目录里面,点击“打开”,(本人系统是10.11.6,不同版本名字叫法不一样教程也不一样)

    3,再次弹出一个窗口,设置另存为名字,之后点击“存储”

    4,完成后就会在当前目录下生成了一个dmg文件了

     

     

    使用命令:

    electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [optional flags...]
    • <sourcedir>: 项目的位置
    • <appname>: 应用名
    • --platform=<platform>: 打包的系统(darwin、win32、linux)
    • --arch=<arch>: 系统位数(ia32、x64)
    • --icon=<icon>: 指定应用的图标(Mac 为 .icns 文件,Windows 为 .ico 或 .png)
    • --out <out>: 指定输出的目录
    • --version=<version>: 指定编译的 electron-prebuilt 版本

    例子:

    electron-packager ./ WeFlow --platform=darwin --arch=x64 --icon=./assets/img/WeFlow.icns --overwrite --out ./dist --version=0.37.8

    我们可以直接在 package.json 的 script 字段中添加脚本,如下:

    "scripts": {
        "build:all": "electron-packager . --all --overwrite",
        "build:mac": "electron-packager ./ WeFlow --platform=darwin --arch=x64 --icon=./assets/img/WeFlow.icns --overwrite --out ./dist --version=0.37.8",
        "build:win64": "electron-packager ./ WeFlow --platform=win32 --arch=x64 --icon=./assets/img/WeFlow.png --overwrite --out ./dist --version=0.37.8",
        "build:win32": "electron-packager ./ WeFlow --platform=win32 --arch=ia32 --icon=./assets/img/WeFlow.png --overwrite --out ./dist --version=0.37.8 --app-version=1.0.0"
    }

    参考学习文档:

    https://runkit.com/npm/electron-packager-itchio

    https://segmentfault.com/a/1190000005744529 

    官网:http://electron.atom.io/

  • koa2笔记 koa-generator脚手架

    koa笔记 koa2 笔记

    之前一直使用express觉得还可以,不久express团队又出了一个koa,也出现了1,2版本,目前学习最好使用koa2,使用了es6语法。直接上干货,使用过express上手搭建一个项目其实很简单。

    首先:

    npm install koa-generator -g  //全局安装这个就可以使用koa命令了

    在你需要创建koa项目的目录下输入以下命令

    koa2 -e koa2   //此时就会出现一个koa2的目录,并且里面会有一些初始化的文件
    如果想使用koa1的话就
    koa -e 目录名称 //生成的是koa1的版本
    koa2 -e 目录名称 //生成的时koa2的版本

    进入到koa2目录下,接着安装需要的依赖模块

    npm install

    启动koa

    node ./bin/www

    最后打开浏览器输入:http://localhost:3000/ ,就可以看到我们网站建立了。

    提示:如果简单使用node命令启动koa 2报错可以尝试使用

    node --harmony xx.js

    来启动我们的koa项目,因为koa有时候会需要es5的一些属性,带有–harmony参数才能够启用部分es5的功能。

    要么升级node.js环境到7.6以上

    ————————————–

    ————————————–

    简单的启动web服务,访问http://localhost:3000/

    var Koa =require('koa');
    var app=new Koa();
    
    app.use(async function (ctx,next) {
      await next();
      ctx.response.type='text/html';
      ctx.response.body='<div style="background: red">你好</div>';
    });
    
    app.listen(3000);

    // koa2中间件执行顺序 (洋葱模式)
    //最先的中间件会被执行,直到执行到最后一个中间件后又会再次返回来,
    //中间件必须await next();不然就不会执行到下一个中间件了。
    //koa2启动运行其实就是在按着洋葱模式执行着一个个中间件
    //如果请求一个css文件和一个js文件,相当于执行了某个中间件两次

    var Koa =require('koa');
    var app=new Koa();
    
    
    app.use(async function (ctx,next) {
      console.log(1); //<--最先被执行
      await next();
      console.log(2); //<--最后被执行
    });
    
    
    app.use(async function (ctx,next) {
      console.log(3);
      console.log(4);
      await next();
    });
    
    
    app.use(async function (ctx,next) {
      await next();
      console.log(5);
      console.log(6);
    });
    
    
    app.use(async function (ctx,next) {
      console.log(7);
      await next();
      console.log(8);
    });
    
    
    app.listen(3000);
    
    // 结果
    // 1
    // 3
    // 4
    // 7
    // 8
    // 5
    // 6
    // 2

    //封装一个中间件

    //test.js中间件

    function log( ctx ) {
      console.log( '执行自定义中间件' )
    }
    
    module.exports = function () {
      return async function ( ctx, next ) {
        log(ctx);
       await next()
      }
    }

    //加载,并调用

    var test=require('./test');
    
    app.use(test());

    *koa@2中间件只支持 async/await 封装的,如果要使用koa@1基于generator中间件,需要通过中间件koa-convert封装一下才能使用

    中间件之间传递数据

    app.use(async function (ctx,next) {
      ctx.haha='haha';
      await next();
    });
    
    
    app.use(async function (ctx,next) {
      await next();
      console.log(ctx.haha); //我能获取到上一个中间件赋值的变量
    });

    ctx上下文
    ctx.response.type 可以缩写为 ctx.type,返回给浏览器的参数
    有等号,表示还可以修改
    ctx.body
    ctx.body=
    ctx.status
    ctx.status=
    ctx.length=
    ctx.type
    ctx.type= // ctx.type = ‘text/plain; charset=utf-8’;
    ctx.headerSent
    ctx.redirect()
    ctx.attachment()
    ctx.set() // ctx.set(‘Content-Type’, ‘text/html’)
    ctx.remove()
    ctx.lastModified=
    ctx.etag=
    —————————
    ctx.request //发送给服务器端
    ctx.header //ctx.request.header = {‘authorization’: “Bearer ” + ctx.cookies.get(‘state’)}
    ctx.method
    ctx.method=
    ctx.url
    ctx.url=
    ctx.path
    ctx.path=
    ctx.query
    ctx.query=
    ctx.querystring
    ctx.querystring=
    ctx.length
    ctx.host
    ctx.fresh
    ctx.stale
    ctx.socket
    ctx.protocol
    ctx.secure
    ctx.ip
    ctx.ips
    ctx.subdomains
    ctx.is()
    ctx.accepts()
    ctx.acceptsEncodings()
    ctx.acceptsCharsets()
    ctx.acceptsLanguages()
    ctx.get()

     

     

    支持Es6的import导入

    package.json 的根节点新增 “type”: “module”  //注意要 v14.15.1以上, 不推荐这种写法,这样就不支持commonjs语法了

    或者把要导入的模块改成后缀为mjs ,  import a from ‘./a.mjs’;

    从 Node.js v13.2 版本开始

    Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定”use strict”。

     

     

    //区分测试环境、生产环境
    可能需要在不同环境下跑不同的逻辑,这时候就需要到
    安装:npm i cross-env -g //为win和linux做好了兼容
    启动执行:cross-env NODE_ENV=production pm2 start ./1.js //以生产方式启动
    获取判断方法:process.env.NODE_ENV //输出 production

    cross-env NODE_ENV=production PORT=3001 node ./1.js
    获取端口process.env.PORT

     

    另一种方法:

    "dev": "./node_modules/.bin/nodemon bin/www token=true",
    
    
    //获取 bin/www xxx1=xxx2  后面的参数 打印出 { xxx1:xxx2 }
    function getOption() {
        const arr = process.argv.slice(2); // 获取命令行参数数组
        const r = arr.reduce((pre, item) => { // 使用reduce方法对参数数组进行处理
            if (item.indexOf("=") !== -1) { // 判断参数是否有等号
                return [...pre, item.split("=")]; // 将带有等号的参数进行分割并添加到结果数组中
            }
            return pre; // 否则返回原结果数组
        }, []);
        const params = Object.fromEntries(r); // 将结果数组转化为参数对象
        return params; // 返回参数对象
    }
    
    调用:getOption().token  //获取都token的字段值
    

     

     

     

     

     

    // 数据跟踪监听

    var Koa =require('koa');
    var app=new Koa();
    
    const logger = require('koa-logger');
    
    app.use(logger());
    
    app.use(async function (ctx,next) {
     await next();
     ctx.response.type='text/html';
     ctx.response.body='<div style="background: red">你好</div>';
    });
    
    app.listen(3000);

    创建一个页面

    创建首页http://127.0.0.1/
     router.get('/', function (ctx, next) { //首页
         ctx.body = 'Hello World!';
     })

    推荐此方法创建页面:
    创建内页并且带2个参数 http://127.0.0.1/www/27/28

     router.get('www/:id/:age', async function (ctx, next) {
       ctx.state = {
          title:'ffff'+ctx.params.id
       };
       await ctx.render('index', {
       });
     })

     get传参

    //page1页面传参
    router.get('page1/:id/:age', async function (ctx, next) {
       ctx.state = {
          title:'ffff'+ctx.params.id
       };
       await ctx.render('index', {
       });
    })
    
    地址:http://xxxx.com/page1/参数1/参数2
    //首页传参
    router.get(':id/:age', async function (ctx, next) {
       ctx.state = {
          title:'ffff'+ctx.params.id
       };
       await ctx.render('index', {
       });
    })
    
    地址:http://xxxx.com/参数1/参数2

    连缀

     router //这里是4个路由
     .get('/', function (ctx, next) {
        ctx.body = 'Hello World!';
     })
     .post('/users', function (ctx, next) {
        // ...
     })
     .put('/users/:id', function (ctx, next) {
        // ...
     })
     .del('/users/:id', function (ctx, next) {
        // ...
     });

    路由回调

    为某个路由访问成功添加回调

    router.get('/',async (ctx, next) => {
      console.log('1');
      await next(); //要加这个才能执行到all回调
      console.log('2');
    }).all('/',async function (ctx, next) {
      console.log('3');
    });
    
    结果:
    1
    3
    2
    跟中间件的洋葱模式一样

    路由全局   (很有用) 拦截器

    效果就是访问任意一个url路由之前时,都要执行某段业务逻辑。

    不管先访问首页还是haha,都要先执行all路由。

    router.all('*',async function (ctx, next) {
      ctx.state={  //全局变量,在ejs模板中可以直接显示出,如<%= web %>
          web:'http://localhost:3000',
          title:'111'
      }
      await next(); //必须加这个
    });
    
    router.get('/',async (ctx, next) => {
      //ctx.state,可以获取到初始变量
      ctx.type='json';
      ctx.body={xx:'123'};
    });
    
    router.get('haha',async (ctx, next) => {
      ctx.body='haha';
    });
    
    
    
    
    

    或者

    router.use(async (ctx,next)=>{
    
        //全局的G变量
        ctx.state.G={
            url:'http://www.itying.com',
            userinfo:111,
            prevPage:222   /*上一页的地址*/
        }
        await next();
    })
    
    router.use('/',index.routes());
    router.use('/admin',admin.routes());

    前缀

     const router = new Router({
        prefix: '/users'
     });
    
     router.get('/', ...); //实际的响应路径是:/users
     router.get('/:id', ...); //实际的响应路径是:/users/:id

    为一个路由命名

    router.get('home',':haha/:vv', function (ctx, next) { //为首页的路由命名为home
     ctx.body = 'Hello World!';
     router.url('home', 3,'age') //第一次参数填写路由名称,第二个参数开始填写自定义参数,输出:/3/age
    })

    路由使用中间件

     router.use(session(), authorize()); //所有路由都使用这两个中间件
     router.use('/users', userAuth()); //只有跳转到/users页面时使用 这个中间件
     router.use(['/users', '/admin'], userAuth()); //只有这两个页面可以使用
     app.use(router.routes()); //上面是设置,此步骤是启动

    单个路由支持多个中间件执行

    //child.js
    
    function log( ctx ) {
      console.log( '执行自定义中间件' )
    }
    
    module.exports = function () {
      return async function ( ctx, next ) {
        log(ctx);
        await next()
      }
    };
    var child=require('./child');
    
    router.get('/',
      child(), //一个中间件
      async (ctx,next)=>{ //一个中间件
         console.log(11111);
         await next();
      },
      async (ctx, next) => {
         await ctx.render('index',{
           title:'xxxxx'
         })
    });

    重定向、页面跳转

     ctx.redirect('http://localhost:3000/www/123/456')
    app.use(async function (ctx,next) {   //访问到首页就自动跳转到/wucg目录。像apache、nginx也能实现
      if(ctx.originalUrl=='/'){
        ctx.response.redirect('/wucg');
      }
      await next();
    });
    
    router.use('/wucg',index.routes(),index2.routes());
    router.use('/admin',admin.routes());
    
    app.use(router.routes(), router.allowedMethods());

    //让页面报错 抛错

    //全局,在一个路由上设置,会让所有接口都报400
    ctx.throw([msg],[status],[properties])
    ctx.throw(400, 'name required');
    
    //局部,单个接口报错
    router.post('/hnsy-app/aed/pageAedInfo',async (ctx, next)=>{
        await new Promise((a,b)=>{
    
            setTimeout(()=>{
                ctx.status=500
                a()
            },8000)
    
        })
    
    })

    SSL、https

    node.js配置ssl、http2

    路由参数验证

    要访问该地址之前http://127.0.0.1/www/参数1/参数2,先验证参数1,2,在决定是否在跳转

    router
     .param('ff', function (ff, ctx, next) {  //参数1的验证逻辑
          console.log(ff+'<----------------');
          return next();
     })
     .param('vv', function (vv, ctx, next) {  //参数2的验证逻辑
          console.log(vv+'<----------------');
          if (!vv) return ctx.status = 404;
          return next();
     })
     .get('www/:ff/:vv', async function (ctx, next) {
       // ctx.state = {
         // title:'shouye1111'
       // };
    
         await ctx.render('index', {
           title:'shouye1111'+ctx.params.ff
         });
    })

    一个路由业务逻辑分离成多个路由

    假如我们在写后台的业务逻辑时,往往需要很多判断,我们不可能把所有代码都写进一个路由中,时间长了代码不容易维护,这时候我们需要把后台的业务逻辑同时分离归类成多个文件,编辑文件app.js

    const admin = require('./routes/admin/admin_login'); //后台登陆页面
    const admin_default = require('./routes/admin/admin_default'); //后台登陆页面后的业务逻辑
    .
    .
    .
    router.use('/admin', admin.routes(), admin.allowedMethods());
    router.use('/admin', admin_default.routes(), admin_default.allowedMethods());

    或者

    const admin = require('./routes/admin/admin_login');
    router.use(admin.routes(), admin.allowedMethods());

    编辑:./routes/admin/admin_login.js,

    const router = require('koa-router')()
    router.prefix('/admin')  //新增

    ————以上是路由的使用——————–
    post和get

    后台获取客户端get发来的数据
    ctx.params     //获取到的是以路由 router.get(‘/:name’) 形式   ,http://xxx/123

    ctx.query   //{wo:’chen’,age:’123′} ,获取到的是url这样传递的数据,http://xxx/1.php?wo=’chen’&age=’123′

    后台获取客户端post发来的数据
    ctx.request.body

    cookies的设置和获取
    ctx.cookies.set(‘name’, ‘value’, {httpOnly:false}) //设置 {httpOnly:false},表示可以通过客户端获取cookies的值
    signed: 是否要做签名
    expires: cookie 有效期时间
    path: cookie 的路径,默认为 /’
    domain: cookie 的域
    secure: false 表示 cookie 通过 HTTP 协议发送,true 表示 cookie 通过 HTTPS 发送。
    httpOnly: true 表示 cookie 只能通过 HTTP 协议发送

    例如:

    ctx.cookies.set(
     'state',
     'hello world',
     {
       domain: 'localhost', // 写cookie所在的域名
       path: '/', // 写cookie所在的路径
       maxAge:7200000, // 2小时有效
       expires: new Date('2017-07-15'), // cookie失效时间
       httpOnly: false, // 是否只用于http请求中获取
       overwrite: false // 是否允许重写
     }
    )
    
    ctx.cookies.set('token', response2.data.access_token,{maxAge:7200000}) //2小时有效

    ctx.cookies.get(name, [options]) //获取

    session使用

    方法一:

    npm install koa-session --save  //安装
    
    //编辑main.js,加入以下内容
    const session = require('koa-session');
    
    app.keys = ['some secret hurr'];
    const CONFIG = {
     key: 'koa:sess', //cookie key (default is koa:sess)
     maxAge: 86400000, // cookie的过期时间 maxAge in ms (default is 1 days)
     overwrite: true, //是否可以overwrite (默认default true)
     httpOnly: true, //cookie是否只有服务器端可以访问 httpOnly or not (default true)
     signed: true, //签名默认true
     rolling: false, //在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)
     renew: false, //(boolean) renew session when session is nearly expired,
    };
    app.use(session(CONFIG, app));

    方法二:

     npm install koa2-cookie-session --save
    import session from "koa2-cookie-session"; //一定要用这种方式加载
    var router = require('koa-router')();
    router.use(session({
      key: "SESSIONID", //default "koa:sid"
      expires:3, //default 7
      path:"/" //default "/"
    }));
    
    router.get('home',':haha/:vv',async function (ctx, next) {
      ctx.session.user = {name: "myname更新"}; //设置
    });
    
    ctx.session.user //获取

    api 接口

    router.get('/json', async (ctx, next) => {
    
     ctx.body = {
       code:200,
       title: 'koa2 json',
       data:{
        xx:1,
        xx:2
       }  
     }
    
    })

    生成log日志文件

    下载:log

    1,安装:npm install –save log4js //4.5.1
    2,解压zip把utils和config目录放到根目录下

    // logger
    const logsUtil = require('./utils/logs.js');
    app.use(async (ctx, next) => {
        const start = new Date();              // 响应开始时间
        let intervals;                      // 响应间隔时间
        try {
            await next();
            intervals = new Date() - start;
            logsUtil.logResponse(ctx, intervals);  //记录响应日志
        } catch (error) {
            intervals = new Date() - start;
            logsUtil.logError(ctx, error, intervals);//记录异常日志
        }
    })
    
    
    router.use('/',index.routes(),index2.routes());
    router.use('/admin',admin.routes());

    启动项目后,访问下网站就会在根目录下生成logs目录,里面就记录这log日志文件了

    记录指定路由日志
    例如routes/index

    const logsHandle = require("../utils/logs.js").logHandle;
    const logsInfo = require("../utils/logs.js").logInfo;
    .
    .
    
    router.get('json', async (ctx, next) => {
        logsHandle("请求了json接口")  // 假设此处需要记录敏感操作的日志
        logsInfo("请求了json接口")    // 假设此处需要在控制台打印日志
        ctx.body = {
            title: 'koa2 json'
        }
    })

    日志打印出

    https://www.cnblogs.com/HoChine/p/10717831.html

    自定义404页面
    (是当用户乱输入url时,没有相应的路由匹配的时候才跳转404页面)

    404页面也可以用nginx、apache来处理
    编辑文件:app.js

    //编辑以下代码
    app.use(async (ctx, next) => {
     if (ctx.status === 404){
        ctx.state={
           title:'404页面'
        };
        await ctx.render('error', {  //error为你的错误页面
        });
     }
    });
    
    
    module.exports = app;

    接着在views/error.ejs,创建错误页面,即可。

     

     

    自定义接口错误信息,编辑app.js

    // 身份认证错误中间件
    app.use(async (ctx, next) => {
        return next().catch(err => {
            if (err.status === 401) {
                // 自定义返回结果
                ctx.status = 401;
                console.log(err.name);
                ctx.body = {
                    code: 401,
                    msg: err.message
                };
            } else {
                throw err;
            }
        });
    });

    效果:

     

     

     

    时间戳

    这里推荐使用一款模块插件

    安装:

    npm install moment

    加载调用:

    var moment = require('moment');
    moment().format();

    案例:

    moment().format('YYYY-M-DD kk:mm:ss');  //2017-01-01 01:01:01

    文档:http://momentjs.com/docs/#/displaying/

    上传

    安装:

    npm install koa-body@2 --save

    前端:创建页面views/upload.ejs

    <!doctype html>
    <html>
    <body>
    <form action="/upload" enctype="multipart/form-data" method="post">
     <input type="text" name="username" placeholder="username"><br>
     <input type="text" name="title" placeholder="title of file"><br>
     <input type="file" name="uploads" multiple="multiple"><br>
     <button type="submit">Upload</button>
    </form>
    </body>
    </html>

    后台:routes/index.js

    router.get('/haha',async function (ctx,next) { 
       await ctx.render('upload', {}); 
    });
    
    
    router.post('upload',
        koaBody({
            multipart: true,
            formidable: {
                uploadDir:process.cwd() + '/public/upload',
                keepExtensions:true,
                onFileBegin: (name, file) => { // 文件上传前的设置
                    const fp = process.cwd() + '/public/upload';
                    if (!fs.existsSync(fp)) { // 检查是否有“public/upload/”文件夹
                        fs.mkdirSync(fp); // 没有就创建
                    }
                }
            }
        }),
        async function (ctx,next) {
            await new Promise((a,b)=>{
    
                if((ctx.request.body.files["file"]).size==0){
                    fs.unlink((ctx.request.body.files["file"]).path,function (err) {
                        if(err) throw err;
                        console.log('成功')
                    });
    
                }
                a();
            });
            ctx.body =ctx.request.body;
        }
    
    )

    要注意提交action的路径和图片上传到服务器的路径

    访问http://localhost:3000/haha

    koa-body参数:

    参数名 描述 类型 默认值
    patchNode 将补丁写入ctx.req节点中 Boolean false
    patchKoa 将补丁写入ctx.request节点中 Boolean true
    jsonLimit JSON 数据体的大小限制 String / Integer 1mb
    formLimit 限制表单请求体的大小 String / Integer 56kb
    textLimit 限制 text body 的大小 String / Integer 56kb
    encoding 表单的默认编码 String utf-8
    multipart 是否支持 multipart-formdate 的表单 Boolean false
    urlencoded 是否支持 urlencoded 的表单 Boolean true
    text 是否解析 text/plain 的表单 Boolean true
    json 是否解析 json 请求体 Boolean true
    jsonStrict 是否使用 json 严格模式,true 会只处理数组和对象 Boolean true
    formidable 配置更多的关于 multipart 的选项 Object
    onError 错误处理 Function
    stict 严格模式,启用后不会解析 GET, HEAD, DELETE 请求 Boolean true

    其中formidable

    参数名 描述 类型 默认值
    maxFields 限制字段的数量 Integer 1000
    maxFieldsSize 限制字段的最大值 Integer 2mb (2 * 1024 * 1024)
    uploadDir 设置文件上传的文件夹 String os.tmpDir()
    keepExtensions 保留原来的文件后缀 Boolean false
    hash 如果希望计算传入文件的校验,请将其设置为“sha1”或“md5” String false
    multipart 是否支持多文件上传 Boolean true
    onFileBegin 开始时对文件进行特殊回调。它可以用于在将文件保存到磁盘之前重命名文件等 Function

    案例:

    推荐把bodyparse加载到app.js里面的全局中,不然每个router都要加载才能获取post传来的数据不现实

    app.use(koaBody({
        multipart: true,
        formidable: {
            uploadDir:process.cwd() + '/public/upload',
            keepExtensions:true,
            onFileBegin: (name, file) => { // 文件上传前的设置
                const fp = process.cwd() + '/public/upload';
                if (!fs.existsSync(fp)) { // 检查是否有“public/upload/”文件夹
                    fs.mkdirSync(fp); // 没有就创建
                }
                // console.log(`bodyparse: name:${name}; file:${util.inspect(file)}`);
            }
        }
    }));

    https://blog.csdn.net/weixin_40415614/article/details/84783723

    上传base64图片

    base64的形式为“data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0。。。。”;
    当接收到上边的内容后,需要将data:image/png;base64,这段内容过滤掉,过滤成:“iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0。。。”;传给后台

    前端:

    <form action="/uploadbase64" enctype="multipart/form-data" method="post">
      <input value="" name="baseimg" hidden id="baseimg">
      <input id="fielinput" type="file" multiple="multiple" accept="image/*" onchange="showPreview(this)"><br>
      <img id="fielImg" src="" />
      <button>Upload</button>
    </form>
    
    
    
    <script>
        function showPreview(source) {
            var file = source.files[0];
            //判断文件类型
            var extfile = file.name;
            var AllImgExt=".jpg|.jpeg|.gif|.bmp|.png|";
            var extName = extfile.substring(extfile.lastIndexOf(".")).toLowerCase();//(把路径中的所有字母全部转换为小写)
            if(AllImgExt.indexOf(extName+"|")==-1)
            {
                var ErrMsg="该文件类型不允许上传。请上传 "+AllImgExt+" 类型的文件,当前文件类型为"+extName;
                alert(ErrMsg);
                return false;
            }
            if(window.FileReader) {
                var fr = new FileReader();
                fr.readAsDataURL(file);
                fr.onloadend = function(e) {
                    document.getElementById("fielImg").src = e.target.result;
                    document.getElementById("baseimg").value = e.target.result;
                };
            }else{
                alert('浏览器不支持预览图片');
            }
        };
    
    </script>

    后台:

    router.post('uploadbase64',async (ctx, next) => {
    
        var imgData=ctx.request.body.fields.baseimg;
        var reg=/^data:image\/(\w+);base64,/;  //传给后台的base64,前面这部分的不要,前端显示才需要
        var base64Data = imgData.replace(reg, "");
        var imgtype=RegExp.lastParen; //获取到括号的字符串
    
    
       // var dataBuffer = new Buffer(base64Data, 'base64');  //也可以用,但是会提示有安全问题
        var dataBuffer =Buffer.from(base64Data, 'base64');
        fs.writeFile(process.cwd() + '/public/upload/image.'+imgtype, dataBuffer, function(err) {
            // if(err){
            //     res.send(err);
            // }else{
            //     res.send("保存成功!");
            // }
        });
        
    
        ctx.body =ctx.request.body;
    });

    下载

    另存为效果

     

     

    接口模拟延迟

    router.post('/syldmh-web/service/wdzhq/getTopicData',async (ctx,next) =>{
    
     async function delay(time) {
      return new Promise(function(resolve, reject) {
         setTimeout(function(){
            resolve();
         }, time);
      });
     };
     await delay(4000);
    
     ctx.body = {}
    
    })

     

     

    图片压缩 

    使用第三方:https://tinypng.com/developers/reference/nodejs

    安装:npm install –save tinify

    在 app.js

    const tinify = require("tinify");
    tinify.key = "API Key";

    上传接口:

    const tinify = require("tinify");
    .
    .
    router.post('upload',koaBody({
       multipart: true,
       formidable: {
         uploadDir:process.cwd() + '/public/upload',
         keepExtensions:true,
         onFileBegin: (name, file) => { // 文件上传前的设置
           const fp = process.cwd() + '/public/upload';
           if (!fs.existsSync(fp)) { // 检查是否有“public/upload/”文件夹
             fs.mkdirSync(fp); // 没有就创建
           }
         }
       }
    }),async (ctx, next) => {
    
      if( !!ctx.request.body.files["file"].length ){ //多图片
        for(let i=0;i<ctx.request.body.files["file"].length;i++){
          fs.readFile(ctx.request.body.files['file'][i].path, function(err, sourceData) {
            if (err) throw err;
            tinify.fromBuffer(sourceData).toBuffer(function(err, resultData) {
              if (err) throw err;
              console.log(resultData);
              fs.writeFile(ctx.request.body.files['file'][i].path, resultData, function(err) { //压缩后覆盖原图
                 console.log('覆盖成功');
              });
            });
          });
         }
      }else{ //单图片
         fs.readFile(ctx.request.body.files['file'].path, function(err, sourceData) {
           if (err) throw err;
           tinify.fromBuffer(sourceData).toBuffer(function(err, resultData) {
              if (err) throw err;
              console.log(resultData);
              fs.writeFile(ctx.request.body.files['file'].path, resultData, function(err) { //压缩后覆盖原图
                 console.log('覆盖成功');
              });
           });
          });
        }
    
       ctx.body =ctx.request.body;
    });

    启动服务器仅执行一次

    console.log('只会执行一次');
    
    app.use(async function (ctx,next) {
      await next();
    });

    只要不把代码写在中间件里面,只有koa启动时执行一次

    文本编辑器wangeditor2

    http://www.kancloud.cn/wangfupeng/wangeditor2/113964

    https://github.com/wangfupeng1988/wangEditor

    加载必要插件:

    https://github.com/wangfupeng1988/wangEditor/tree/master/test/plupload/lib/plupload

    <!--引入wangEditor.css-->
    <link rel="stylesheet" type="text/css" href="../dist/css/wangEditor.min.css">
    
    <div id="div1">
     <p>请输入内容...</p>
    </div>
    <!--引入jquery和wangEditor.js-->   <!--注意:javascript必须放在body最后,否则可能会出现问题-->
    <script type="text/javascript" src="../dist/js/lib/jquery-1.10.2.min.js"></script>
    <script type="text/javascript" src="lib/plupload/plupload.full.min.js"></script>
    <script type="text/javascript" src="../dist/js/wangEditor.min.js"></script>
    <script type="text/javascript"> 
    function printLog(title, info) {
     window.console && console.log(title, info);
    }
    
    // ------- 配置上传的初始化事件 -------
    function uploadInit () {
     // this 即 editor 对象
     var editor = this;
     // 编辑器中,触发选择图片的按钮的id
     var btnId = editor.customUploadBtnId;
     // 编辑器中,触发选择图片的按钮的父元素的id
     var containerId = editor.customUploadContainerId;
    
     //实例化一个上传对象
     var uploader = new plupload.Uploader({
     browse_button: btnId, // 选择文件的按钮的id
     url: '/admin/upload', // 服务器端的上传地址
     flash_swf_url: '/plupload/lib/plupload/Moxie.swf',
     sliverlight_xap_url: '/plupload/lib/plupload/Moxie.xap',
     filters: {
     mime_types: [
     //只允许上传图片文件 (注意,extensions中,逗号后面不要加空格)
     { title: "图片文件", extensions: "jpg,gif,png,bmp" }
     ]
     }
     });
    
     //存储所有图片的url地址
     var urls = [];
    
     //初始化
     uploader.init();
    
     //绑定文件添加到队列的事件
     uploader.bind('FilesAdded', function (uploader, files) {
     //显示添加进来的文件名
     $.each(files, function(key, value){
     printLog('添加文件' + value.id);
     });
    
     // 文件添加之后,开始执行上传
     uploader.start();
     });
    
     //单个文件上传之后
     uploader.bind('FileUploaded', function (uploader, file, responseObject) {
     //注意,要从服务器返回图片的url地址,否则上传的图片无法显示在编辑器中
     var url = responseObject.response;
     //先将url地址存储来,待所有图片都上传完了,再统一处理
     urls.push(url);
    
     printLog('一个图片上传完成,返回的url是' + url);
     });
    
     //全部文件上传时候
     uploader.bind('UploadComplete', function (uploader, files) {
     printLog('所有图片上传完成');
    
     // 用 try catch 兼容IE低版本的异常情况
     try {
     //打印出所有图片的url地址
     console.log(urls)
     $.each(urls, function (key, value) {
     printLog('即将插入图片' + value);
    
     // 插入到编辑器中
     //editor.command(null, 'insertHtml', '<img src="' + value + '" style="max-width:100%;"/>');
     editor.$txt.append('<img src="' + value + '" style="max-width:100%;"/>');
     });
     } catch (ex) {
     // 此处可不写代码
     } finally {
     //清空url数组
     urls = [];
    
     // 隐藏进度条
     editor.hideUploadProgress();
     }
     });
    
     // 上传进度条
     uploader.bind('UploadProgress', function (uploader, file) {
     // 显示进度条
     editor.showUploadProgress(file.percent);
     });
    }
    
    
    // ------- 创建编辑器 -------
    var editor = new wangEditor('div1');
    editor.config.customUpload = true; // 配置自定义上传的开关
    editor.config.customUploadInit = uploadInit; // 配置上传事件,uploadInit方法已经在上面定义了
    editor.create();
    </script>

    admin/upload页面

    //编辑器中的上传,支持单、多文件上传
    router.post('/upload',
     koaBody({
     multipart: true,
     formidable: {
     uploadDir:path.join(process.cwd(), '/public/images')
     }
     }),
     async function (ctx,next) {
    
     //console.log(ctx.request.body.fields);
     // => {username: ""} - if empty
     //console.log( ((((ctx.request.body.files["file"])).name).split('.'))[(((((ctx.request.body.files["file"])).name).split('.')).length-1)] );
     //console.log( (((ctx.request.body.files)["uploads"])).path );
     // console.log( (((((ctx.request.body.files)["uploads"])).name).split('.'))[(((((ctx.request.body.files)["uploads"])).name).split('.')).length-1] );
     /* => {uploads: [
     {
     "size": 748831,
     "path": "/tmp/f7777b4269bf6e64518f96248537c0ab.png",
     "name": "some-image.png",
     "type": "image/png",
     "mtime": "2014-06-17T11:08:52.816Z"
     },
     {
     "size": 379749,
     "path": "/tmp/83b8cf0524529482d2f8b5d0852f49bf.jpeg",
     "name": "nodejs_rulz.jpeg",
     "type": "image/jpeg",
     "mtime": "2014-06-17T11:08:52.830Z"
     }
     ]}
     */
     // ctx.body = JSON.stringify(ctx.request.body, null, 2)
    
     await new Promise((a,b)=>{
        cox.body='http://www.xxx.com/1.jpg'
     })
     }
    )

    验证码

    https://sdeno.com/?p=5425

    加密

    var crypto = require('crypto');  //node.js内置
    module.exports = function(pwd) {
      var hash = crypto.createHash('sha256').update(pwd).digest('base64');
      return hash;
    };

    验证token

    node.js实现token身份验证

    手机页面

    使用pc端或者手机端访问可以智能判断切换页面,实现方法有2种:

    1,在客户端顶部添加js来判断pc、手机端来实现域名跳转,来访问不同页面。好处就是让服务器减轻负担,坏处就是域名发生跳转。

    2,在后台判断来实现加载pc、手机端模版,好处是域名始终不变。坏处就是,稍微给服务器添加一些负担。

    npm install koa2-useragent --save

    //编辑app.js

    const Koa = require('koa');
    .
    .
    .
    import userAgent from 'koa2-useragent';
    app.use(userAgent());

    //路由

    router.get('/', async function (ctx, next) {
        console.log(ctx.userAgent);
        .
        .
        .
        if(ctx.userAgent.isMobile){  //客户端不同,应用不同模板
             await ctx.render('mobile_index.ejs', {
             });
        }else{
             await ctx.render('home_index', {
             });
        }
    })

    https://www.npmjs.com/package/koa2-useragent

    子进程
    一些繁重的业务逻辑给子线程去做然后传回给主线程,防止主线程挂壁

    //父亲

    var childProcess = require('child_process');
    .
    .
    router.get('/',async (ctx, next) => {
      await new Promise((a,b)=>{
        childProcess.fork(process.cwd()+'/routes/child.js',[]).on('message', function(m) {
           console.log('儿子发来的信息: ', m); //此处是,老爸接受到儿子发来的信息
           a();
        }).send(ctx); //把ctx传给儿子
      });
    
      await ctx.render('index', Object.assign({
        title:'55'
      },pubObj));
    });
    //child.js 儿子 (子进程)
    ;(async function () {
      process.on('message', function(ctx) {
        ctx.xxx=123; // ctx 是老爸传来的数据,也可以自己添加额外数据传回去
        process.send({ Hello: '老爸我是儿子' }); //向老爸发送信息
        process.exit(0); //儿子发数据给老爸后,任务完成退出。
      });
    })();

    推荐看看  www.xgllseo.com/?p=5801

    数据缓存

    npm install node-cache --save

    准备:

    加载并实例化

    const NodeCache = require( "node-cache" );
    const myCache = new NodeCache(); //参数默认
    const myCache = new NodeCache( { stdTTL: 0, checkperiod: 600 } ); 
    
    //单值
    myCache.set( "myKey", "aa");//设置
    myCache.get( "myKey" ) //取值
    myCache.has("myKey") //判断值是否存在true存在,false不存在
    myCache.del( "myKey" ) //删除
    value = myCache.take( "myKey" )  //赋值给value变量并删除"myKey"
    
    //多值
    myCache.mset([
        {key: "myKey", val: obj, ttl: 10000},
        {key: "myKey2", val: obj2},
    ])
    myCache.mget( [ "myKeyA", "myKeyB" ] );
    myCache.del( [ "B", "C" ] )
    
    myCache.keys(); //获取当前所有变量名 [ "all", "my", "keys", "foo", "bar" ]
    
    //回调
    myCache.on( "set", function( key, value ){
        // ... do something ...
    });
    
    myCache.on( "del", function( key, value ){
        // 手动删除,或者值过期
    });
    
    myCache.on( "expired", function( key, value ){
        //值过期
    });

    https://www.npmjs.com/package/node-cache

    代理

    请求第三方接口,把数据渲染到前端

    安装所需模块:

    npm i request -S  //用来请求接口类似ajax
    npm install --save-dev koa2-proxy-middleware  //设置代理

    编辑app.js

    const proxy = require('koa2-proxy-middleware'); //加载
    const bodyparser = require('koa-bodyparser')
    .
    .
    .
    //参数设置
    const options = {
      targets: {
        '/api/(.*)': {
          target: 'http://testh3.xxx.com/',
          changeOrigin: true,
          pathRewrite: {
            '^/api': ''
          }
        },
      }
    }
    
    app.use(proxy(options)); //注册到koa中
    
    
    // bodyparser一定要放在代理后面不然会有问题
    app.use(bodyparser({  //解析post数据
      enableTypes:['json', 'form', 'text']
    }))

    路由案例:

    const router = require('koa-router')()
    const request = require('request');
    
    //注意/api/就相当于前缀,其实真是的接口没有,本地代理需要加上而已
    request.post('http://localhost:3000/api/wucg/cms/provinceCity/list', function optionalCallback(err, httpResponse, body) {
    
        router.get('/', async (ctx, next) => {
            await ctx.render('index', {
                title: body
            })
        })
    
    })

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    纯koa2模块介绍
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    以上的教程是基于koa-generator脚手架进行开发,安装的同时也大量安装了一些附属依赖模块让开发的更简单。koa-generator其实也就是以koa2为基础,依赖大量的模块进行组装,如果单纯的使用koa2来进行一步步开发又是如何的呢?

    首先安装

    npm install koa@next   //保证安装koa是2.x版本

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    搭建http

    //1.js
    const Koa = require('koa');
    const app = new Koa();
    
    app.listen(3000);

    执行node 1.js

    打开http://localhost:3000/ ,会看到”Not Found”,那是因为没有像客户端发送任何数据。

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    向客户端发送数据

    const Koa = require('koa');
    const app = new Koa();
    
    app.use((ctx)=>{
        ctx.response.body = '你好';
    });
    
    app.listen(3000);

    打开页面 http://localhost:3000/ ,可以看到显示内容“你好”,如果没有指定发送给客户端的数据类型默认是:text/plain

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    指定发送客户端的数据类型

    const Koa = require('koa');
    const app = new Koa();
    
    app.use((ctx)=>{
        ctx.response.type = 'html';
        ctx.response.body = '<html><head><title>你好</title></head><body><p>这里</p></body></html>';
    });
    
    app.listen(3000);

    其他常见类型有:xml、json、text

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    koa2原生路由

    app.use((ctx)=>{
        if(ctx.request.path == '/'){  //首页
            ctx.response.type = 'html';
            ctx.response.body = '<html><head><title>你好</title></head><body><p>首页</p></body></html>';
        }
        if(ctx.request.path == '/about'){   // http://localhost:3000/about
            ctx.response.type = 'html';
            ctx.response.body = '<html><head><title>你好</title></head><body><p>关于我们</p></body></html>';
        }
    });

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    静态资源

    //在二级目录public里面存放着css.css
    const Koa = require('koa');
    const app = new Koa();
    
    
    app.use(require('koa-static')(__dirname + '/public'));
    app.listen(3000);

    html:

    目录:public\js\jquery.min.js
    
    <script src="/js/jquery.min.js"></script>  //引入

    访问http://127.0.0.1:3000/css.css ,可以直接访问到内容

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    为静态资源加版本号

    <script src="<%- '/js/jquery.min.js?='+ver %>"></script>
    var pubObj={ver:'22.2'};  //公共对象,推荐放在app.js中
    
    router.get('/',async (ctx, next) => {
      await ctx.render('index', Object.assign({
         title:'首页'
      },pubObj));
    });

    推荐使用gulp、webpack类似工具添加版本号

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    302自动跳转、重定向

    const Koa = require('koa');
    const app = new Koa();
    
    
    app.use((ctx)=>{
        if(ctx.request.path == '/'){
            ctx.response.redirect('/about')  //访问首页时,会自动跳转到/about页面
            ctx.response.type = 'html';
            ctx.response.body = '<html><head><title>你好</title></head><body><p>首页</p></body></html>';
        }
        if(ctx.request.path == '/about'){
            ctx.response.type = 'html';
            ctx.response.body = '<html><head><title>你好</title></head><body><p>关于我们</p><form method="get" action="/ser"><input type="text" name="xxx1"><input type="submit" value="提交"></form></body></html>';
        }
    
    });
    
    app.listen(3000);

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    中间件,在客户端中将数据传递给后台接收时,的中间过程都会经历一层层的中间处理后才能将数据传递到后台。

    app.use() //就是专门加载中间件的,new require(‘koa’)().use

    const Koa = require('koa');
    const app = new Koa();
    
    // 以下执行顺序是同步执行
    const one = (ctx, next) => {
        console.log('>> one');
        next();
        console.log('<< one');
    }
    
    const two = (ctx, next) => {
        console.log('>> two');
        next();
        console.log('<< two');
    }
    
    const three = (ctx, next) => {
        console.log('>> three');
        next();
        console.log('<< three');
    }
    
    app.use(one);
    app.use(two);
    app.use(three);
    
    app.listen(3000);
    -----------------
    结果:
    >> one
    >> two
    >> three
    << three
    << two
    << one
    
    
    const Koa = require('koa');
    const app = new Koa();
    
    
    const one = (ctx, next) => {
     console.log('>> one');
     next();
     console.log('<< one');
    }
    
    const two = (ctx, next) => {
     console.log('>> two');
                            //next()  删除
     console.log('<< two');
    }
    
    const three = (ctx, next) => {
     console.log('>> three');
     next();
     console.log('<< three');
    }
    
    app.use(one);
    app.use(two);
    app.use(three);
    
    app.listen(3000);
    -----------------
    结果:
    >> one
    >> two
    << two
    << one
    

    所以说,要保证所有的中间件能运行的到,就要使用next()进行串联

    中间件,异步执行

    const one =async (ctx, next) => {
       await console.log('>> one1');
        next();
        await console.log('<< one2');
    }
    
    const two =async (ctx, next) => {
        await console.log('>> two3');
        next();
        await console.log('<< two4');
    }
    
    const three =async (ctx, next) => {
        await console.log('>> three5');
        next();
        await console.log('<< three6');
    }

    执行结果完全打乱

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    合并中间件,如果觉得app.use()使用的频率过高,可以使用koa-compose

    npm install koa-compose

    案例:

    const middlewares = compose([one, two,three]);
    app.use(middlewares);

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    500 错误

    const main = ctx => {
     ctx.throw(500);
    };

    404错误

    const main = ctx => {
     ctx.response.status = 404;   //就相当于ctx.throw(404)
     ctx.response.body = 'Page Not Found';
    };

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    错误的统一处理

    为了方便处理错误,最好使用try…catch将其捕获。但是,为每个中间件都写try…catch太麻烦,我们可以让最外层的中间件,负责所有中间件的错误处理。

    const handler = async (ctx, next) => {
      try {
        await next();
      } catch (err) {  //报错执行此处
        ctx.response.status = err.statusCode || err.status || 500;
        ctx.response.body = {
          message: err.message
        };
      }
    };
    
    const main = ctx => {
      ctx.throw(500);
    };
    
    app.use(handler);
    app.use(main);
    

    类似效果,对错误后的处理

    const main = ctx => {
     ctx.throw(500);
    };
    
    app.on('error', (err, ctx) =>
     console.error('server error', err);
    );

    注意:同时使用try…catch 和 app.on(‘error’,fn),监听error事件不会被触发,除非释放错误事件,例如:

    const handler = async (ctx, next) => {
     try {
       await next();
     } catch (err) {
       ctx.response.status = err.statusCode || err.status || 500;
       ctx.response.type = 'html';
       ctx.response.body = '<p>Something wrong, please contact administrator.</p>';
       ctx.app.emit('error', err, ctx);  //释放错误app.on('error',fn)才能被执行
     }
    };
    
    const main = ctx => {
       ctx.throw(500);
    };
    
    app.on('error', function(err) {
       console.log('logging error ', err.message);
       console.log(err);
    });

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    cookies

    const main = function(ctx) {
     ctx.cookies.set('view', '187'); //设置
     ctx.response.body = ctx.cookies.get('view'); //取值
    }

  • npm link创建全局链接

    举个例子,我们已经通过 全局安装npm install -g express 安装express

    可以直接使用命令express -v。

    如果在我们项目工作目录中要想引用express就必须再次本地安装npm install express,这显然不太科学。我们可以这么做:

    1,首先切换到你的项目根目录下

    2,查看你当前node.js默认把模块全局安装到哪个路径下,

    npm root -g

    3,执行:

    npm link express ./node_modules/express -> /usr/local/lib/node_modules/express
    npm link express ./node_modules/express -> 全局目录

    之后就多出了一个express像快捷键的目录,通过这种方法,我们就可以把全局包当本地包来使用了。

  • js好用的评分插件

    js 星星 评分 星星
    start123

     

    代码风格:

    $('.demo-live-application').score({
     number : 10,
     size : 32,
     color : '#08C',
     score : 4,
     vertical : false,
     hints : ['bad', 'poor', 'regular', 'good', 'gorgeous'],
     click : function(score, event){
     alert('Class Name: '+this.className+'\n' + 'Score: '+score+'\n' + 'Event Type: '+event.type+'\n');
     },
     readOnly : false,
     fontAwesome : true,
     debug : true
    });

     

    http://haozki.me/score.js/

    下载:https://github.com/haozki/score.js/zipball/master