网站用户系统集成微博登录时注意事项

借用微博账号来登录网站是一件很方便的事,微博也提供了比较丰富的API来完成这件事。比较常用的就是JS API和服务器端oauth API。

JS API的好处是集成非常简单,只需要引入微博的js文件并定义一个WB2.anyWhere函数即可,在这个函数里定义好login和logout的回调服务器接口,而在服务器接口里,你仍然需要服务器端oauth API获取登录用户信息,并完成本地网站的用户创建过程。

但缺点也非常致命,你无法对登录按钮和登录后的状态信息进行完全控制,如果你需要一种看起来非常自然的集成效果,那微博自带的那几个屌兮兮的样式绝对让人无法忍受。而另一方面,实际上服务器端的oauth API使用也是必不可少,回调时候总归要在服务器端绑定一个本地用户。

最终,我们还是选择抛弃JS API。通过服务器端API完成登录的基本流程是:

1. 生成登录链接

$o = new SaeTOAuthV2( WB_AKEY , WB_SKEY );

$code_url = $o->getAuthorizeURL( WB_CALLBACK_URL );

$cur_url = $_SERVER[“REQUEST_URI”];

return “$code_url&forcelogin=true&redirect=$cur_url”;

2. 用户点击时会,打开微博的登录页面。用户登录成功后重定向到回调页面。此时,根据回调带回的code参数获取accesstoken,在用accesstoken获取登录用户信息,并完成本地用户创建或绑定。

$o = new SaeTOAuthV2( WB_AKEY , WB_SKEY );

if (isset($_REQUEST[‘code’])) {

$keys = array();

$keys[‘code’] = $_REQUEST[‘code’];

$keys[‘redirect_uri’] = WB_CALLBACK_URL;

try {

$token = $o->getAccessToken( ‘code’, $keys ) ;

} catch (OAuthException $e) {

var_dump($e);

}

}

 

if ($token) {

$_SESSION[‘token’] = $token;

setcookie( ‘weibojs_’.$o->client_id, http_build_query($token) );

$c = new SaeTClientV2( WB_AKEY , WB_SKEY ,  $_SESSION[‘token’][‘access_token’]);

$uid_get = $c->get_uid();

$uid = $uid_get[‘uid’];

$um = $c->show_user_by_id($uid);

$u = UserSns::create($um[‘id’],$um[‘name’],$um);

Visitor::login($u->getUser());

$this->simpleRender(‘weibosuccess’);

} else {

echo ‘授权失败。’;

}

这样完成了用户创建后,基本就完成了。

但有时候,如果用户系统是基于email的(也就是email要用来登录、验证、找密码、接收通知等等),最好的办法是在授权成功后不要只是简单地自动创建一个用户,而应该让用户完成注册流程,也就是再填写email和登录密码然后创建一个不是那么特殊的用户,从而更好地将微博登录的用户和网站自身的用户系统融合。

OSX下安装phpredis

由于OSX自带的php是5.3.26,而我使用的XAMPP又是5.4.16. 安装时想当然的直接照着别人的方法去做。结果总是因为使用了错误的php配置编译phpredis,造成总是如下错误:

PHP Warning:  PHP Startup: redis: Unable to initialize module
Module compiled with module API=20090626
PHP    compiled with module API=20100525
These options need to match
 in Unknown on line 0

算是典型性OSX上多PHP环境造成的问题,呵呵

开始我把/usr/bin下的php,phpize,php-config全部改为link指向XAMPP里对应的文件,但编译出来还是有问题。最后不得已,全部改成完整路径。算是解决了。

网上搜索如果不仔细看的话都是这样简单的做法:

phpize
./configure
sudo make
sudo make install

但实际上:

sudo /Applications/XAMPP/xamppfiles/bin/phpize

sudo MACOSX_DEPLOYMENT_TARGET=10.6 CFLAGS=”-arch i386 -arch x86_64 -g -Os -pipe -no-cpp-precomp” CCFLAGS=”-arch i386 -arch x86_64 -g -Os -pipe” CXXFLAGS=”-arch i386 -arch x86_64 -g -Os -pipe” LDFLAGS=”-arch i386 -arch x86_64 -bind_at_load” ./configure –with-apxs=/Applications/XAMPP/xamppfiles/bin/apxs –with-php-config=/Applications/XAMPP/xamppfiles/bin/php-config

