小程序网络请求header头设置Content-Type

小程序发起http网络请求,通过wx.request函数的调用。该函数接收一个对象,可以在对象里配置包括地址headermethod等。method默认为post请求。但是,我们在服务器端使用$_POST或框架如Phalcon里的$this->request->getPost()等获取参数时,取得结果为空。但是通过流获取:

$params = file_get_contents('php://input')

Phalcon里通过:

$params = $this->request->getJsonRawBody();

这样是能够得到参数的。为什么POST请求,却不能在POST里得到呢?我们注意看文档,会发现此时的headercontent-typeapplication/json(默认方式),再查看PHP的文档,可以看到:

当 HTTP POST 请求的 Content-Type 是 application/x-www-form-urlencoded 或 multipart/form-data 时,会将变量以关联数组形式传入当前脚本。

也就是说,我们需要重新设置小程序的header头里的content-typeapplication/x-www-form-urlencoded,就可以获取POST里的参数了。

在Phalcon中为View添加缓存

我们可以为PhalconView添加事件监听,View一共有5个事件:

Event Name Triggered Can stop operation ?
beforeRender Triggered before starting the render process Yes
beforeRenderView Triggered before rendering an existing view Yes
afterRenderView Triggered after rendering an existing view No
afterRender Triggered after completing the render process No
notFoundView Triggered when a view was not found No

di注入是view时添加事件:

<?php

use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Mvc\View;

$di->set(
    'view',
    function () {
        // Create an events manager
        $eventsManager = new EventsManager();

        // Attach a listener for type 'view'
        $eventsManager->attach(
            'view',
            function (Event $event, $view) {
                echo $event->getType(), ' - ', $view->getActiveRenderPath(), PHP_EOL;
            }
        );

        $view = new View();

        $view->setViewsDir('../app/views/');

        // Bind the eventsManager to the view component
        $view->setEventsManager($eventsManager);

        return $view;
    },
    true
);

上面是监听了View的所有事件,如果要监听某个具体的,如监听beforeRender

$eventsManager->attach('view:beforeRender', function(){

})

现在,就可以利用afterRender事件来缓存页面,提升性能。

首先,需要向di注入一个viewCache服务:

<?php

use Phalcon\Cache\Frontend\Output as OutputFrontend;
use Phalcon\Cache\Backend\Memcache as MemcacheBackend;

// Set the views cache service
$di->set(
    'viewCache',
    function () {
        // Cache data for one day by default
        $frontCache = new OutputFrontend(
            [
                'lifetime' => 86400,
            ]
        );

        // Memcached connection settings
        $cache = new MemcacheBackend(
            $frontCache,
            [
                'host' => 'localhost',
                'port' => '11211',
            ]
        );

        return $cache;
    }
);

这里的cache也可以换成Redis等你在使用的。lifetime为默认缓存时间,当调用该服务时,可以另外给出lifetime的值。键为keyPhalcon会默认使用MD5加密当前的Controller/view

定义afterRender事件触发时要做的事情:

class ViewPlugin extends \Phalcon\Mvc\User\Plugin {

    public function afterRender(\Phalcon\Events\Event $event, $view){
        $request    = \Phalcon\Di::getDefault()->get('request');
        $key        = md5($request->getURI());
        if (!$view->getCache()->exists($key)) {
            $view->getCache()->save($key, $view->getContent(), DEFAULT_VIEW_EXPIRE_TIME);
        }
    }
}

这样当访问一个新页面后就会缓存下来。接下来就是第二步,再次访问该页面时,直接向浏览器输出缓存内容,可以定义在父类里:

$this->view_cache_key = md5($this->request->getURI());
if ($this->view->getCache()->exists($this->view_cache_key)) {
    echo $this->view->getCache()->get($this->view_cache_key);
    exit;
}

这就是利用View的事件缓存页面,当然也可以用于其他场景,如生成静态HTML页面

参考文档:

Phalcon view

如何阻止表单自动填充?

默认情况下,我们再次填写提交过的表单时,就会出现浏览器已经自动帮我们填写好或者输入框获取到焦点时展示下拉选项供我们自己选择,这样方便我们快捷的完成表单。但是也有问题,如果是在公共电脑上,又或者是这个表单时关系到你的钱包的,那么你是不希望所输入的东西被浏览器自动记录或填充的。

  • 禁用自动填充

要禁用自动填充,可以通过属性autocomplete实现,它有两个值onoff

autocomplete="off"

该属性可以用在form标签上,也可以用在input标签上。加上上面的代码后,浏览器的处理如下:

1. 告诉浏览器不再保存用户输入的数据来自动填充表单
2. 浏览器停止缓存数据到会话历史(session history)。即:我们点击浏览器回退箭头时,之前提交的表单数据不会展示出来。
  • 阻止密码管理自动填充

上面这样就可以了吗?当我们登录、注册时,浏览器会弹出提示是否记住密码,如果选择记住密码,下次进入这个页面依然会自动完成。为什么?因为现在的浏览器都实现了集成密码管理。如果有浏览器账号,还会在任意一台你登录账号的电脑上自动填充。这样,登录表单设置autocomplete="off"就无效了。

