推荐一款非常强大的扒站工具 - 最骚的就是你 - 博客园

mikel阅读(24)

来源: 推荐一款非常强大的扒站工具 – 最骚的就是你 – 博客园

在平时的开发或者学习的过程中,我们难免会看到一些让人心动的网站,于是自己想把它弄下来,自己手工一个文件一个文件把它保存下来不太可能,那得累死你,下面我推荐一款扒站的神器,可以把你所喜欢的目标网站整个网站给扒下来,这个神器就是teleport ultra。下面简单介绍下使用教程。

下载与安装

首先到官网下载:http://www.tenmax.com/teleport/ultra/download.htm ,然后按照软件的安装指引完成安装

扒站步骤

1、打开软件

2、点击File,然后点击 New Project Wizred…,弹出如下界面,选择第一项,点击下一步

3、然后在输入框输入你要扒的网站的地址,点击下一步

4、选择everything,点击下一步,然后点击完成

5、选择本地保存网站源文件的路径后,点击保存

6、再单击start,即开始扒网站的文件

7、完成扒站

移动支持 – ASP.NET MVC 4 系列 - SkySoot - 博客园

mikel阅读(95)

来源: 移动支持 – ASP.NET MVC 4 系列 – SkySoot – 博客园

       目前,有各种各样的方法可以提高网站应用程序的移动体验。在某些情况下,我们只想在小规格上做一些微小的风格变化;另一些情况下,我们可能完全改变外观显示或者一些视图的内容;最极端的情况下,我们可能重新创建一个专门针对移动用户的 Web 应用程序。针对这些情况,MVC 4 提供了如下几种方案:

  • 适应性呈现:默认的 Internet 和 Intranet 应用程序模板使用 CSS 媒体查询(CSS media queries)来缩小到较小的移动规格(mobile form factors)。
  • 显示模式:MVC 4 采用了基于约定的方法,可以根据发出请求的浏览器选择不同的视图。与适应性不同,显示模式允许我们改变发往移动浏览器的标记!
  • Mobile Project 模板:这个新的项目模板帮助创建只针对移动设备使用的 Web 应用程序。

 

适应性呈现

       image   image

       MVC 4 默认模板在移动浏览器中做了很多处理,显而易见,页面智能的根据移动设备屏幕尺寸进行缩放,而不是简单的缩小页面尺寸(收缩文本和其他元素),为了能在移动设备上使用,页面进行了重绘。页面利用的是两个普遍支持的浏览器功能:

       Viewport 元标记

           基于浏览器嗅探或用户选择,Viewport 标记只在那些专门为小规格设计的页面中使用。这种情况下,我们按如下方式使用 Viewport 标记:

       <meta name=”viewport” content=”width=320″ />  // 不适用于大尺寸页面

           一个更好的解决方案是把我们的 CSS 扩展到各种规模,然后告诉浏览器支持任意设备!这很容易实现:

       <meta name=”viewport” content=”width=device-width” />

       CSS 媒体查询的自适应样式

           我们已经告诉浏览器页面足够智能,可以自动缩放到当前设备的屏幕尺寸。但如何兑现这一承诺呢?答案是 CSS 媒体查询。CSS 媒体查询允许我们在特定的媒体(显示)功能指定 CSS 规则,CSS 规则进行自上而下的评估,我们可以在 CSS 文件的顶部应用一般的规则,用专门在 CSS 中进行小规格显示的规则进行重写,并且媒体查询环绕这些规则,以使它们不能在大规格显示的浏览器中使用

 

       列举一个非常简单的示例,当宽度大于 850px 的屏幕上显示时,背景是蓝色,否则红色。CSS 如下:

body {
    background-color: blue;
}
 
@media only screen and (max-width:850px) {
    body {
        background-color: red;
    }
}

image

image

 

显示模式

       MVC 4 中的视图逻辑已经改变,添加了基于约定的替代视图。当浏览器用户代理显示是一个已知的移动设备时,默认的视图引擎首先查找名称以 .Mobile.cshtml 结尾的视图。例如,桌面浏览器请求主页时,应用程序使用 Views\Home\Index.cshtml 模板,而当移动浏览器请求主页时,程序就会使用 Views\Home\Index.Mobile.cshtml 模板。这些都由约定来处理,我们不必配置或注册

       (这里不方便测试就不贴图了,有兴趣的朋友可以下载移动浏览器模拟器来启动项目自行测试)

 

Mobile Project 模板

       如果需要创建只针对移动浏览器使用的应用程序,可以使用 Mobile Site 模板来创建新应用程序。该模版为我们创建的网站预先配置使用 JQuery Mobile 库,这个库为移动 Web 应用程序提供了大量增强功能:

  • 用户界面采用触摸优化的 UI 小部件
  • 为主要的移动浏览器而设计,并通过了测试
  • Ajax 导航提供了动画页面过渡,使其在低带宽的情况下,依然保持良好的性能
  • 通过主题支持,我们可以使用 CSS 主题重置整个网站的皮肤
  • 列表视图使用移动友好的接口,为浏览和操纵信息列表提供了很好的用户体验

image image image

       Mobile Project 模板可以开发强大的 JQuery Mobile ASP.NET MVC 应用程序,但这需要深入了解 jQuery Mobile。可以参考一些学习资料:

ASP.NET MVC Display Mode 移动端视图 配置对微信内置浏览器的识别 - Cameron - 博客园

mikel阅读(83)

来源: ASP.NET MVC Display Mode 移动端视图 配置对微信内置浏览器的识别 – Cameron – 博客园

最近在捣鼓一个稍微有点low的商城网站,没有计划做app却要求有个wap版,而前端又没有做成响应式,时间WTF,直接利用了ASP.NET mvc的Display Mode Provider

使用方式依照上面的链接地址,ASP.NET mvc application启动的时候会在全局变量 DisplayModeProvider.Instance.Modes 集合中加入 DisplayModeId == “Mobile” 的 IDisplayMode ,因此如果想要在移动端浏览器中展示移动视图只需要添加对应的以 .Mobile.cshtml 结尾的视图文件就可以了;可以通过chrome浏览器F12模拟移动端进行测试。