sudo make

sudo make install

最后修改php.ini文件,加入extension=redis.so

总结下来,OSX上的php环境绝对是一件让人蛋疼的事。几乎所有需要编译安装的php扩展(如memcached,mongodb)都需要这样做。否则都是那个经典的“not match”。

tinymce的免费filemanager

TinyMCE不用介绍,最流行的富文本编辑器。可它自带的文件管理器插件是需要付费的。没关系,自然有热心的程序员贡献免费开源的版本。FileManager4TinyMCE就是其中之一(8.0以后改名为Responsive FileManager ,不过没关系,依然是开源的)

安装时只要根据官方网站的说明即可。但使用时候需要注意几点:

1. config.php里需要配置相应的路径,同时需要配置对应上传目录路径的权限为766。因为运行apache的用户需要写权限到这个目录。

2. 同时还需要把 filemanager这个文件夹的权限也改为至少766,否则上传控件会报没有写权限的错误(”The path is not writable. Please check your permissions”),因为这个filemanager的文件夹里有一个thumbs的文件夹用来存放缩略图的地方,如果没有写权限,缩略图就无法生成。导致明明上传了的文件,但是却无法在控件中显示。

树莓派第一次亲密接触

第一版的树莓派,买回来一直都没有花时间折腾,不知道每天在忙什么。最近终于有点时间。记录一下。

