2015年9月

Laravel 的 Session机制简介

前些天,为了解答一个问题,就去研究了Laravel的源码,讲讲我的收获:
这个是问题源:
http://segmentfault.com/q/1010000003776645?_ea=365137

本文讲的Laravel全部使用5.1版本。

我们首先看Laravel是如何创建Session组件的。
首先我们可以看见在Kernel.php中注册了StartSession这个类(这里不去讨论Laravel的DI和IoC),看下这个类是如何使用的。

Session Start

我们在这个类中的handle方法看到如下代码

public function handle($request, Closure $next)
{
    $this->sessionHandled = true;

    // If a session driver has been configured, we will need to start the session here
    // so that the data is ready for an application. Note that the Laravel sessions
    // do not make use of PHP "native" sessions in any way since they are crappy.
    if ($this->sessionConfigured()) {
        $session = $this->startSession($request);

        $request->setSession($session);
    }

    $response = $next($request);

    // Again, if the session has been configured we will need to close out the session
    // so that the attributes may be persisted to some storage medium. We will also
    // add the session identifier cookie to the application response headers now.
    if ($this->sessionConfigured()) {
        $this->storeCurrentUrl($request, $session);

        $this->collectGarbage($session);

        $this->addCookieToResponse($response, $session);
    }

    return $response;
}

OK,一个典型的过滤器,在这个StartSession中获取Session的方法是getSession

Get Session

它使用了Laravel注入的SessionManager 这个类的完整限定名是:\Illuminate\Session\SessionManager

/**
 * Start the session for the given request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Session\SessionInterface
 */
protected function startSession(Request $request)
{
    with($session = $this->getSession($request))->setRequestOnHandler($request);

    $session->start();

    return $session;
}

/**
 * Get the session implementation from the manager.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Session\SessionInterface
 */
public function getSession(Request $request)
{
    $session = $this->manager->driver();
    $session->setId($request->cookies->get($session->getName()));

    return $session;
}

我们在这个函数中看到,它调用了SessionManagerdrive方法,drive方法是SessionManager的父类Manager中定义的,看到它的实现是这样的

/**
 * Get a driver instance.
 *
 * @param  string  $driver
 * @return mixed
 */
public function driver($driver = null)
{
    $driver = $driver ?: $this->getDefaultDriver();

    // If the given driver has not been created before, we will create the instances
    // here and cache it so we can return it next time very quickly. If there is
    // already a driver created by this name, we'll just return that instance.
    if (! isset($this->drivers[$driver])) {
        $this->drivers[$driver] = $this->createDriver($driver);
    }

    return $this->drivers[$driver];
}

/**
 * Create a new driver instance.
 *
 * @param  string  $driver
 * @return mixed
 *
 * @throws \InvalidArgumentException
 */
protected function createDriver($driver)
{
    $method = 'create'.ucfirst($driver).'Driver';

    // We'll check to see if a creator method exists for the given driver. If not we
    // will check for a custom driver creator, which allows developers to create
    // drivers using their own customized driver creator Closure to create it.
    if (isset($this->customCreators[$driver])) {
        return $this->callCustomCreator($driver);
    } elseif (method_exists($this, $method)) {
        return $this->$method();
    }

    throw new InvalidArgumentException("Driver [$driver] not supported.");
}

也就是调用getDefaultDriver方法

/**
 * Get the default session driver name.
 *
 * @return string
 */
public function getDefaultDriver()
{
    return $this->app['config']['session.driver'];
}

这里就是我们在app/config/session.php中定义的driver字段,获取这个字段后,我们可以从createDriver的源码看到,它实际上是调用createXXXXDriver的方式获取到driver的。

OK,我们看下最简单的File,也就是createFileDriver

/**
 * Create an instance of the file session driver.
 *
 * @return \Illuminate\Session\Store
 */
protected function createFileDriver()
{
    return $this->createNativeDriver();
}

/**
 * Create an instance of the file session driver.
 *
 * @return \Illuminate\Session\Store
 */
protected function createNativeDriver()
{
    $path = $this->app['config']['session.files'];

    return $this->buildSession(new FileSessionHandler($this->app['files'], $path));
}

/**
 * Build the session instance.
 *
 * @param  \SessionHandlerInterface  $handler
 * @return \Illuminate\Session\Store
 */
protected function buildSession($handler)
{
    if ($this->app['config']['session.encrypt']) {
        return new EncryptedStore(
            $this->app['config']['session.cookie'], $handler, $this->app['encrypter']
        );
    } else {
        return new Store($this->app['config']['session.cookie'], $handler);
    }
}

这个Store就是我们Session的实体了,它的具体读写调用使用抽象的Handler进行,也就是说如果是读文件,就new FileHandler如果是Redis就是new RedisHandler实现readwrite即可。

Session ID

回到StartSession 这个类,我们再看getSession这个方法

 $session->setId($request->cookies->get($session->getName()));

了解Web的人都应该知道,session是根据cookie中存的key来区分不同的会话的,所以sessionId尤为重要,这里我们先不关心Cookie是如何读取,我们知道在这里读取了cookie中存放的SessionId就够了。