通过查看mvc源码发现IDisplayMode的默认实现为 DefaultDisplayMode ,它通过 HttpContext.GetOverriddenBrowser().IsMobileDevice 来判断是否为移动端,如果Controller需要针对pc端及mobile端做不同处理可以通过这种方式来判断,其他方式可能会造成两边不一致。

从GetOverriddenBrowser这个命名可以看出它的判断结果是可以通过某种方式改变的,也就是视图切换,实现如下:

复制代码
public RedirectResult SwitchView(bool mobile, string returnUrl)
{
    if (Request.Browser.IsMobileDevice == mobile)
        HttpContext.ClearOverriddenBrowser();
    else
        HttpContext.SetOverriddenBrowser(mobile ? BrowserOverride.Mobile : BrowserOverride.Desktop);

    return Redirect(returnUrl);
}
复制代码

在做的过程中我就在考虑,web server判断web client无非是通过传过去userAgent字符串,.net framework原生不可能准确判断每一种移动端设备,顶多预存了一些比较普遍的浏览器的userAgent;果不其然,在微信内置浏览器中打开呈现的是pc版的视图。再次定位到HttpContext.GetOverriddenBrowser().IsMobileDevice,其值在未被重写的时候是直接通过 Request.Browser.IsMobileDevice 得到的,它的结果由来是通过 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\Browsers 中的配置文件来决定的:

里面的配置是xml格式,通过命名可以大致猜出含义,我唯一不明白的是里面browser节点和gateway节点的区别;很奇怪,这一块的知识国内搜索引擎基本搜不到,fan墙google勉强找到一篇介绍这个的https://www.codeproject.com/Articles/1088703/How-to-detect-browsers-in-ASP-NET-with-browser-fil,写的比较详细,按照该作者对Edge浏览器的配置,写了一个wechat.browser:

复制代码
<browsers>
  <!-- mozilla/5.0 (linux; u; android 4.1.2; zh-cn; mi-one plus build/jzo54k) applewebkit/534.30 (khtml, like gecko) version/4.0 mobile safari/534.30 micromessenger/5.0.1.352  -->
  <browser id="WechatInAndroid" parentID="Chrome">
    <identification>
      <userAgent match="MicroMessenger" />
      <userAgent match="Safari" />
    </identification>

    <capabilities>
      <capability name="isMobileDevice"           value="true" />
      <capability name="mobileDeviceManufacturer" value="Google" />
      <capability name="mobileDeviceModel"        value="Android" />
      <capability name="canInitiateVoiceCall"     value="true" />
    </capabilities>
  </browser>
</browsers>
复制代码

wechat内置浏览器的userAgent比较另类,Android中都还有safari;其中MicroMessenger是它特有的,判断有这个就将isMobileDevice设置为true就行了。上面写的有点偷懒,都没有使用正则判断出版本号,也没有过滤桌面版微信~

在web项目中新建App_Browsers文件夹然后将wechat.browser即可生效。

The End.

原文地址:http://www.cnblogs.com/CameronWu/p/6764045.html

实时开发框架Meteor API解读系列 Meteor.methods_Keep Moving-CSDN博客

mikel阅读(83)

来源: 实时开发框架Meteor API解读系列 Meteor.methods_Keep Moving-CSDN博客

写在前面的话
该系列是零散的。 写到哪个是哪个 不会有个顺序。
尽量一篇博客一个API
尽量搭配一个代码实例 (因为时间比较零碎不一定能够保证)
每篇博文我会标记所使用的Meteor版本,防止因为API变化,导致博客的内容的正确性问题
能够看懂官方API就尽量看官方的,官方的才是最准确的。我这里只是提供一个参考
V 0.7.01

本篇文章涉及到的API:Meteor.methods,Meteor.call,Meteor.apply,Meteor.starup
Meteor.methods — Anywhere
关于 Meteor.methods 先看一下官方的简单描述

Defines functions that can be invoked over the network by clients.

大概意思就是 定义一些方法可以被客户端通过网络进行调用。注意下面的解释都是以这一条为基础来解释的。

ps:因为我初次看官方文档就糊涂了。不知道大家注意了没有,官方文档在每个API 后面有个标记,标记api的使用范围是在客户端(Client)使用还是服务端(Server)使用,亦或是同时都可以使用(Anywhere)。
在Meteor.methods后面的标识是Anywhere我的第一反应是服务端通过Meteor.methods定义的方法客户端可以调用,客户端通过Meteor.methods定义的方法服务端可以调用。其实如果稍微思考一下就会知道这样想是错误的。因为客户端有那么多,Meteor怎么知道到底使哪个客户端来执行这个方法呢? 但是这里的Anywhere是什么意思呢?稍后解释。

*强调 该API的作用也可以说它的主要目的是实现: 在服务端定义一组方法,使客户端能够调用。换句话说,通过这个API,客户端可以命令服务端做一些我们允许做的事情。
client 端定义的Meteor.methods叫做 ‘stub’(官方用的是这个单词) ,至于怎么翻译,看个人理解吧。这是下文需要用的一个描述。[我翻译成’存根’,理解为 对服务端方法调用的客户端补充动作。]
参数:Object
函数Meteor.methods的参数是一个object对象,这个对象里面的属性名就是供客户端调用的函数名,该属性对应的值就是共调用的函数。
例如:

{
“hello”:function(){..},
“welcome”:function(){..},
“bey”:function(){..}
}
如下使用:(服务端)

Meteor.methods({
“say”:function(name){
//do somthing
console.log(“Hello ” + name);
return “Hello boy”;
} ,
“draw”:function(…){
…..
},
…..
});
返回值:JSON对象(官方原文是: EJSON-able value)或者一个Meteor.Error对象

Meteor.Error 当后台处理到异常时,应该主动抛出的出错信息。这是一个Meteor自定义的一个异常对象。这个对象与本篇主题不相关,暂不做解释。可能一篇博客介绍它。
在被调用函数的内部 (如say对应函数的内部)存在一个上下对象 this。这个对象组成如下:

{
isSimulation: Boolean,// a boolean value, true if this invocation is a stub.
unblock: Function,//when called, allows the next method from this client to begin running.
userId: String,//the id of the current user.
setUserId: Function//a function that associates the current client with a user.
connection: Object//on the server, the connection this method call was received on.
}
根据官方的解释可能不是很i清楚,后面我们通过代码进行测试。

