作者: admin

  • hapi笔记/教程

    本教程基于hapi@13.5.3

    "dependencies": {
    "boom": "^3.2.2",
    "ejs": "^2.5.7",
    "good": "^6.6.3",
    "good-console": "^5.3.2",
    "hapi": "^13.5.3",
    "hapi-auth-cookie": "^6.1.1",
    "hapi-mobile-detect": "^1.3.5",
    "inert": "^4.2.1",
    "joi": "^8.4.2",
    "netmask": "^1.0.5",
    "vision": "^4.1.1"
    }
    

    hapi

    基础包:hapi_www

     

    教程基于npm install –save hapi@13.5.3

    1,搭建http

    var Hapi = require(‘hapi’);
    var server=new Hapi.Server();
    server.connection({port:4000});
    server.route([{
    method:’GET’,
    path:’/’,
    handler:function (request,reply) {
    reply({name:’234′});
    }
    }]);

     

    server.start((err)=>{
    console.log(‘正常’);
    });

     

    访问:http://127.0.0.1:4000/

     

     

     

    2,在shell窗口命令里面,实时监听http访问记录日志

     

    var Hapi = require(‘hapi’);
    var server=new Hapi.Server();
    server.connection({port:4000});
    server.route([{
    method:’GET’,
    path:’/’,
    handler:function (request,reply) {
    reply({name:’234′});
    }
    }]);

     

    //需要安装 npm install –save good@6 good-console@5
    server.register([{
    register:require(‘good’),
    options:{
    reporters:[{
    reporter:require(‘good-console’),
    events:{response:’*’}
    }]
    }
    }],(err)=>{
    server.start((err)=>{
    console.log(‘正常’);
    });
    });

     

     

     

    3,路由分离,(把一些路由独立出来)

     

    //route.js

    module.exports=[{
    method:’GET’,
    path:’/haha’,
    handler:function (request,reply) {
    reply(‘你好哈哈’);
    //this.db 这样可以找到数据库
    }
    }];

     

     

    //index.js

    var Hapi = require(‘hapi’);

    var Sqlite3=require(‘sqlite3’);
    var db1=new Sqlite3.Database(‘./db’)

    var server=new Hapi.Server();
    server.connection({port:4000});
    server.route([{
    method:’GET’,
    path:’/’,
    handler:function (request,reply) {
    reply({name:’234′});
    }
    }]);

     

    server.bind({db:db1}); //能保证引入的外来路由可以在handler里面的this.db找到数据库

    //调用:
    server.route(require(‘./route’));

     

     

     

    4,传参。将父页面的参数,传入给引入的子模块中

    server.bind({db:’数据’});
    server.route(require(‘./route’)); //在route.js中的handler里面可以使用this.db来得到数据

    //route.js
    module.exports=[{
    method:’GET’,
    path:’/’,
    handler:function (request,reply) {
    this.db //打印出:数据
    }
    }];

     

     

     

    5,ejs模板
    npm install ejs –save

    server.register([{
    register:require(‘good’),
    options:{
    reporters:[{
    reporter:require(‘good-console’),
    events:{response:’*’}
    }]
    }
    },
    require(‘inert’),
    require(‘vision’), //加载模板,可以使用server.views()方法
    ],(err)=>{

    server.views({ //配置模板
    engines:{
    ejs:require(‘ejs’) //npm install ejs –save
    },
    relativeTo:__dirname,
    path:’./views’,
    isCached:false //建议正式上线改为true
    });

    server.route(require(‘./route’));
    server.start((err)=>{
    console.log(‘正常’);
    });

     

    });

     

    在views目录中存放index.ejs
    内容: <%= title %>

     

    使用:
    handler:function (request,reply) {
    reply.view(‘index’,{
    title:request.params.id
    });
    }

     

     

     

     

    6,后台接收get、post数据

    接收get
    method:’GET’,
    path:’/test/{id}’,
    handler:function (request,reply) {
    reply.view(‘index’,{
    title:request.params.id
    });
    }

     

    接收post
    <form method=”post” action=”/post_test”>
    <input type=”text” name=”user”>
    <input type=”submit” value=”提交”>
    </form>

     

    method:’POST’,
    path:’/post_test’,
    config:{
    payload:{
    output:’data’
    }
    },
    handler:function (request,reply) {
    console.log(‘post数据’+request.payload.user);
    }

     

     

    7, cookies

    npm install –save hapi-auth-cookie@6

     

    server.register([{
    register:require(‘good’),
    options:{
    reporters:[{
    reporter:require(‘good-console’),
    events:{response:’*’}
    }]
    }
    },
    require(‘inert’),
    require(‘vision’),
    require(‘hapi-auth-cookie’), //加载cookies
    ],(err)=>{

    if(err){
    throw err;
    }

    //设置cookies
    server.auth.strategy(‘session’,’cookie’,’try’,{
    password:’password-that-is-at-least-32-chars’, //加密方式
    ttl: 24 * 3600 * 1000, //1天后过期,如果不设置一旦关闭页面cookies值就消失
    isSecure:false, //true时,只允许https使用
    });

     

    });

     

    设置:
    handler:function (request,reply) {
    request.cookieAuth.set({
    name:encodeURIComponent(request.params.id)
    });

    reply.view(‘index’,{
    title: request.params.id
    });
    }

     

    取值:
    request.auth.credentials.name

     

    清除:
    request.cookieAuth.clear();

     

    判断是否存在cookies:
    request.auth.isAuthenticated //返回布尔值

     

     

     

    8,页面跳转
    reply.redirect(‘/’);

     

     

    9,路由路径

    path:’/images/{id}’
    http://127.0.0.1/images/sdf

     

    //可选参数
    path:’/images/{id?}’
    http://127.0.0.1/images/sdf
    或者
    http://127.0.0.1/images/

     

    //通配符
    path:’/{id*}’
    这个路径是全能型匹配,静态资源就是靠通配符配置的

     

     

     

    10,服务器方法

    类似 把多个路由里面的公共方法抽出来独立成一个公共的方法。

    创建:
    server.method({
    name:’mean123′,
    method:function (values,next) {
    // var sum=values.reduce((a,b)=>{a+b});
    return next(null,values);
    },
    options:{}
    });

     

    调用:(要在路由里面的handler里面调用)
    handler:function (request,reply) {
    server.methods.mean123(JSON.parse(request.payload.user),(err,mean)=>{
    console.log(JSON.stringify(mean));
    return reply.view(‘index’,{
    title:{mean:JSON.stringify(mean)}[‘mean’]
    });
    });
    }

     

     

    11,同步(先决条件)

    server.route({
    method:’post’,
    path:’/post_test’,
    config:{
    payload:{
    output:’data’ //靠request.payload.属性名 来获取。如果<input name=”user”>那么request.payload.user
    },
    pre:[{
    assign:’xx’, //在handler里面靠request.pre.xx获取
    method:function (request,reply) { //这里比handler要先执行
    setTimeout(function () {
    reply(‘数据啊’); //要使用reply才能将数据返回给handler中的request.pre.xx
    },5000);

    // reply(‘跳过handler函数,直接将数据显示到浏览器’).takeover();

    }
    }]
    },
    handler:function (request,reply) { //一定要等pre的method执行完毕后,才轮到handler执行
    console.log(‘user:’+request.payload.user); //user是<input name=”user”>
    console.log(‘xx:’+request.pre.xx);
    reply.view(‘index’,{
    title:request.pre.xx
    });

    }
    });

     

     

     

     

     

     

     

    12,带有先决条件的服务器方法 (类似:多个方法同步排队执行)

     

    声明多个服务器方法:
    server.method([{
    name:’mean123′,
    method:function (request,reply) {
    setTimeout(function () {
    reply(‘数据123′);
    },5000);

    },
    options:{}
    },
    {
    name:’mean456’,
    method:function (request,reply) {
    console.log(request.pre.mean123); //可以获取到先执行完毕的服务器方法的数据
    reply(‘数据456′);
    },
    options:{}
    }
    ]);

     

    调用:
    server.route({
    method:’post’,
    path:’/post_test’,
    config:{
    payload:{
    output:’data’
    },
    pre:[‘mean123′,’mean456’] //同步,要等mean123的服务器方法执行完毕后,才轮到mean456执行
    },
    handler:function (request,reply) {
    reply.view(‘index’,{
    title:request.pre.mean123+’–‘+request.pre.mean456
    });
    }
    });

     

    异步:
    例如:
    pre:[
    [ //这两个服务器方法,属于异步执行,必须两个都完成,才能继续执行
    ‘abc’,
    ‘efg’
    ],
    ‘mean123’,
    ‘mean456’
    ]
    //这样的设计解决了,地狱回调的痛苦,而且数据可以共享

     

     

     

    13,上传

    var path=require(‘path’);
    var fs=require(‘fs’);

     

    html:
    <form method=”post” action=”/upload_page” enctype=”multipart/form-data”>
    <input type=”file” name=”upload”>
    <input type=”submit” value=”上传”>
    </form>

     

     

    路由:
    //上传
    server.route({
    method:’post’,
    path:’/upload_page’,
    config:{
    payload:{
    output:’stream’,
    parse:true,
    maxBytes:1048576 *4,//允许上传 4mb
    timeout:10000 //上传超时默认10秒
    },
    },
    handler:function (request,reply) {
    var upload=request.payload.upload;
    var uploadname=path.basename(request.payload.upload.hapi.filename);
    var des=path.join(__dirname,’uploads’,uploadname);
    upload.pipe(fs.createWriteStream(des));
    reply.view(‘index’,{
    title:’上传完成’
    });
    }
    });

     

    文件会上传存放在根目录的uploads目录下

     

     

     

    14,设置黑明单,禁止指定ip不能访问

    npm install –save boom@3 netmask@1.0.5

    var Boom = require(‘boom’);
    var Netmask = require(‘netmask’).Netmask;

     

    //黑名单
    var blacklist=[
    ‘127.0.0.0/8’
    ];

    //——————–
    var blockips=function (request,reply) {
    var ip=request.info.remoteAddress; //获取客户端ip
    for(let i=0;i<blacklist.length;i++){
    var block=new Netmask(blacklist[i]);
    if(block.contains(ip)){ //加!,blacklist就成了白名单
    console.log(‘禁止该’+ip+’ip访问’);
    return reply(Boom.forbidden()); //如果执行到这步就停止原来的生命周期,返回403
    }
    }

    reply.continue(); //继续其他的生命周期流程走

    }
    //——————–
    server.ext(‘onRequest’,blockips); //扩展点,把自定义好的功能插入到,hapi生命周期中执行(可以理解为,客户端访问任意页面时,总会执行此函数)

     

     

     

    15,扩展点
    要使用扩展点,就要了解客户端向服务器请求页面的过程,生命周期都有哪些,并在不同的生命周期的阶段中,插入扩展点来实现功能。

    可以参看14的案例

     

     

    16,客户端reply
    也就是负责把服务器返回来的信息显示到浏览器中

     

    handler:function(request,reply){
    reply(‘你好’); //reply() 返回的是一个response,并不是直接把字符串打印到浏览器上,所以在此过程中我们可以修改返回到浏览器上的数据类型

    }

     

    reply.view(‘index’,{
    title: ‘fff’
    })
    .type(‘text/plain’) //不解析html,直接把html代码输出到浏览器中
    .code(200)
    .header(‘x-powered-by’,’hapi’); //服务器的头信息,类似注释作

     

     

    server.ext(‘onPreResponse’,(request,reply)=>{ //onPreResponse一般能监听到 静态资源。如:images、css,或者type(‘text/plain’)的链接
    console.log(‘VVVVVVVVVVVVVVVVVVVV’);
    console.log(request.response.statusCode);
    console.log(request.response.headers);
    console.log(‘^^^^^^^^^^^^^^^^^^^^’);
    reply.continue();
    });

     

     

     

    17,创建路由

     

    独立创建一个路由:
    server.route({
    method:’get’,
    path:’/asf’,
    handler:function (request,reply) {
    reply(‘内容’);
    }
    });

    访问: http://127.0.0.1/asf

     

    多个路由:

    server.route([{
    method:’get’,
    path:’/asf’,
    handler:function (request,reply) {
    reply.view(‘index’,{
    title:’1′
    });
    }
    },
    {
    method:’get’,
    path:’/test’,
    handler:function (request,reply) {
    reply(‘继续’);
    }
    },
    {
    method:’POST’,
    path:’/post_test’,
    config:{
    payload:{
    output:’data’
    }
    },
    handler:function (request,reply) {
    reply.view(‘index’,{
    title:request.payload.user
    });
    }
    }
    ]);

     

    可以访问:
    http://127.0.0.1/asf
    http://127.0.0.1/test
    和提交数据到
    http://127.0.0.1/post_test

     

     

    18,错误页面 404、500

     

    var Boom = require(‘boom’);
    var Joi=require(‘joi’);

     

    html: error.ejs
    <p><%= statuscode %></p>
    <p><%= error_name %></p>

     

     

    server.route([
    {
    method:’get’,
    path:’/asf/{name}’,
    handler:function (request,reply) {
    return reply(‘ok’);
    },
    config:{
    validate:{
    params:{
    name:Joi.string().min(4) // 路由至少要大于等于4位
    }
    }
    }
    }
    ]);

     

     

    server.ext(‘onPreResponse’,(request,reply)=>{
    if(request.response.isBoom){
    var error_name= request.response.output.payload.error;
    var statuscode= request.response.output.payload.statusCode;
    return reply.view(‘error’,{
    statuscode:statuscode,
    error_name:error_name,
    title:’这里是错误页面’
    }).code(statuscode);
    }
    reply.continue();
    });

     

     

     

     

    19,验证

     

    hapi的内部验证推荐使用的joi模块,一旦过不了validate验证就不会执行到handler函数,直接报错Boom错误。

     

    npm install joi@8.4.2 –save

    var Joi=require(‘joi’); //推荐去了解更多joi用法

     

    (1)get数据提交验证 (验证路径参数):

    method:’get’,
    path:’/asf/{id}’,
    handler:function (request,reply) {
    reply.view(‘index’,{
    title:’sfsfsdfds’
    });
    },
    config:{
    validate:{
    params:{
    id:Joi.string().min(4) //必须是字符串而且要大于等于4位
    }
    }
    }

     

    (2)post数据提交验证

    method:’POST’,
    path:’/post_test’,
    config:{
    payload:{
    output:’data’
    },
    validate:{
    payload:{
    user:Joi.number() // user 是 <input type=”text” name=”user” /> 值必须是数字
    }
    }
    },

     

     

    (3)服务器传给客服端时,验证数据(比较适合做对外接口的数据验证,例如采集)
    method:’get’,
    path:’/asf’,
    handler:function (request,reply) {
    reply({title:123}); //输出到客户端的对象中,其中title必须时数字
    },
    config:{
    response:{
    schema:{
    title:Joi.number()
    }
    }
    }

     

     

    (4)当以get或者post方式传给后台的数据验证失败时,可以触发指定错误回调

    method:’get’,
    path:’/asf/{id}’,
    handler:function (request,reply) {
    reply.view(‘index’,{
    title:’sfsfsdfds’
    });
    },
    config:{
    validate:{
    params:{
    id:Joi.string().min(4) //必须是字符串而且要大于等于4位
    },
    failAction:function (request,reply,source,error) {
    reply(‘sdf’); //request,reply就是hanlder里面的
    //source 告诉我们是哪部分出错
    //error 输出一些错误信息
    }
    }
    }

     

     

     

    20,判断客户端是pc端还是手机端,选择加载不同的模版,域名不变

    npm install hapi-mobile-detect@1.3.5 –save

     

    const MobileDetect = require(‘hapi-mobile-detect’);

     

    handler:function (request,reply) {

    if(!!request.plugins.md.mobile()){
    reply.view(‘./mobile/index_mobile’,{
    title:’手机’
    });
    }else{
    reply.view(‘index’,{
    title:’pc’
    });
    }

    }

     

    ./views/index.ejs
    ./views/mobile/index_mobile.ejs

     

     

     

    21,插件(中间件)

    加载需要靠:server.register加载
    server.register([ //服务器启动时,仅仅加载一次,而且还是同步执行
    {register:require(‘./one_module’),
    options:{
    xx:’参数值,传个插件’
    }
    }
    ]);

     

     

    //one_module.js

    exports.register=function (server,options,next) {
    console.log(‘工作了’+options.xx); //工作了参数值,传个插件
    next();
    };

    exports.register.attributes={ pkg:require(“../package.json”) };

     

    如果插件依赖某个模块,可以使用:
    exports.register=function (server,options,next) {
    server.dependency([‘vision’,’good’],function (server,next) {
    console.log(‘adsfafas’);
    next();
    });
    next();
    };

    exports.register.attributes={ pkg:require(“../package.json”) };

     

     

     

     

    22,服务器静态数据

    var server=new Hapi.Server({
    app:{
    xx:’这里是自定义值’
    }
    });

     

     

    在路由中获取:

    handler:function (request,reply) {
    request.server.settings.app.xx
    },

     

     

     

    23,缓存

     

    客户端缓存:
    server.route({
    method:’GET’,
    path:’/{param*}’,
    handler:{
    directory:{
    path:’public’
    }
    },
    config:{
    cache:{
    privacy:’private’,
    expiresIn:86400*1000 //为静态资源缓存
    }
    }
    });

     

    同样也可以在路由器中配置,缓存页面。

     

     

  • 解决:1045 access denied for user ‘root’@’localhost’ using password yes

    mysql -u root -p
    use mysql;
    update mysql.user set authentication_string=password(‘密码’) where user=’root’ ;
    重启

  • 解决:ERROR 1130: Host xxx.xxx.xxx.xxx is not allowed to connect to this MySQL server

    GRANT ALL PRIVILEGES ON *.* TO ‘root’@’%’ IDENTIFIED BY ‘密码’ WITH GRANT OPTION;
    flush privileges;
    重启

  • 治疗梅尼埃

    这里想唠叨一下跟技术无关的话题。

    近段时间本人梅尼埃病轻微复发,整个人萎靡不振,精神恍惚,心情也很差。之前吃的药开始还有点效果,发现后面越吃越没有用,是药三分毒,后面干脆不吃了但是病依然存在。

    前面说的都是患此病的一些病人状态,虽然暂时对身体没什么影响,但是长期也会对生活和工作造成影响。这里我也是偶然间想到,之前吃的药(甲磺酸倍他司汀片),医生当时就通俗的跟我解析说让大脑的血管流通加快,可以起到暂时缓解该病。尽然是让大脑血管流通加快,我可以依靠运动提高身体的新城代谢一样也能起到同样的效果呀。

    每次左耳开始耳鸣就是病开始复发的时候,我都要跑上1公里的,事实证明依靠运动是可以暂时减缓治疗梅尼埃病的病情。

    希望那些跟我一样同病相怜的人看到我此文章,没生过病的人不知道一个拥有健康的身体是多么奢侈的事情,也希望你们也能恢复健康。