开始是用官方站的NOOBS,但是始终无法启动,怀疑是RP问题。后来用dd重新做了一张raspbian(参考:http://elinux.org/RPi_Easy_SD_Card_Setup)。

启动后总是黑屏,怀疑是hdmi转vga的线的问题,然后从某宝又买了一根。后来发现是config.txt的设置问题,参考了这篇文章修改后,终于开机成功。

开机后发现只要光标移动就会闪屏,修改了无数次config.txt都未果,后来发现是电源供电问题 – hdmi转vga线会占用一部分电压,后来想起来ipad的充电电源貌似电流大一些,换上以后居然不闪了。一切正常。

然后就是配置网络:(摘自树莓派论坛)

1.配置ip地址
编辑sudo nano  /etc/network/interfaces
自动获取ip的配置:
auto eth0  
allow-hotplug eth0    
iface eth0 inet dhcp
手动配置ip:
auto eth0   
iface eth0 inet static        
address 192.168.1.7       
netmask 255.255.255.0        
gateway 192.168.0.254
2.配置DNS
编辑 sudo nano /etc/resolv.conf
domain lan
search lan
nameserver 192.168.1.1
nameserver 192.168.99.100
注:nameserver 后面的ip就是dns服务器,根据自己的运营商情况填吧

更新raspbian:

sudo apt-get install rpi-update

具体参考:https://github.com/Hexxeh/rpi-update

后面就慢慢折腾吧。

34 个使用 Raspberry Pi 的酷创意

Raspberry Pi(树莓派)试用小记

附个分辨率的映射:

These values are valid if hdmi_group=2 (DMT) 

hdmi_mode=1 640×350 85Hz 

hdmi_mode=2 640×400 85Hz 

hdmi_mode=3 720×400 85Hz 

hdmi_mode=4 640×480 60Hz 

hdmi_mode=5 640×480 72Hz 

hdmi_mode=6 640×480 75Hz 

hdmi_mode=7 640×480 85Hz 

hdmi_mode=8 800×600 56Hz 

hdmi_mode=9 800×600 60Hz 

hdmi_mode=10 800×600 72Hz 

hdmi_mode=11 800×600 75Hz 

hdmi_mode=12 800×600 85Hz 

hdmi_mode=13 800×600 120Hz 

hdmi_mode=14 848×480 60Hz 

hdmi_mode=15 1024×768 43Hz DO NOT USE 

hdmi_mode=16 1024×768 60Hz 

hdmi_mode=17 1024×768 70Hz 

hdmi_mode=18 1024×768 75Hz 

hdmi_mode=19 1024×768 85Hz 

hdmi_mode=20 1024×768 120Hz 

hdmi_mode=21 1152×864 75Hz 

hdmi_mode=22 1280×768 reduced blanking 

hdmi_mode=23 1280×768 60Hz 

hdmi_mode=24 1280×768 75Hz 

hdmi_mode=25 1280×768 85Hz 

hdmi_mode=26 1280×768 120Hz reduced blanking 

hdmi_mode=27 1280×800 reduced blanking 

hdmi_mode=28 1280×800 60Hz 

hdmi_mode=29 1280×800 75Hz 

hdmi_mode=30 1280×800 85Hz 

hdmi_mode=31 1280×800 120Hz reduced blanking 

hdmi_mode=32 1280×960 60Hz 

hdmi_mode=33 1280×960 85Hz 

hdmi_mode=34 1280×960 120Hz reduced blanking 

hdmi_mode=35 1280×1024 60Hz 

hdmi_mode=36 1280×1024 75Hz 

hdmi_mode=37 1280×1024 85Hz 

hdmi_mode=38 1280×1024 120Hz reduced blanking 

hdmi_mode=39 1360×768 60Hz 

hdmi_mode=40 1360×768 120Hz reduced blanking 

hdmi_mode=41 1400×1050 reduced blanking 

hdmi_mode=42 1400×1050 60Hz 

hdmi_mode=43 1400×1050 75Hz 

hdmi_mode=44 1400×1050 85Hz 

hdmi_mode=45 1400×1050 120Hz reduced blanking 

hdmi_mode=46 1440×900 reduced blanking 

hdmi_mode=47 1440×900 60Hz 

hdmi_mode=48 1440×900 75Hz 

hdmi_mode=49 1440×900 85Hz 

hdmi_mode=50 1440×900 120Hz reduced blanking 

hdmi_mode=51 1600×1200 60Hz 

hdmi_mode=52 1600×1200 65Hz 

hdmi_mode=53 1600×1200 70Hz 

hdmi_mode=54 1600×1200 75Hz 

hdmi_mode=55 1600×1200 85Hz 

hdmi_mode=56 1600×1200 120Hz reduced blanking 

hdmi_mode=57 1680×1050 reduced blanking 

hdmi_mode=58 1680×1050 60Hz 

hdmi_mode=59 1680×1050 75Hz 

hdmi_mode=60 1680×1050 85Hz 

hdmi_mode=61 1680×1050 120Hz reduced blanking 

hdmi_mode=62 1792×1344 60Hz 

hdmi_mode=63 1792×1344 75Hz 

hdmi_mode=64 1792×1344 120Hz reduced blanking 

hdmi_mode=65 1856×1392 60Hz 

hdmi_mode=66 1856×1392 75Hz 

hdmi_mode=67 1856×1392 120Hz reduced blanking 

hdmi_mode=68 1920×1200 reduced blanking 

hdmi_mode=69 1920×1200 60Hz 

hdmi_mode=70 1920×1200 75Hz 

hdmi_mode=71 1920×1200 85Hz 

hdmi_mode=72 1920×1200 120Hz reduced blanking 

hdmi_mode=73 1920×1440 60Hz 

hdmi_mode=74 1920×1440 75Hz 

hdmi_mode=75 1920×1440 120Hz reduced blanking 

hdmi_mode=76 2560×1600 reduced blanking 

hdmi_mode=77 2560×1600 60Hz 

hdmi_mode=78 2560×1600 75Hz 

hdmi_mode=79 2560×1600 85Hz 

hdmi_mode=80 2560×1600 120Hz reduced blanking 

hdmi_mode=81 1366×768 60Hz 

hdmi_mode=82 1080p 60Hz 

hdmi_mode=83 1600×900 reduced blanking 

hdmi_mode=84 2048×1152 reduced blanking 

hdmi_mode=85 720p 60Hz 

hdmi_mode=86 1366×768 reduced blanking

微信图文消息样式参考

微信公众平台的编辑器支持的功能太少,比如字体、颜色、样式都不能很方便的修改。不过好在微信编辑器是支持自定义css样式,就是自己要稍微动动手。

这里保存一些样式,以备不时之需。直接copy到微信图文消息的编辑器就可以用了。目前微信还是不支持字体。没啥就是css,图个方便。

<blockquote style=”font-size:17px;padding:0px 20px;border-left:5px solid #c9c9c9;background-color: transparent”>引用文字风格</blockquote>

引用文字风格

<blockquote style=”font-size:17px;padding:0px 20px;border: 1px dashed #EFEBC2;background-color: #FFFEEF;”>引用文字风格</blockquote>

引用文字风格

<blockquote style=”font-size:17px;padding:0px 20px;border: 2px solid #EFEFEF;background: -75px -20px #F0F0F0;border-radius: 10px;”>引用文字风格</blockquote>

引用文字风格

<p style=”font-size:20px;background-color: transparent”>字体大小20px</p>

字体大小20px

字体大小17px

字体大小15px

<p style=”font-size:17px;background-color: transparent;color:blue”>文字颜色</p>

文字颜色

<p style=”font-size:17px;background-color: transparent;font-weight: bold”>文字加粗</p>

文字加粗

<p style=”font-size:17px;background-color: transparent;font-style:italic”>文字斜体</p>

文字斜体

<p style=”font-size:17px;background-color: transparent;text-decoration: underline”>文字下划线</p>

文字下划线

<p style=”font-size:17px;background-color: transparent;text-decoration: line-through”>删除线</p>

删除线

技术傻瓜

今天无意中听到“技术傻瓜”这个词,和最近在思考的问题有点关联 – 信息爆炸让人可以接触到的信息极大的丰富了,但是貌似人的处理信息的能力并没有极大提高。

我们可以随时google、百度到大多数问题的答案,我们也有了更充足的理由去不屑于所谓“死记硬背”的答案,甚至于如果能够问出一个google和百度解决不了的问题都已经成了“创意”。答案唾手可得的结果是什么?貌似是我们对“思考”的不屑。

那人还比机器强在哪里呢?

信息爆炸的结果在目前看来是把人的时间炸“碎了”,而且是越来越碎。从互联网诞生的那一刻开始,人类就是不停地往这台最大的“计算机”里“输入”东西,现在我们每天输入的是不到140个字的“只言片语”,曾经的“只言片语”是世外高人留给你的禅机,现在则成了每天必要的“呢喃”。前些年我们还发发原创贴,写写博客,现在我们只会回复个“沙发”“板凳”,写写微博了。

技术是个值得敬畏的东西,我不担心儿子以后只会用食指写字,我担心的是儿子变成互联网的“哑终端”。

UML中usecase之间的关系

老文。

 

用例描述的是系统外部可见的行为,是系统为某一个或几个参与者提供的一段完整的服务。从原则上来讲,用例之间都是并列的,它们之间并不存在着包含从属关系。但是从保证用例模型的可维护性和一致性角度来看,我们可以在用例之间抽象出包含(include)、扩展(extend)和泛化(generalization)这几种关系。这几种关系都是从现有的用例中抽取出公共的那部分信息,然后通后过不同的方法来重用这部公共信息,以减少模型维护的工作量。

 

泛话(继承)就不说了很好理解.

 

包含(include) 

 

包含关系是通过在关联关系上应用<<include>>构造型来表示的,如下图所示。它所表示的语义是指基础用例(Base)会用到被包含用例(Inclusion),具体地讲,就是将被包含用例的事件流插入到基础用例的事件流中。

 

扩展(extend) 

 

扩展(extend)关系如下图所示,基础用例(Base)中定义有一至多个已命名的扩展点,扩展关系是指将扩展用例(Extension)的事件流在一定的条件下按照相应的扩展点插入到基础用例(Base)中。

至少从这个两个定义里面都看到了”将扩展(被包含)用例的事件流插入基础用例事件流”, 也就是 如果 用例A include 用例B, 似乎就又可以描述为 用例B extend 用例A, 是这样吗?

前一分钟, 我问了从我身边经过的一个同事这个问题, 他的答案是:可以把extend理解为并联,include理解为串联. 也就是说 include所包含的用例必须是基础用例的一部分,没有他基础用例就不完整. 而extend则意味着扩展, 可以扩展用例插入到任何扩展点.或者说 “对于包含关系而言,子用例中的事件流是一定插入到基础用例中去的,并且插入点只有一个。而扩展关系可以根据一定的条件来决定是否将扩展用例的事件流插入基础用例事件流,并且插入点可以有多个。”

通过使用mahout的核心api来实现中文文本的分类

mahout版本:0.7 (mahout的安装请参考:https://cwiki.apache.org/MAHOUT/buildingmahout.html)

hadoop版本:1.0.3

lucene版本:3.6.0(+paoding1.0无法通过maven直接导入依赖,需要单独加到classpath)

 

mahout标准的分类demo展示了如何对英文文本内容分类,并且使用的是mahout的命令行脚本。下面主要是介绍使用mahout的api来完成同样的事,并且是中文。

 

标准的mahout文本分类分为以下几步:

  1. sequencing:将训练样本从简单的文本转换为hadoop标准的sequence格式
  2. vectorize: 向量化,将sequence格式的样本转换为向量形式。如“钓鱼岛是中国的”,先转换为分词后的序列“钓鱼,钓鱼岛,岛,中国”,然后给每个词一个index,最终转换为”1212:1,232:1, 16:1,789:1″这样的向量
  3. split: 样本分为训练集和测试集
  4. train: 根据训练集训练出模型
  5. test: 使用测试集测试模型并获得模型的准确率和confusionmatrix
  6. classify: 使用模型对新样本进行分类

下面分别介绍每个步骤:

 

sequencing

 

java代码:

args = new String[] { “-i”, sampleDir, “-o”, sequenceDir };

SequenceFilesFromDirectory sequenceJob = new SequenceFilesFromDirectory();

sequenceJob.setConf(getConf());

sequenceJob.run(args);

 

sampleDir是样本文件,组织结构是每个类一个子文件夹,文件夹内是对应的一个个样本文件。

执行后会在sequenceDir目录下看到一个chunk-0的文件,可以通过mahout的SequenceFileDumper转换为文本,转换后可以看到内容是这样:

 

Input Path: file:/Users/derekzhangv/Develop/temptest/testTopic/testTopic-seq/chunk-0

Key class: class org.apache.hadoop.io.Text Value Class: class org.apache.hadoop.io.Text

 

Key: /good/51057: Value: 浪潮之巅

 

Key: /good/55107: Value: 电动车

 

Key: /bad/85364: Value: 婴儿用品

 

Count: 3

 

(技巧:如果需要在命令行里方便地查看可以设置一个alias:“alias seqdump=’/Users/derekzhangv/Develop/mahout-0.7/bin/mahout seqdumper -i `pwd`/$1 | more’ ”)

 

vectorize

 

java代码:

args = new String[] { “-i”, sequenceDir, “-o”, vectorDir, “-lnorm”,

“-nv”, “-wt”, “tfidf”, “-s”, “2”// minSuport, default 2 , “-a”, “net.paoding.analysis.analyzer.PaodingAnalyzer” };

SparseVectorsFromSequenceFiles vectorizeJob = new SparseVectorsFromSequenceFiles();

vectorizeJob.setConf(getConf());

vectorizeJob.run(args);

 

参数:-i/-o是指定输入/输出的目录,-lnorm指定使用对数来normalize向量,-nv指定向量需要命名,-wt指定权重计算方法tf或者tfidf,-s指定最小支持数量(即只考虑出现指定次数或者以上的词),-a指定analyzer分词方法,这里只需要指定一个中文分词analyzer既可,这里是PaodingAnalyzer。

 

执行后会在[vectorDir]下产生如下文件:

df-count

dictionary.file-0

frequency.file-0

tf-vectors

tfidf-vectors

tokeninzed-documents

wordcount

可以用vectorDumper看看里面的内容。这里就不赘述了。

 

split

 

java代码:

args = new String[] { “-i”, tfidfVectorDir, “–trainingOutput”,trainVectorDir, “–testOutput”, testVectorDir,”–randomSelectionPct”, “40”, “–overwrite”, “–sequenceFiles”,

     “-xm”, “sequential” };

SplitInput splitJob = new SplitInput();

splitJob.setConf(getConf());

splitJob.run(args);

 

参数:–trainingOutput和–testOutput分别指定训练和测试样本的输出路径,–randomSelectionPct指定比例,–sequenceFiles说明输入文件格式为sequence,-xm执行方式,默认是map reduce,这里指定sequential

执行后会产生对应的两个文件夹,里面是part-r-00000文件,可以通过sequencedumper查看。

 

train

 

java代码:

args = new String[] { “-i”, trainVectorDir, “-o”, modelDir, “-li”,labelIndexDir, “-el”, “-ow” };

TrainNaiveBayesJob trainJob = new TrainNaiveBayesJob();

trainJob.setConf(getConf());

trainJob.run(args);

 

参数:-o指定模型输出路径,-li指定labelindex文件的路径,这个文件是在vectorize时候产生的。-el指明label是从样本中提取,-ow指明可以覆盖文件。

执行后会在[modelDir]下看到naiveBayesModel.bin文件,就是训练出来的结果了。

 

test

 

java代码:

args = new String[] { “-i”, testVectorDir, “-m”, modelDir, “-l”,

labelIndexDir, “-ow”, “-o”, testingDir };

TestNaiveBayesDriver testJob = new TestNaiveBayesDriver();

testJob.setConf(getConf());

testJob.run(args);

 

参数:-i指定测试样本vector的路径,-m模型所在路径,-l是label索引路径,-ow指明覆盖原来的输出,-o输出路径

输出的文件可以通过sequencedumper查看。

 

classify

 

java代码:(有点dirty)

Configuration conf = new Configuration();

AbstractNaiveBayesClassifier classifier = loadClassifier(topicId, conf);

Vector instance = buildInstance(topicId,text);

Vector r = classifier.classifyFull(instance);

Path labelIndexPath = new Path(this.tempDir + “/” + topicId +“/” + topicId

+ “-labelIndex”);

Map<Integer, String> labelMap = BayesUtils.readLabelIndex(conf,

labelIndexPath);

 

int bestIdx = Integer.MIN_VALUE;

double bestScore = Long.MIN_VALUE;

HashMap<String, Double> resultMap = new HashMap<String, Double>();

for (int i = 0; i < labelMap.size(); i++) {

Vector.Element element = r.getElement(i);

resultMap.put(labelMap.get(element.index()), element.get());

if (element.get() > bestScore) {

bestScore = element.get();

bestIdx = element.index();

}

}

ClassifyResult result = new ClassifyResult();

if (bestIdx != Integer.MIN_VALUE) {

String label = labelMap.get(bestIdx);

double score = bestScore;

result.setLabel(label);

result.setScore(score);

}

return result;

 

–附上buildInstance和loadClassifier的代码:

private AbstractNaiveBayesClassifier loadClassifier(String topicId,Configuration conf) throws IOException {

Path modelPath = new Path(this.modelDir + “/” + topicId + “-model”);

NaiveBayesModel model = NaiveBayesModel.materialize(modelPath, conf);

AbstractNaiveBayesClassifier classifier = new StandardNaiveBayesClassifier(model);

  return classifier;

}

 

private Vector buildInstance(String topicId,String text){

try {

reBuildDictionary(topicId);

  }  catch (IOException e) {

e.printStackTrace();

}

Vector vector = new RandomAccessSparseVector(FEATURES);

FeatureExtractor fe = new FeatureExtractor();

HashSet<String> fs = fe.extract(text);

for (String s : fs) {

int index = dictionary.get(s);

vector.setQuick(index, frequency.get(index));

}

return vector;

}

(这里就是比较dirty的地方,需要根据dictionary.file-0和frequency.file-0来构造索引,然后用来vectorize目标样本(要分类的样本)

private boolean dictRebuilt = false;

privatevoid reBuildDictionary(String topicId) throws IOException{

if(dictRebuilt) return;

Configuration conf = getConf();

 

    Path dictionaryFile = new Path(tempDir+“/”+topicId+“/”+topicId+“-vectors/dictionary.file-0”);

    // key is feature, value is the document frequency

    for (Pair<Text,IntWritable> record 

        : new SequenceFileIterable<Text,IntWritable>(dictionaryFile, true, conf)) {

      dictionary.put(record.getFirst().toString(), record.getSecond().get());

    }

    Path freqFile = new Path(tempDir+“/”+topicId+“/”+topicId+“-vectors/frequency.file-0”);

    // key is feature, value is the document frequency

    for (Pair<IntWritable,LongWritable> record 

        : new SequenceFileIterable<IntWritable,LongWritable>(freqFile, true, conf)) {

    frequency.put(record.getFirst().get(), record.getSecond().get());

    }

    dictRebuilt = true;

}

 

结束。