现在Meteor.methods的参数类型,和其中的函数应用已经讲,那么接着讲 如何来调用这些在Server端定义的内容。

有两个方法可以使用

1. Meteor.call
2. Meteor.apply
(个人认为:这两个方法的区别有点类似js的call与apply的区别)

Meteor.call — Anywhere
具体形式如下:

Meteor.call(“function name”,arg1,arg2,..,function(error,result){
//do something
});
function nanme 表示 在Meteor.methods中定义的对象的属性名,通过这个属性名来表示调用服务端对应的这个方法。

arg1,arg2,.. 是任意个参数,这个参数将作为服务端函数的参数。

注意这些参数仅为json或者一般数据类型,不能传递包含function对象
最后一个是回调函数。如果服务端函数抛出异常则 将赋值给error这个参数。
如果没有异常抛出,则服务端函数的返回值将赋值给result这个参数。

接下来看具体实现和代码。

meteor create api-001 #创建meteor应用
cd api-001 #进入应用目录
rm *.html *.css *.js #删除自动生成的.html,css,js文件
mkdir server #创建server文件夹
mkdir client #创建client文件夹
(关于文件夹的作用请参考其他人博客,或官方文档,我暂时还没有写。)
在server文件夹中新建一个js文件,如 main.js 内容如下:

Meteor.methods({
“test”:function(){
console.log(“Hello I’m Server”);
}
});
现在代码已经完成了,运行这个工程。
用浏览器(建议使用Chrome,该系列博客都将以此浏览器进行)打开http://localhost:3000 按F12 进入开发者模式 找到console控制台,输入:

Meteor.call(“test”)
这时在meteor启动的命令窗口将看到字符串输出。这就是Meteor.call的使用了。客户端调用服务端的方法。

(这里使用chrome控制台来完成相关测试是为了尽量保持博客的简洁性。同样的语句当然也可以写到客户端的js文件中,然后在html中引用,调用。效果是一样的,但是对于这篇博客来说 代码量就显的繁琐了。关于chrome的开发者模式的使用,请查找资料,或者查看我的博客如果你在看这篇博客时我已经写了的话)
最简单的demo已经了解过了,接下来看下它的返回值和参数的传递。
改动一下 main.js:

Meteor.methods({
//这里的参数可以是N个
“test”:function(a,b){
console.log(a + ” ” + b);
//这里使用的返回类型是json对象,当然可以使用基本数据类型,如string,number,bool等
return {
a:a,
b:b,
c:a+b
}
}
});
meteor自动重启。同样在chrome控制台输入:

Meteor.call(“test”,1,2,function(error,result){
//服务端抛出的异常将被这个回调函数的地一个参数接收,也就是error
//当服务端无异常抛出时,error为undefined
if(error){
console.log(“服务端异常”);
console.log(error);
return;
}
//服务端函数的返回值将被第二个参数接受,也就是result
console.log(result);
});
打印结果很明确就是服务端的返回值。

注意,因为Meteor.call可以在Server和Client同时使用,
在Server端运行Meteor.call时,最后一个参数不是回调函数的话,在可能的情况下服务端将同步执行这个方法体)验证代码如下 main.js:

var fs = Npm.require(‘fs’);
Meteor.methods({
“test”:function(a){
return a;
}
});

Meteor.startup(function(){
var s = Meteor.call(“test”,5);
console.log(s);
});

强调!这仅是在Server可以这样用,在Client如果要取到Server的返回值,必须使用回调函数才能获取到。
————————————
现在,下面的这个结论如果不能理解或者对你造成了迷惑请忽略它,因为它的实际作用不大。只是因为API中对它有个简单的提及,那么就用代码代码验证一下。
文档原文:
“On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method.”
原文就是上面这样了。不知道你思考了没有我上面提及到的这句话伸展:”在Client如果要取到Server的返回值”。 什么伸展呢?因为Meteor.methods在客户端也可以使用,那么如果Client 的Meteor.call调用Client的Meteor.methods中的函数 是不是也需要用回调函数呢? 结论是:不一定需要。
但是根据原文文档(原文没有直接提及,是根据”you do not pass a callback and you are not inside a stub”推断出来的,所以说这个结论实际作用不大),你得满足一个条件才行。条件是:你的Meteor.call必须在stub[前文已经解释过]中。(当然Client 的Metoer.methods本身不能涉及到异步调用。这个和Server端的同步是同样的样要求),而且在sub中使用Method.call 只会调用Client 的Meteor.methods中定义的方法,而Server的Meteor.methods不会执行。
官方文档提到了这个上面的这个情况。原文如下:
“Finally, if you are inside a stub on the client and call another method, the other method is not executed (no RPC is generated, nothing ‘real’ happens). If that other method has a stub, that stub stands in for the method and is executed.”

通过以下代码的运行就能看到:
Server/main.js

Meteor.methods({
hello:function(){
console.log(“server”);
return 10;
}
});

Client/client.js

Meteor.methods({
hello:function(){
console.log(“clent”);
return 12;
},
say:function(){
console.log(‘say’);
var hello = Meteor.call(“hello”);//Server下的hello 不会执行。
console.log(hello);//打印值是12
}
});

Meteor.apply — Anywhere
服务端代码不需要改动,在浏览器控制台输入:

Meteor.apply(“test”,[1,2],function(errror,result){
if(error){
//do something;
return;
}
// do somthing
console.log(result);
});
可以看到 Meteor.apply和Meteor.call 在一般用法 上基本没区别,仅是传递参数的方式不同而已,一个使用参数列表,一个使用参数数组。其实对于大多数 开发过程而言 掌握上述两种的使用已经能够满足开发需要了。因此对无更多要求的人而言,本篇博客可以到此为止。前文提到的 服务端函数 内部的上下文对象的使用 和 关于Meteor.methods的Anywhere 这两个还没解释的地方 可以忽略了。

Meteor.methods的Anywhere
大家细心一点可能发现Meteor.call,Meteor.apply同样是”Anywhere”.

先解释一下Server的call,apply和Client的call,apply的区别。

区别很简单:

Server:
仅能调用服务端的Meteor.methods中定义的函数
Client:
同时调用服务端和客户端的Meteor.methods中定义的函数。这个涉及到了Meteor.methods的Anywhere了。具体请往下看。
因为前面已经介绍了methods,call,apply的简单用法了。那么下面的代码就尽量简化不再描述这些了。

首先修改server的main.js。内容如下:

Meteor.methods({
“test”:function(a){
console.log(“server”);
return a+1;
}
});
//Meteor加载完所有文件后将执行这个方法。具体使用请参考官方文档,比较简单。
Meteor.startup(function(){
//然meteor启动后执行test方法
Meteor.call(“test”,1,function(err,result){
console.log(result);
});
});
另外,最开始的时候就新建了一个client文件,在该文件夹下 新建一个js文件,如client.js
内容如下:

Meteor.methods({
“test”:function(a){
console.log(“Client”);
return a+”aaa”;
}
});
代码基本就这样 然后重新运行meteor.

可以看到在Meteor.startup调用的Meteor.call只执行了Server下定义的test函数。

接着打开Chrome的控制台,输入:

Meteor.call(“test”,1,function(err,result){
console.log(result);
});
可以发现,客户端Meteor.methods和服务端Meteor.methods定义的test方法都执行了。而且在这个call函数的里回调函数的返回值 是服务端test的返回值,客户端test函数的返回值自动被舍弃了。

如果Client和Server同时定义了Meteor.methods 函数,在进行调用时,server 执行Meteor.call或Meteor.apply 只会使Server端的Meteor.methods中定义的函数执行
,而Client端执行的call和apply 会同调用客户端和服务端中Meteor.methods定义的函数。
且返回值都是以Server端的函数返回值,客户端的返回值将自动舍弃。其实这个结论自己多写几个代码就能够了解。
到这里 Meteor.methods的Anywhere不用再解释,应该也明白了。

Meteor.methods中函数的上下文对象 this
this中包含五个属性如下 (这些当需要用到时,能够想起有怎么个属性存在就可以了。到时候会自动知道该怎么使用)

{
isSimulation: Boolean,// a boolean value, true if this invocation is a stub.
unblock: Function,//when called, allows the next method from this client to begin running.
userId: String,//the id of the current user.
setUserId: Function//a function that associates the current client with a user.
connection: Object//on the server, the connection this method call was received on.
}
this.userId
调用这个函数的用户的id ,类型:string。如果用户没有登录,值为null。 该属性在 Server的Meteor.methods和Client的Meteor.methods都存在。用官方的描述就是Anywhere。 这个属性依赖Meteor的accounts 模块。这里不赘述。

this.setUseId 类型:Function,该setUerId函数的接收一个字符串或者null。使用范围:Server 设置当前链接的 用户。也就是说可以改变登录用户,或者使用户登录[如果this.userId为null],注销登录用户[this.setUserId(null)] .同样需要 Meteor的accounts模块的支持。

this.isSimulation 类型:Boolean 。使用范围:Anywhere
这个属性 我解释不好(如有好的理解请在博客下方留言),官方的解释是这样的:

Access inside a method invocation. Boolean value, true if this invocation is a stub.
[我理解意思是在Client的Meteor.methods中该值为true,Server中为false]。而且经过代码如下代码测试貌似和我理解的意思差不多 :

1. Server的call 或apply调用时,在Server的Meteor.methods中“`isSimulation“`为false

2. Client的call或apply调用时,
在Server的Meteor.methods中“`isSimulation“`为false,
在Client的Meteor.methods中“`isSimulation“`为true
测试代码:
client.js:

Meteor.methods({
“test”:function(a){
console.log(“Client”);
console.log(this.isSimulation); //false
return a+”aaa”;
}
});
main.js:

Meteor.methods({
“test”:function(a){
console.log(“server”);
console.log(this.isSimulation);//true
return a+1;
}
});

Meteor.startup(function(){
Meteor.call(“test”,1,function(err,result){
console.log(result);
});
});
4 this.unblock()
解释这个之前,我刚才得到了一个结论,一直没有注意到(没有使用过this.unblick()也没注意过它是否是阻塞的,写博客确实也是一种自我学习过程,当然这是题外话了。)的结论。默认情况下在Client调用Server端的函数这种过程是阻塞式的。意识就是说,如果有多个客户端同时调用test这个函数,而test这个函数的执行需要耗费一段,那么这个调用会进行排队依次执行。而就是只要当前面N次调用都完成以后,后面的调用才会进行。可以经过一下代码验证:
清空client.js的内容,不需要。 然后main.js的内容是:

var fs = Npm.require(‘fs’);
Meteor.methods({
“test”:function(a){
if(a==1){
//这里我通过多次读写文件的时间耗费进行阻塞
//,注意文件大小最好是在10M-40M之间,太小的文件,或者一般的计算可能执行速度太快看不到效果
for(var i=0;i<20;i++){
var data = fs.readFileSync(“/home/ec/download/NVIDIA”);
fs.writeFileSync(“/home/ec/download/test/NVIDIA”+i, data);
}
}
console.log(a);
}
});
在单个浏览器端,或者同时多个浏览器运行:(把命令写在一行)

Meteor.call(“test”,1); Meteor.call(“test”,2);
这样就可以看到阻塞的效果。[我猜大概这样是为了实现类似资源锁之类操作]

那么现在,我们不想它阻塞在这里也是很容易做的。没错!就是在函数的第一行执行:
(这里貌似存在一个bug 这个this.unblock()没有起作用 ,具体原因可参考 详情 和 解决方法)

this.unblock();
这是main.js内容如下:(参考解决过后的代码)

var fs = Npm.require(‘fs’);
var readFile = Meteor._wrapAsync(fs.readFile.bind(fs));
var writeFile = Meteor._wrapAsync(fs.writeFile.bind(fs));
Meteor.methods({
“test”:function(a){
this.unblock();
if(a==1){
//这里我通过多次读写文件的时间耗费进行阻塞
//,注意文件大小最好是在10M-40M之间,太小的文件,或者一般的计算可能执行速度太快看不到效果
var data = readFile(“/home/ec/download/NVIDIA”);
for(var i=0;i<5;i++){
writeFile(“/home/ec/download/test/NVIDIA”+i, data);
console.log(a+”-“+i);
}
}
console.log(a);
return a;
}
});
好了,this.unblock()的使用是这篇博客多耗费了2天时间。总算跳过去了。现在就剩最后一个点了。