对于现代浏览器这一特性,要阻止登录表单自动填充,autocomplete属性有了一个新的值new-password。当对input输入框设置时,便不再会自动填充了。

小程序的模板使用介绍

模板,即一部分业务代码片段,具有通用性。使用模板的好处,自然是方便维护。前端时间在做一个视频小程序时,便遇到了需要模板的场景。

这个小程序是个关于视频的项目,在首页,分类,搜索,个人中心(我发布的视频)都存在视频列表(页面包含列表),所以我打算将列表拿出来做个模板。

小程序定义模板语法:

<template name="yourTemplateName">
    <!--
        这里是页面布局的代码,例如:
        <view>
            <text>hello world!</text>
        </view>
    -->
</template>

定义好模板后保存为一个wxml的文件。

在需要使用模板的页面,首先引入模板

<import src="path/to/template/template.wxml" />

然后,在需要展示模板的地方使用模板

<template is="yourTemplateName" />

如果,模板里有变量,可以通过data传入

<template is="yourTemplateName" data="{{item}}"/>

如果有多个值,这样传入

<template is="yourTemplateName" data="{{name: theName, age: theAge, gender: theGender}}"/>

这是wxml模板的使用,与wxml文件相关联的还有两个文件wxss文件、js文件。wxss文件没有什么可说的,直接在需要的页面的wxss文件里使用import引入即可,这里说下js文件。

小程序每个页面都需要在js文件里使用Page函数注册页面,对于模板里的事件等一系列操作,我们不可能在每个地方都去写一遍。和定义模板一样,我们新建一个独立的js文件,内容如下:

export function functionName(params){
    #coding here
}

然后在需要的js页面引入

var lib = require(path/to/function);

var fn = lib.functionName;

这样,在Page函数里,我们就可以使用fn这个函数了。

如何在ios的微信webview里强制刷新

移动端网页webview中遇到了一个问题,A页面进入B页面做了一些操作,影响了A页面部分数据,再返回到A页面,受影响数据应该显示新数据。在android的手机里面使用自带虚拟返回按钮,不会存在这样的问题。但是在ios里面,使用自带返回按钮回到的界面并没有更新。这是为什么?原因是**bfcache(back-forward cache**)。详细描述看这里

解决方案链接的文章里也有说道,我这里采用的是监听pageonshow事件:

window.onpageshow = function(event) {
      if (event.persisted) {
            window.location.reload()
      }
};

MySQL导出数据到文件

从数据库导出数据的方式很多,如命令的mysqldump可以将整个数据库或某些表或某些数据导出。

整个数据库导出:

mysqldump [-h] -u root -p database>back.sql

某部分表导出:

mysqldump [-h] -u root -p database table1 table2 > back.sql

某些数据:

mysqldump [-h] -u root -p database table1 --where="id<100" > back.sql

上面的方式一般作为备份或数据迁移使用,自己想查看并不是那么直观,结果里都是些mysql命令,如何只将查询结果导出来呢?可以使用这个语句:

select * into OUTFILE '/path/to/file' from table where conditions

这里需要注意的是,如我们使用下面这样的语句:

select * into OUTFILE '/data/back/tmp/order.txt' from order where create_date='2017-03-27'

如果你已经将order.txt文件建好了,会报错说文件已经存在。那么说明文件不需要我们去建,于是删掉。再次运行命令可以又报错说没有写的权限,暴力点就给目录加上0777的权限就可以了。

MySQL如何支持emoji表情

emoji表情对于移动端来说,十分常见。可是,最近才发现我们项目还不支持emoji表情。数据库使用的字符集是UTF-8,WTF?还有UTF-8搞不定的?那该用什么?该用utf8mb4。那么,改吧。

要想支持emoji表情,首先数据库版本必须是5.5.3及其以上。如果你的数据库版本还没有达到要求,那么请升级。版本符合要求的,开始修改数据库、表、字段信息吧。

  • 数据库
ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