接下去看怎么加载数据

Data

依然是StartSession这个类,
我们看startSession这个方法,下一句调用的就是$session->start();,这句话是干嘛呢?经过前面的分析,我们知道$session已经是Store对象了,那么它的start方法如下:

public function start()
{
    $this->loadSession();

    if (! $this->has('_token')) {
        $this->regenerateToken();
    }

    return $this->started = true;
}

/**
 * Load the session data from the handler.
 *
 * @return void
 */
protected function loadSession()
{
    $this->attributes = array_merge($this->attributes, $this->readFromHandler());

    foreach (array_merge($this->bags, [$this->metaBag]) as $bag) {
        $this->initializeLocalBag($bag);

        $bag->initialize($this->bagData[$bag->getStorageKey()]);
    }
}

 /**
 * Read the session data from the handler.
 *
 * @return array
 */
protected function readFromHandler()
{
    $data = $this->handler->read($this->getId());

    if ($data) {
        $data = @unserialize($this->prepareForUnserialize($data));

        if ($data !== false && $data !== null && is_array($data)) {
            return $data;
        }
    }

    return [];
}

依次调用了loadSessionreadFromHandler 好,可以看到从handler中调用了read方法,这个方法就是从我们之前定义的各种handler中读取数据了,它是个抽象方法,在子类中具体实现,然后返回给Store(存入到Store中的attributes数组中去了,到此为止,Session的初始化方法都结束了,

public function get($name, $default = null)
{
    return Arr::get($this->attributes, $name, $default);
}

这个函数去读取Session中的数据了。

Summary

OK,Session整个初始化的过程总结下:

StartSession#handle -> StartSession#startSession -> StartSession#getSession -> SessionManager#getDefaultDriver -> SessionManager#createXXXHandler -> Store -> Store#setId -> Store#startSession

Laravel巧妙的使用了面向对象的接口方式,为我们提供了各种各样不同的存储方式,一旦我们了解了存储方式和加密规则,让不同的web容器进行Session共享的目的也可以达到~

Android 实现一个立方体旋转效果

好久不见~

今天我们来看看如何实现一个立方体翻转的效果。

如图

5dd54131gw1ewhzt3daq7g20f00qob2a.gif

看上去很麻烦,实际上实现起来还是蛮轻松的。
这里我们使用到的有两个类。

  1. android.graphic.Camera 这是在图像学概念里的摄像机,这是一个透视摄像机

  2. android.graphic.Matrix 矩阵,用来表示图像的变化。

头疼的钻研路开始

我们先从摄像头上的角度分析:
正常情况下,我们是这么看画面的(那个电池一样的东西就当是摄像头吧)

clipboard.png
我们要产生立方体的效果,那逻辑上应该是这么看:
clipboard.png

Camera提供了几个接口,我们这使用到的接口有两个:

  1. Rotate 旋转

  2. Translate 平移

这两个函数的操作都对画布的!
这里我们首先要有2个View。xml结构入下:

<cn.geminiwen.canvassupport.view.SplashLayout
        android:text="@string/hello_world"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#000" >
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#f00">
        </RelativeLayout>
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#0f0">
        </RelativeLayout>
</cn.geminiwen.canvassupport.view.SplashLayout>

SplashLayout就是我的自定义布局,用来绘制立方体效果的布局。

我们把第一个view作为backgroundView,第二个View作为foregroundView,使得效果是从backgroundView翻转到foregroundView

具体代码入下:

private void cube(Canvas canvas, double interpolation) {
        View foregroundView = getChildAt(0);
        View backgroundView = getChildAt(1);
        int width = getWidth();
        int height = getHeight();
        long drawingTime = getDrawingTime();
        float rotate;

        //begin drawForeground
        rotate = (float)(- sFinalDegree * interpolation);
        mCamera.save();
        mCamera.translate((float)(width - interpolation * width), 0, 0);
        mCamera.rotateY(rotate);
        mCamera.getMatrix(mMatrix);
        mCamera.restore();

        mMatrix.postTranslate(0, height / 2);
        mMatrix.preTranslate(-width, -height / 2);
        canvas.save();
        canvas.concat(mMatrix);
        drawChild(canvas, foregroundView, drawingTime);
        canvas.restore();
        //end drawForeground



        //draw Background
        rotate = (float)(sFinalDegree - sFinalDegree * interpolation);
        mCamera.save();
        mCamera.translate((float)(-width * interpolation), 0, 0);
        mCamera.rotateY(rotate);
        mCamera.getMatrix(mMatrix);
        mCamera.restore();

        mMatrix.postTranslate(width, height / 2);
        mMatrix.preTranslate(0, -height / 2);
        canvas.save();
        canvas.concat(mMatrix);
        drawChild(canvas, backgroundView, drawingTime);
        canvas.restore();
        //end draw Background

    }

这段代码放到ViewGroupdispatchDraw方法里即可,因为ViewGroup只能在dispatchDraw方法中绘制子视图。
其中,canvas代表画布,interpolation代表动画从0.0 到 1.0 的过程,方便插入器的使用。

这里来解释下我们的过程。

View状态变换

起始状态background是这样的:

  1. 绕Y轴正方向转90度

  2. 画布x轴移动到width的位置。

可以参照上图中的画布2的状态。

终点状态是这样:

  1. 绕Y轴正方向0度。

  2. 画布x轴移动到0的位置。

可以参考上图中的画布的状态。

旋转问题

综上所述,我们设置转动角度sFinalDegree为90。
interpolation从0到1的过程,
background的rotate就变成了从900的过程。

平移问题

这时候我们考虑平移的情况,这个情况会比较复杂,因为我们这里有两种平移方式,平移摄像机或者直接平移画布

这里我们说下区别,如果移动摄像机,会导致图像的投影发生变化,举个例子:
比如我们已经在投影上绕Y轴旋转90度,如果移动X轴的话,看如下图的区别:

1、未平移摄像机

clipboard.png

2、平移摄像机

clipboard.png
从图上我们知道,这个旋转过的画布的前端和后端我们都是可以看见的,这当然不符合我们要求,那么我们直接平移画布是什么意思呢?

我们知道对摄像机做了操作之后,应用到画布上,实际是画一个画布的投影,直接移动画布的话,就是改变其坐标系系统,达到效果,我们可以理解为同时对摄像机和view进行平移,最终达到的效果就是摄像头相对view的位置和1一样,但是我们的画布却平移了,这就达到了我们最终的要求。

我们看代码虽然我们平移的是画布,但是我们平移的过程中确是使用移动摄像机的方式来绘制投影,这又是为什么呢?

我们从三角函数的投影来解释这个问题。
首先看见我平移摄像头的方式是线性的,也就是y=kt这种方式,斜率一定,也就是随着时间变化,我平移的距离是线性增加的。那么考虑旋转的时候的投影情况:
被旋转的角就是角a,我们的画布长为width,那么画布的投影长度为
width * cos(a)
clipboard.png
它是一个三角函数。变化趋势先快后慢,因此我们在旋转过程中会看见右边露出背景,造成视觉上的不友好,怎么解决这个问题呢?

这时候就借助我们的摄像机平移的投影方式。

clipboard.png

这里绿色的线是我们的投影线,它的投影长度比cos(a) * width要长很多,因此它就可以让我们在旋转过程中不产生黑边,给人视觉上的饱满感,会让我们的视觉效果好很多。

我们的foregroundView就是一个backgroundView的逆向过程,因此使用类似的代码,然后假设interpolation是从1-0的过程即可,同时它的坐标系整体要往左移动一个屏幕。

总结

做UI的效果,特别需要一些比较好的数据基础,在图像处理中,搞清楚透视、矩阵的一些计算方式和概念非常重要,今天我们介绍了利用Camera来进行辅助我们进行矩阵的计算。

源码

demo已经放在github上
demo已经放在github上
demo已经放在github上
https://github.com/geminiwen/AndroidCubeDemo 欢迎star 欢迎fork!

编写Node原生模块

平常我们写node module的时候,都是直接用javascript去写,今天我们来学习下如何使用c/c++来写node模块,用c/c++写的优势就在于,你可以调用许多系统级的API,如fork,缺点就是它强平台依赖的,不一定能在所有平台下去运行。

写一个node addon一点都不可怕 * 3

我们用到的工具有2个

1.cmake-js 代替node-gyp,使用起来很方便。
2.nodejs源码(需要一些头文件和库)

cmake-js是使用CMake作为工具,构建跨平台的Makefile,极大方便了Makefile配置的一个js工具。

我们做一个Hello World
效果如

module.exports.hello = function() { return 'world'; };

废话不多,直接上代码

// hello.cc
#include <node.h>

namespace demo {    //此处的命名空间应该和你的模块名一致

using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));   //设置返回值
}

void init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "hello", Method);    //注册函数
}

NODE_MODULE(demo, init)    //注册模块

}  // namespace demo

比起普通的js代码,的确要复杂很多呀~

然后我们写好CMakeList.txt

cmake_minimum_required(VERSION 3.3)     #设定cmake的版本 
project(hello)                          #设定项目名称

include_directories(${CMAKE_JS_INC})    #加载由cmake-js提供的环境变量


set(SOURCE_FILES "hello_world.cc")      #设置我们需要编译的文件列表

add_library(hello SHARED exports.cpp ${SOURCE_FILES})  #标明我们要编译成一个lib,使用定义好的文件

set_target_properties(hello PROPERTIES PREFIX "" SUFFIX ".node") #设定编译出来的文件名(默认是libxxx.so,这里改成xxx.node)

好,在这个文件夹下面执行cmake-js,然后只要编译链接通过,在你的Build/Release下面就会出现hello.node

然后在你的js文件里进行测试

var hello = require("hello").hello;
console.log(hello());  //输出 world

哈,是不是很简单!
接下去的任务就是好好学习v8相关的API了,在C/C++环境下,要千万注意内存泄露的问题!

欢迎关注我Github 以及 @Gemini