this.connection Server
这是一个Connection对象,具体可以参考Server connections和这个部分的Meteor.onConnection(callback) 此属性不再本篇博客的讨论范围内。既然不讨论,那么就抄个官方文档的解释放在这里吧。

Access inside a method invocation. The connection this method was received on. null if the method is not associated with a connection, eg. a server initiated method call.

其中一个表达的大概意思是 如果没有通过http(非Client-Server或DDP)而是直接在Server中来调用这个methods里面的方法,那么connection是一个null。

本篇博客结束了。

对了最后其实还落下了一点点。那就是关于Meteor.apply(name, params [, options] [, asyncCallback])第三个可选参数options的解释了。
应该比较好理解 。同样贴上一个官方文档。

Options #仅在client端才有此参数
wait Boolean
(Client only) If true, don’t send this method until all previous method calls have completed, and don’t send any subsequent method calls until this one is completed.
onResultReceived Function
解释:如果为true,除非前面的方法调用都已经完成回调了,才会开始执行本次的函数调用。并且在本次函数调用完成之前 不会在执行其他任何的函数调用了。
(Client only) This callback is invoked with the error or result of the method (just like asyncCallback) as soon as the error or result is available. The local cache may not yet reflect the writes performed by the method.

这个的验证代码可以自己尝试写一下。就当是思考题吧。

转载请注明出处。谢谢!
————————————————
版权声明:本文为CSDN博主「ec_huyinghuan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a6383277/article/details/18098929/

Meteor应用的启动过程分析 - hoolev的个人页面 - OSCHINA - 中文开源技术交流社区

mikel阅读(87)

来源: Meteor应用的启动过程分析 – hoolev的个人页面 – OSCHINA – 中文开源技术交流社区

使用Meteor创建和运行一个应用是非常简单的,而简单的背后就是繁杂的细节。 我们希望通过分析源码,抽丝剥茧,来理解这简单背后的细节之美。

meteor v0.9.0.1

运行一个应用

首先我们得创建一个应用meteor create test,后面的代码分析都会用到这个应用。 在Meteor中只要在应用目录中执行meteor命令就可以运行这个应用了,应用正常运行之后会有如下打印:

[[[[[ ~/WCode/test ]]]]]
=> Started proxy.
=> Started your app. 
=> App running at: http://localhost:3000/

从上面的打印看Meteor先启动proxy、然后再启动app,但是在启动proxy之前应该还做了一些事的, 比如校验传入参数、获取环境变量、加载Package和Module等等。 根据上述推测,我们暂时把应用的启动过程分为四个步骤:环境配置、加载、启动proxy和启动app。

入口

当我们执行curl https://install.meteor.com/ | sh完成meteor安装之后, 系统中会出现一个/usr/local/bin/meteor可执行文件和一个~/.meteor/目录。 ~/.meteor/目录中包含了Meteor运行需要的所有脚本、包和模块。 /usr/local/bin/meteor这个Shell脚本做了两件事:

  • 检查Meteor是否成功安装,没有就重新安装一遍。
  • 运行~/.meteor/meteor

其实这个脚本执行了一次之后,就不需要再执行这个脚本了, 完全可以把~/.meteor/加入到$PATH中或者创建链接来直接执行~/.meteor/meteor

执行ls ~/.meteor/meteor -al这个命令就可以看到其实这只是个链接, 实际的文件是~/.meteor/packages/meteor-tool/1.0.26/meteor-tool-os.linux.x86_32/meteor

这也是个Shell脚本,也做了两件事:检查Meteor版本和运行exec "$DEV_BUNDLE/bin/node" "$METEOR" "$@" 其中DEV_BUNDLE="$SCRIPT_DIR/dev_bundle",METEOR="$SCRIPT_DIR/tools/main.js"$@是输入的命令和参数 而SRCIPT_DIR=~/.meteor/packages/meteor-tool/.1.0.26.13pjtg1++os.linux.x86_32+web.browser+web.cordova/meteor-tool-os.linux.x86_32/

因此,meteor运行的真正入口是main.js,这个文件在源码中位于tools目录下。

main.js

main.js中Fiber(function(){...}).run()类似于C语言中的main()函数,是所有函数的入口。 这个函数首先检查Node的版本和ROOT_URL,然后解析并校验传入的命令和参数,最后执行命令。 所有命令的实现在tools/command.js中,默认的命令是run。

JS和C#.NET获取客户端IP - 海角之上 - 博客园

mikel阅读(46)

来源: JS和C#.NET获取客户端IP – 海角之上 – 博客园

我们经常在项目中会遇到这种需要获取客户端真实IP的需求,其实在网上也能随便就能查到各种获取的方法,我也是在网上查了加上了自己的实践,说一下自己在实践后的感受,基本上网上大部分都是用JS的方法来获取客户端的IP,今天我也分享一种C# .NET可以获取到客户端IP的方法。

一、JS获取客户端IP

基本上在网上可以查到的以下几种方法:

(1)使用搜狐接口(适用所有平台及浏览器)

<script src="http://pv.sohu.com/cityjson?ie=utf-8"></script>  
<script type="text/javascript">  
document.write(returnCitySN["cip"]+','+returnCitySN["cname"])  
</script>

这种方式我有尝试过,第一天还好好的,第二天就不行了,后面就变成:在我本地电脑上直接运行是没有问题的,但是发布到服务器后就没有效果了。

(2)使用新浪接口(适用所有平台及浏览器)

复制代码
<script type="text/javascript" src="http://counter.sina.com.cn/ip/" charset="gb2312"></script>       <!--获取接口数据,注意charset -->
<script type="text/javascript"> 
document.writeln("IP地址:"+ILData[0]+"<br />");           //输出接口数据中的IP地址 
document.writeln("地址类型:"+ILData[1]+"<br />");         //输出接口数据中的IP地址的类型 
document.writeln("地址类型:"+ILData[2]+"<br />");         //输出接口数据中的IP地址的省市
document.writeln("地址类型:"+ILData[3]+"<br />");         //输出接口数据中的IP地址的
document.writeln("地址类型:"+ILData[4]+"<br />");         //输出接口数据中的IP地址的运营商
</script>
复制代码