将要存储emoji表情的表修改如下:

ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  • 字段
ALTER TABLE table_name CHANGE column_name column_name VARCHAR(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

对于字段,除了字符集设置,其他信息字段类型,长度等请根据自己项目的实际情况设置。

使用Canvas压缩图片

为什么要前端压缩?

首先,图片在前端压缩是可以实现的。为什么要在前端压缩呢?有什么好处?在前端压缩会不会更耗时……对于这一系列的问题,可以很肯定的回答,前端压缩是需要的。尤其对于流量大的网站,可以节省很多带宽呢(都是RMB呀)。那么,怎么压缩呢?当然,这项工作就交给HTML5Javascript吧。有他俩搭档,很容易就能完成这件事情。

原理

当你在网上搜索*HTML5上传文件*时,你会找到很多拖拽的demo。不论是拖拽还是按钮上传,原理都这样:

  • 对事件(change,drop)的监听
  • 通过文件API–FileReader获取图片
  • 通过canvas绘制图片,在这里可以对图片进行缩放,大小压缩

实践

HTML代码:

<form>
<input type="file" id="uploadfile"/>
</form>

Javascript代码:

<script>
document.getElementById('uploadfile').addEventListener('change', function(event){
    //code for resize
}, false);
</script>

压缩代码

;(function ($,doc) {
    'use strict';

    var defaultOps = {
        url: null,
        quality: 40,
        resize: false,
        scale:{
            maxWidth: 640,
            maxHeight: null
        },
        output: null,
        formData: {},
        uploadProgress: function () {

        },
        uploadComplete: function () {

        },
        uploadFailed: function () {

        },
        uploadCanceled: function () {

        },
        uploading: function () {

        }
    };

    function _compload(evt, options) {
        var self = this;

        this.defaults = $.extend(true, defaultOps, options);
        if(!this.defaults.url){
            throw new Error('url is invalid');
        }

        this.afterSelect = function () {
            var file = evt.target.files[0],
                reg  = new RegExp('^image.*$'),
                type = this.defaults.output || file.type;

            if(!file){
                throw new Error('文件为空');
            }
            if(!reg.test(file.type)){
                throw new Error('只能上传图片');
            }

            var reader = new FileReader(),
                i      = doc.createElement('img');

            reader.onload = function (e) {
                i.src = e.target.result;
            }
            reader.readAsDataURL(file);

            i.onload = function (e) {
                var compressImg = self.compress(i, self.defaults.quality, type),
                    imgFile     = self.convert(compressImg.src);

                self.upload(imgFile);
            };
        }

        this.compress = function (source_img_obj, quality, outputType) {

            var cvs     = doc.createElement('canvas'),
                nWidth  = source_img_obj.naturalWidth,
                nHeight = source_img_obj.naturalHeight,
                sWidth  = self.defaults.scale.maxWidth,
                sHeight = self.defaults.scale.maxHeight,
                scale   = 1;

        if(self.defaults.resize){
                    if(sWidth && sHeight){
                        scale = Math.min(sWidth/nWidth, sHeight/nHeight);
                    }else if (sWidth && nWidth >= sWidth && !sHeight){
                        scale = sWidth/nWidth;
                    }else if (!sWidth && nHeight >= sHeight && sHeight){
                        scale = sHeight/nHeight;
                    }
                }

            cvs.width  = nWidth * scale;
            cvs.height = nHeight * scale;

            var ctx              = cvs.getContext("2d").drawImage(source_img_obj, 0, 0, cvs.width, cvs.height),
                newImageData     = cvs.toDataURL(outputType, quality/100),
                result_image_obj = new Image();

            result_image_obj.src = newImageData;

            return result_image_obj;
        }

        this.convert = function (img_src) {
            var BASE64_MARKER = ';base64,',
                dataURL = img_src;
            if (dataURL.indexOf(BASE64_MARKER) == -1) {
                var parts = dataURL.split(',');
                var contentType = parts[0].split(':')[1];
                var raw = parts[1];

                return new Blob([raw], {type: contentType});
            }

            var parts = dataURL.split(BASE64_MARKER);
            var contentType = parts[0].split(':')[1];
            var raw = window.atob(parts[1]);
            var rawLength = raw.length;

            var uInt8Array = new Uint8Array(rawLength);

            for (var i = 0; i < rawLength; ++i) {
                uInt8Array[i] = raw.charCodeAt(i);
            }

            return new Blob([uInt8Array], {type: contentType});
        }

        this.upload = function (file) {
            self.defaults.uploading();
            setTimeout(function () {
                var fd = new FormData();
                fd.append("file", file);
                for (var k in self.defaults.formData){
                    fd.append(k, self.defaults.formData[k]);
                }
                var xhr = new XMLHttpRequest();
                xhr.upload.addEventListener("progress", self.defaults.uploadProgress, false);
                xhr.addEventListener("load", self.defaults.uploadComplete, false);
                xhr.addEventListener("error", self.defaults.uploadFailed, false);
                xhr.addEventListener("abort", self.defaults.uploadCanceled, false);
                xhr.open("POST", self.defaults.url);
                xhr.send(fd);
            }, 10);
        }

        this.afterSelect();
    }

    window.Compload = _compload;
})(jQuery,document);

如何防止频繁调用Javascript一段代码?

应用场景
首先说下什么时候会用到,当我们给页面注册了scroll,resize等这类事件时,可能就需要用到了。为什么呢?比如,我们需要做一个页面滚动条滚动到底部时就自动加载下一页的功能,那么当滚动条滚动时就会频繁的触发scroll事件。为了减少js的执行,提升页面的性能,我们就需要滚动条在页面底部时才触发加载。

代码实现

function debounce(func, wait, immediate) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    }

Uncaught RangeError:Maximum call stack size exceeded

在做ajax交互时,出现了点击按钮毫无反应,初初以为是事件绑定问题,检查过代码过后确认应该是其他问题。后来chromeconsole控制台提示:

Uncaught RangeError:Maximum call stack size exceeded

于是,使用firefox developer版调试,console控制台报错:

TypeError: 'stepUp' called on an object that does not implement interface HTMLInputElement

终于找到了一个比较重要的错误提示,于是上Google搜索,在stackoverflow上找到问题,是因为$.ajaxdata属性里加入了html元素,即:

data = $('[name=name]').val()

后面的val()没有写导致的。