这个地址说实话我都打不开,可能是慢。放弃了。

 

以上两种都是使用的第三方接口的方式,好像还有一些个人的接口,从我的实践来看,第三方接口不稳定,而且修改了接口可能会导致你的项目出错,其实是不可取的。

还有针对IE浏览器,使用AcitiveX插件方法,这种方法必需要让用户设置自己的IE浏览器允许运行AcitiveX插件才可行,而且Firefox和Chrome等浏览器不支持,显然也是不可取的。

综上,其实我是不推荐采用JS的方式来获取客户端IP的,下面介绍一种C# .NET的方式,也是我自己在使用的。

二、C# .NET方式获取客户端IP

直接贴代码

复制代码
string IP;
if (Request.ServerVariables["HTTP_VIA"] != null) // 服务器, using proxy
{
         //得到真实的客户端地址
         IP = Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();  // Return real client IP.
}
else //如果没有使用代理服务器或者得不到客户端的ip  not using proxy or can't get the Client IP
{
          //得到服务端的地址    
          IP = Request.ServerVariables["REMOTE_ADDR"].ToString(); //While it can't get the Client IP, it will return proxy IP.
}
复制代码

 

JS获取ip地址 - Goatherd - 博客园

mikel阅读(41)

来源: JS获取ip地址 – Goatherd – 博客园




微信公众号开发之语音消息识别 - 洛水3000 - 博客园

mikel阅读(83)

来源: 微信公众号开发之语音消息识别 – 洛水3000 – 博客园

1.开通语音识别(默认关闭)

2.语音识别

请注意,开通语音识别后,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个Recognition字段(注:由于客户端缓存,开发者开启或者关闭语音识别功能,对新关注者立刻生效,对已关注用户需要24小时生效。开发者可以重新关注此帐号进行测试)。开启语音识别后的语音XML数据包如下:

 

复制代码
  1 <?php
  2 /**
  3   * wechat php test
  4   */
  5 
  6 //define your token
  7 define("TOKEN", "weixin");
  8 $wechatObj = new wechatCallbackapiTest();
  9 //$wechatObj->valid();//接口验证
 10 $wechatObj->responseMsg();//调用回复消息方法
 11 class wechatCallbackapiTest
 12 {
 13     public function valid()
 14     {
 15         $echoStr = $_GET["echostr"];
 16 
 17         //valid signature , option
 18         if($this->checkSignature()){
 19             echo $echoStr;
 20             exit;
 21         }
 22     }
 23 
 24     public function responseMsg()
 25     {
 26         //get post data, May be due to the different environments
 27         $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
 28 
 29           //extract post data
 30         if (!empty($postStr)){
 31                 /* libxml_disable_entity_loader is to prevent XML eXternal Entity Injection,
 32                    the best way is to check the validity of xml by yourself */
 33                 libxml_disable_entity_loader(true);
 34                   $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
 35                 $fromUsername = $postObj->FromUserName;
 36                 $toUsername = $postObj->ToUserName;
 37                 $keyword = trim($postObj->Content);
 38                 $time = time();
 39                 $msgType = $postObj->MsgType;//消息类型
 40                 $event = $postObj->Event;//时间类型,subscribe(订阅)、unsubscribe(取消订阅)
 41                 
 42                 $textTpl = "<xml>
 43                             <ToUserName><![CDATA[%s]]></ToUserName>
 44                             <FromUserName><![CDATA[%s]]></FromUserName>
 45                             <CreateTime>%s</CreateTime>
 46                             <MsgType><![CDATA[%s]]></MsgType>
 47                             <Content><![CDATA[%s]]></Content>
 48                             <FuncFlag>0</FuncFlag>
 49                             </xml>"; 
 50                            
 51                 switch($msgType){
 52                     case "event":
 53                     if($event=="subscribe"){
 54                         $contentStr = "Hi,欢迎关注海仙日用百货!"."\n"."回复数字'1',了解店铺地址."."\n"."回复数字'2',了解商品种类.";
 55                     } 
 56                     break;
 57                     case "text"://文本消息
 58                         switch($keyword){
 59                             case "1":
 60                             $contentStr = "店铺地址:"."\n"."杭州市江干区.";    
 61                             break;
 62                             case "2":
 63                             $contentStr = "商品种类:"."\n"."杯子、碗、棉签、水桶、垃圾桶、洗碗巾(刷)、拖把、扫把、"
 64                                          ."衣架、粘钩、牙签、垃圾袋、保鲜袋(膜)、剪刀、水果刀、饭盒等.";
 65                             break;
 66                             default:
 67                             $contentStr = "对不起,你的内容我会稍后回复";
 68                         }
 69                     break;
 70                     case "voice"://语音消息
 71                     //语音识别
 72                     $recognition = $postObj->Recognition;
 73                     $format = $postObj->Format;
 74                     $contentStr = "你发送的是语音消息"."\n"."语音格式为:"."\n".$format."\n"."语音内容为:"."\n".$recognition;
 75                     break;
 76                 }
 77                 $msgType = "text";
 78                 $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr);
 79                 echo $resultStr;
 80         }else {
 81             echo "";
 82             exit;
 83         }
 84     }
 85         
 86     private function checkSignature()
 87     {
 88         // you must define TOKEN by yourself
 89         if (!defined("TOKEN")) {
 90             throw new Exception('TOKEN is not defined!');
 91         }
 92         
 93         $signature = $_GET["signature"];
 94         $timestamp = $_GET["timestamp"];
 95         $nonce = $_GET["nonce"];
 96                 
 97         $token = TOKEN;
 98         $tmpArr = array($token, $timestamp, $nonce);
 99         // use SORT_STRING rule
100         sort($tmpArr, SORT_STRING);
101         $tmpStr = implode( $tmpArr );
102         $tmpStr = sha1( $tmpStr );
103         
104         if( $tmpStr == $signature ){
105             return true;
106         }else{
107             return false;
108         }
109     }
110 }
111 
112 
113 ?>
复制代码

 

阿里官方Redis开发规范! - 知乎

mikel阅读(84)

来源: 阿里官方Redis开发规范! – 知乎

本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。

  • 键值设计
  • 命令使用
  • 客户端使用
  • 相关工具

通过本文的介绍可以减少使用Redis过程带来的问题。

一、键值设计

1、key名设计

可读性和可管理性

以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id

ugc:video:1

简洁性

保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:

user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}

不要包含特殊字符

反例:包含空格、换行、单双引号以及其他转义字符

2、value设计

拒绝bigkey

防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。反例:一个包含200万个元素的list。非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法

选择适合的数据类型

例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)

反例:

set user:1:name tom
set user:1:age 19
set user:1:favor football

正例:

hmset user:1 name tom age 19 favor football

控制key的生命周期

redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。

二、命令使用

1、O(N)命令关注N的数量

例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。

2、禁用命令

禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。

3、合理使用select

redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。

4、使用批量操作提高效率

  • 原生命令:例如mget、mset。
  • 非原生命令:可以使用pipeline提高效率。

但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。

注意两者不同:

  • 原生是原子操作,pipeline是非原子操作。
  • pipeline可以打包不同的命令,原生做不到
  • pipeline需要客户端和服务端同时支持。

5、不建议过多使用Redis事务功能

Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)

6、Redis集群版本在使用Lua上有特殊要求

1、所有key都应该由 KEYS 数组来传递,redis.call/pcall 里面调用的redis命令,key的位置,必须是KEYS array, 否则直接返回error,”-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS arrayrn”2、所有key,必须在1个slot上,否则直接返回error, “-ERR eval/evalsha command keys must in same slotrn”

7、monitor命令

必要情况下使用monitor命令时,要注意不要长时间使用。

三、客户端使用

1、避免多个应用使用一个Redis实例

不相干的业务拆分,公共数据做服务化。

2、使用连接池

可以有效控制连接,同时提高效率,标准使用方式:

执行命令如下
Jedis jedis = null;
try {
    jedis = jedisPool.getResource();
 //具体的命令
    jedis.executeCommand()
} catch (Exception e) {
    logger.error("op key {} error: " + e.getMessage(), key, e);
} finally {
 //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
 if (jedis != null)
        jedis.close();
}

3、熔断功能

高并发下建议客户端添加熔断功能(例如netflix hystrix)

4、合理的加密

设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)

5、淘汰策略

根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。

其他策略如下:

  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息”(error) OOM command not allowed when used memory”,此时Redis只响应读操作。

四、相关工具

1、数据同步

redis间数据同步可以使用:redis-port

2、big key搜索

redis大key搜索工具

3、热点key寻找

内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题

五、删除bigkey

  • 下面操作可以使用pipeline加速。
  • redis 4.0已经支持key的异步删除,欢迎使用。

1、Hash删除: hscan + hdel

public void delBigHash(String host, int port, String password, String bigHashKey) {
 Jedis jedis = new Jedis(host, port);
 if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
 ScanParams scanParams = new ScanParams().count(100);
 String cursor = "0";
 do {
 ScanResult<Entry<String, String>> scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
 List<Entry<String, String>> entryList = scanResult.getResult();
 if (entryList != null && !entryList.isEmpty()) {
 for (Entry<String, String> entry : entryList) {
                jedis.hdel(bigHashKey, entry.getKey());
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));

 //删除bigkey
    jedis.del(bigHashKey);
} 

2、List删除: ltrim

public void delBigList(String host, int port, String password, String bigListKey) {
 Jedis jedis = new Jedis(host, port);
 if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
 long llen = jedis.llen(bigListKey);
 int counter = 0;
 int left = 100;
 while (counter < llen) {
 //每次从左侧截掉100个
        jedis.ltrim(bigListKey, left, llen);
        counter += left;
    }
 //最终删除key
    jedis.del(bigListKey);
} 

3、Set删除: sscan + srem

public void delBigSet(String host, int port, String password, String bigSetKey) {
 Jedis jedis = new Jedis(host, port);
 if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
 ScanParams scanParams = new ScanParams().count(100);
 String cursor = "0";
 do {
 ScanResult<String> scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
 List<String> memberList = scanResult.getResult();
 if (memberList != null && !memberList.isEmpty()) {
 for (String member : memberList) {
                jedis.srem(bigSetKey, member);
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));

 //删除bigkey
    jedis.del(bigSetKey);
} 

4、SortedSet删除: zscan + zrem

public void delBigZset(String host, int port, String password, String bigZsetKey) {
 Jedis jedis = new Jedis(host, port);
 if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
 ScanParams scanParams = new ScanParams().count(100);
 String cursor = "0";
 do {
 ScanResult<Tuple> scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
 List<Tuple> tupleList = scanResult.getResult();
 if (tupleList != null && !tupleList.isEmpty()) {
 for (Tuple tuple : tupleList) {
                jedis.zrem(bigZsetKey, tuple.getElement());
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));

 //删除bigkey
    jedis.del(bigZsetKey);
}

原作者:付磊-起扬
原文链接:阿里云Redis开发规范-云栖社区-阿里云
原出处:阿里云
侵删

深夜,我偷听到程序员要对session下手…… - 轩辕之风 - 博客园

mikel阅读(74)

来源: 深夜,我偷听到程序员要对session下手…… – 轩辕之风 – 博客园

我是一个web服务器

我是一个web服务器,我的工作是给人类提供上网服务,我每天要为数以万计的人提供网页浏览服务。

 

 

已经是深夜了,我还在和手下几个兄弟为了一件事紧张讨论着。

“老大,现在咱们每天处理的请求越来越多了,session同步的问题不能再拖了,必须想个办法”

“二哥说的是啊,老大,不能再拖了”

“老二,老三,咱们是一个集群,你们说的问题我不是不知道,我昨天听程序员们在讨论说要给我们接入一个叫Redis的家伙,相信这一问题很快就能得到解决啦,大家再忍忍。”

 

 

“Redis,他是谁,什么来头?怎么没听过这号人物”

“我也没见过,等等看吧”

session-cookie时代

到底是什么问题,让我们兄弟几个如此着急上火?事情还得从多年以前说起······

那时候,这俩兄弟还没来,就我一个web服务器,每天处理的不过是一些静态资源文件,像HTML、CSS、JS、图片等等,日子过的清闲自在。

 

 

日子一天天过着,互联网却悄然发生着变化。除了静态网页之外,可以动态交互的网络应用开始如雨后春笋般涌现,像各种各样的论坛啊,购物网站啊之类的。

这家公司的老板也不例外,招了一帮程序员要搞一套支持动态网页交互的网站出来。

以往的时候,我只需要按照HTTP协议的规范处理请求就完事儿了。不过动态交互应用出现后,我还得记住每一个请求背后的用户是谁,要不然就张冠李戴,全乱套了。

为了解决这个问题,程序员们想出了一个叫session的办法:

浏览器登陆以后,我就分配一个session id,表示一个会话,然后返回给浏览器,让它保存着。后续再来请求的时候,就把它给带上,我就能知道是谁啦!

 

 

还别说,这办法还是挺管用的,成功解决了用户身份识别的问题,这一用就是好几年。

不过,互联网的发展实在是太快了,用户量蹭蹭上涨,而我却发起了愁。

原先用户量少的时候,session id管理起来倒也简单,现在用户越来越多,对应的session id数量也与日俱增,我有点不堪重负了。

终于前不久,公司对web服务器进行了扩展,给我找来了两个小弟,还专门添置了一个nginx来进行负载均衡,这一下我们变成了3台web服务器组成的小集群了。

 

 

我的工作一下轻松了许多,两位小兄弟为了分担了不少。我原以为以后的日子要好过一些了,可没想到,两位小兄弟的加入却引入了新的问题。

原先的session id虽然很多,我一个人累是累点,但是方便管理啊!现在人手是增加了,可是这个session id的管理问题却变得复杂起来。

因为咱们现在是个集群,请求如果发到我这,我给登记了session id,但下一次请求说不定就发到老二那里,一会儿又发到老三那里,这个就说不准了,这样我们几个手头的信息不一致,就会出现一些异常情况,用户估计要破口大骂:这什么辣鸡网站

后来我们跟nginx商量了一下,让他同一个用户来的请求都发给我们固定的一个人,这才稳住了局面。

不过好景不长,后面我们三兄弟都相继出现过宕机的情况,这时候nginx还是得把请求交给还在工作的兄弟,原来的问题就又出现了!

我们几个逼急了,商量了一下,干脆大家伙来同步session id的信息好了,有新增、失效的情况都给其他几个兄弟招呼一下,大家都管理一份,这样就不会出现不一致的问题了。

 

 

搞了半天,又变成以前一个人管理所有session id的情况了,不仅如此,还要抽出时间和几位兄弟同步,把session id搬来搬去,工作量不减反增了。

就这样艰难的过了一段日子,大家都怨声载道,所以有了开头的那一番讨论。

这一次,希望这位新来的叫Redis的伙伴能拯救我们。

独立缓存——Redis

过了几天,总算把这个叫Redis的小伙伴给盼来了!

这小子看起来特别精神,了解清楚情况后,告诉我们说:“三位老哥,以后这session id都统一存在我这里吧,你们别各自保存了,这不是各位的擅长”

“你行吗?”,老二看着不太相信他的话,一脸不屑。

“行不行,试试不就知道了吗?”

 

 

接下来,我们听从了Redis的建议,不再保存这烦人的session id,全部一股脑儿交给了他,我们需要的时候再找他获取。

你还别说,这小子个子不大,本事不小,读写速度都特快,让我们头疼的问题总算是解决了!

Token时代

几个月后的一天···

“听说了吗?程序员们又要更改session id的存储方案了”,这一天,老二神神秘秘的说到。

“不对不对,我听到的版本是以后不用session id了,要变天了!”,老三也凑了上来。

一旁的redis老哥一听不乐意了,“咋的?是嫌我干的不好吗?”

我也赶紧催促,“你俩就别卖关子了,听到了什么,快说说”

 

 

老三示意大家围拢一些,小声说到:“我上次听两个程序员在议论,不知道他们在哪里学来了一套叫JWT(JSON Web Token) 的技术,硬说让我们来管理保存session id负担太重了,以后不保存了!还说,还说···”

“还说啥,你倒是说啊!”

“还说,Redis也不是万能的,也有崩溃的风险,一崩溃就全完了,所以要革新技术”,老三继续说到。

Redis一听更着急上火了,“我工作这么久以来,从没有撂过挑子吧,怎么能这么说我呢?再不行我也可以像你们搞个集群嘛”

“Redis老弟你先别急。唉,老三,这不保存session id,以后怎么鉴别用户呢?你有没有听到他们怎么说的?”

“听他们说,没有session id,但是换了一个token,用它来识别用户”

老二一听不以为意:“换了个名字,换汤不换药嘛!咱们还不是要保存token,才能匹配谁是谁”

老三摇了摇头:“不是的,这可不只是改了个名字那么简单!这个token是由三部分构成,就像这样:”

 

 

“你们看,第一部分是JWT的基本信息,然后把用户的身份信息放在第二部分,接着和第一部分合在一起做一个计算,计算的时候加入了一个只有我们才知道的密钥secretkey,计算结果作为第三部分。最后三部分拼在一起作为最终的token发送给客户端保存着···”

还没等老三说完,老二点出了其中的关键:“我知道了,后面咱们再收到这个token的时候,就可以通过同样的算法验证前面两部分的结果和第三部分是不是相同,就知道这个token是不是伪造的啦!因为密钥只有我们知道,别人没办法伪造出一个token的!最后确认有效之后,再取第二部分的用户身份信息,就知道这是谁了!”

 

 

听完他们的分析,我和Redis老兄都默默的点了点头,“有点意思啊,这样一来,咱们确实不用存了!不过现在咱们几个工作配合的也挺好的,他们费这么大劲是为了什么啊?”

“我猜他们是想节约开支,把Redis老哥给裁掉!”,老二说到。

老三摇了摇头,“依我看,八成他们是想展示技术给领导看,这不又快到职级晋升答辩了,他们想搞事情!唉,老大,这事你怎么看?”

“我啊,我···”

朋友们,你怎么看?session-cookie和JWT,你更倾向谁?