给ecshop编辑器kindeditor图片上传添加水印功能_ecshop教程_php教程_最模板

mikel阅读(1030)

来源: 给ecshop编辑器kindeditor图片上传添加水印功能_ecshop教程_php教程_最模板

最模板以前在网站介绍过ecshop更换编辑成kindeditor的方法,但是上传的图片不能自动增加水印,今天把相应方法告诉大家:

在kindeditor/php/upload_json.php中,有两处需要修改的地方。

第一步,在文件末尾添加一个方法

function imageWaterMark($groundImage, $waterPos=0, $waterImage=”, $alpha=80, $waterText=”, $textFont=9, $textColor=’#FF0000′){
$isWaterImage = FALSE;
$formatMsg = ‘不支持该图片格式!请使用GIF、JPG、PNG格式的图片。’;
//读取水印文件
if(!empty($waterImage) && file_exists($waterImage)){
$isWaterImage = TRUE;
$water_info = getimagesize($waterImage);
$water_w = $water_info[0];//取得水印图片的宽
$water_h = $water_info[1];//取得水印图片的高
switch($water_info[2]){//取得水印图片的格式
case 1:$water_im = imagecreatefromgif($waterImage);break;
case 2:$water_im = imagecreatefromjpeg($waterImage);break;
case 3:$water_im = imagecreatefrompng($waterImage);break;
default:die($formatMsg);
}
}
//读取背景图片
if(!empty($groundImage) && file_exists($groundImage)){
$ground_info = getimagesize($groundImage);
$ground_w = $ground_info[0];//取得背景图片的宽
$ground_h = $ground_info[1];//取得背景图片的高
switch($ground_info[2]){//取得背景图片的格式
case 1:$ground_im = imagecreatefromgif($groundImage);break;
case 2:$ground_im = imagecreatefromjpeg($groundImage);break;
case 3:$ground_im = imagecreatefrompng($groundImage);break;
default:die($formatMsg);
}
}else{
alert(“水印图片不存在!”);
}
//水印位置
if($isWaterImage){//图片水印
$w = $water_w;
$h = $water_h;
$label = “图片的”;
}
if(($ground_w<$w) || ($ground_h<$h)){
echo “需要加水印的图片的长度或宽度比水印”.$label.”还小,无法生成水印!”;
return; // www.zuimoban.com
}
switch($waterPos){
case 0://随机
$posX = rand(0,($ground_w – $w));
$posY = rand(0,($ground_h – $h));
break;
case 1://1为顶端居左
$posX = 0;
$posY = 0;
break;
case 2://2为顶端居中
$posX = ($ground_w – $w) / 2;
$posY = 0;
break;
case 3://3为顶端居右
$posX = $ground_w – $w;
$posY = 0;
break;
case 4://4为中部居左
$posX = 0;
$posY = ($ground_h – $h) / 2;
break;
case 5://5为中部居中
$posX = ($ground_w – $w) / 2;
$posY = ($ground_h – $h) / 2;
break;
case 6://6为中部居右
$posX = $ground_w – $w;
$posY = ($ground_h – $h) / 2;
break;
case 7://7为底端居左
$posX = 0;
$posY = $ground_h – $h;
break;
case 8://8为底端居中
$posX = ($ground_w – $w) / 2;
$posY = $ground_h – $h;
break;
case 9://9为底端居右
$posX = $ground_w – $w;
$posY = $ground_h – $h;
if(!$isWaterImage){
$posY = $ground_h – $h-20;
}
break;
default://随机
$posX = rand(0,($ground_w – $w));
$posY = rand(0,($ground_h – $h));
break;
}
//设定图像的混色模式
imagealphablending($ground_im, true);
if($isWaterImage){//图片水印
//imagecopy($ground_im, $water_im, $posX, $posY, 0, 0, $water_w,$water_h);//拷贝水印到目标文件
//生成混合图像
imagecopymerge($ground_im, $water_im, $posX, $posY, 0, 0, $water_w, $water_h, $alpha);
} else {//文字水印
if( !empty($textColor) && (strlen($textColor)==7)){
$R = hexdec(substr($textColor,1,2));
$G = hexdec(substr($textColor,3,2));
$B = hexdec(substr($textColor,5));
} else {
die(“水印文字颜色格式不正确!”);
}
imagestring($ground_im, $textFont, $posX, $posY, $waterText, imagecolorallocate($ground_im, $R, $G, $B));
}
//生成水印后的图片
@unlink($groundImage);
switch($ground_info[2]){//取得背景图片的格式
case 1:imagegif($ground_im,$groundImage);break;
case 2:imagejpeg($ground_im,$groundImage,100);break;//注意这里的100,经测试,100是图片质量最佳的,但文件大小会增加很多,95的时候质量还不错,大小和原来的差不多。作者采用95的值。
case 3:imagepng($ground_im,$groundImage);break;
default:die($errorMsg);
}
//释放内存
if(isset($water_info)) unset($water_info);
if(isset($water_im)) imagedestroy($water_im);
unset($ground_info);
imagedestroy($ground_im);
}

第二步,找到$json = new Services_JSON();(这个文件中有两处,alert($msg)方法里的不用操作),在后面添加如下一段代码

$water_mark = 1;//1为加水印, 其它为不加
$water_pos = 9;//水印位置,10种状态【0为随机,1为顶端居左,2为顶端居中,3为顶端居右;4为中部居左,5为中部居中,6为中部居右;7为底端居左,8为底端居中,9为底端居】
$water_img = $_SERVER[‘DOCUMENT_ROOT’].’/images/water.gif’;//水印图片,默认填写空,请将图片上传至网站根目录的images下,例: water.gif
$water_alpha = 50;//水印透明度
$water_text = ”;//水印字符串,默认填写空;
//$water_fontfile = $_SERVER[‘DOCUMENT_ROOT’] .’/upfiles/fonts/arial.ttf’;//文字水印使用的字体;
if($water_mark == 1){
imageWaterMark($file_path, $water_pos, $water_img, $water_alpha, $water_text);
}

这里只需要上传一张水印图片即可。

完成上述操作后,在用编辑器上传图片就可以实现上图的效果了。

ECSHOP商城2.73后台SQL注入漏洞修复(8月12日)_温州电商-赵陇真_新浪博客

mikel阅读(1167)

 

来源: ECSHOP商城2.73后台SQL注入漏洞修复(8月12日)_温州电商-赵陇真_新浪博客

今天又发现一个ecshop后台SQL注入漏洞,在这里分享给大家,祝愿乐清的网站建设技术越来越好。

文件在/admin/comment_manage.php后台SQL注入漏洞。      /admin/comment_manage.php修复方法(大概在第336行)      $filter[‘sort_by’]      = empty($_REQUEST[‘sort_by’]) ? ‘add_time’ : trim($_REQUEST[‘sort_by’]);

$filter[‘sort_order’]   = empty($_REQUEST[‘sort_order’]) ? ‘DESC’ : trim($_REQUEST[‘sort_order’]);

修改为      $sort = array(‘comment_id’,’comment_rank’,’add_time’,’id_value’,’status’); $filter[‘sort_by’] = in_array($_REQUEST[‘sort_by’], $sort) ? trim($_REQUEST[‘sort_by’]) : ‘add_time’; $filter[‘sort_order’] = empty($_REQUEST[‘sort_order’]) ? ‘DESC’ : ‘ASC’;

修复完成,以后有什么网站,最好还是先找赵老师看一下,以免造成成吨的损失,因小失大。      ECSHOP商城2.73最新漏洞修复大全

ECSHOP /admin/affiliate_ck.php sql注入 - PHP - 漏洞时代

mikel阅读(1539)

来源: ECSHOP /admin/affiliate_ck.php sql注入 – PHP – 漏洞时代

/admin/affiliate_ck.php (207-350)是整个get_affiliate_ck函数.

其中发现对$_GET[‘auid’]的取值仅仅是直接传入就加入了sql语句

只要满足了$_GET[‘auid’]不为空就可以直接代入,导致了sql注入发生

1

执行的sql语句为

修复方案

对$_GET[‘auid’]强制转换

diff

ecshop /includes/modules/payment/alipay.php SQL Injection Vul SQL注入漏洞修复- 推酷

mikel阅读(1061)

 

来源: ecshop /includes/modules/payment/alipay.php SQL Injection Vul – 推酷

catalog

1. 漏洞描述
2. 漏洞触发条件
3. 漏洞影响范围
4. 漏洞代码分析
5. 防御方法
6. 攻防思考

1. 漏洞描述

ECSHOP支付插件存在SQL注入漏洞,此漏洞存在于/includes/modules/payment/alipay.php文件中,该文件是ECshop的支付宝插件。由于ECShop使用了str_replace函数做字符串替换,黑客可绕过单引号限制构造SQL注入语句。只要开启支付宝支付插件就能利用该漏洞获取网站数据,且不需要注册登入。GBK与UTF-8版本ECshop均存在此漏洞

Relevant Link:

http://sebug.net/vuldb/ssvid-60643

2. 漏洞触发条件

3. 漏洞影响范围

4. 漏洞代码分析

/includes/modules/payment/alipay.php

function respond()
{
	if (!empty($_POST))
	{
		foreach($_POST as $key => $data)
		{
		$_GET[$key] = $data;
		}
	}
	$payment  = get_payment($_GET['code']);
	$seller_email = rawurldecode($_GET['seller_email']);
	$order_sn = str_replace($_GET['subject'], '', $_GET['out_trade_no']);
	//未对$order_sn进行有效注入过滤
	$order_sn = trim($order_sn);  
	..

ECShop在/includes/init.php中有段代码会将外界传入的数据进行转义,比如单引号;同样在/includes/modules/payment/alipay.php中外界传入的$_GET[‘subject’]和$_GET[‘out_trade_no’]也是被转义过的,但是在该文件的str_replace()函数会将$_GET[‘out_trade_no’]中的$_GET[‘subject’]过滤掉(黑客输入转义符号,将转义符本身过滤叼),最后经过str_replace()函数处理后引入单引号,使闭合单引号重新获得攻击能力,触发SQL注入

Relevant Link:

http://sebug.net/vuldb/ssvid-61150

5. 防御方法

/includes/modules/payment/alipay.php

function respond()
{
	if (!empty($_POST))
	{
		foreach($_POST as $key => $data)
		{
		$_GET[$key] = $data;
		}
	}
	$payment  = get_payment($_GET['code']);
	$seller_email = rawurldecode($_GET['seller_email']);
	$order_sn = str_replace($_GET['subject'], '', $_GET['out_trade_no']);
	/* 对$order_sn进行有效过滤 */
	$order_sn = trim(addslashes($order_sn));
	/* */
	..

Relevant Link:

http://bbs.ecshop.com/viewthread.php?tid=1125380&extra=page=1&orderby=replies&filter=172800

6. 攻防思考

Copyright (c) 2015 LittleHann All rights reserved

从Chrome源码看浏览器如何构建DOM树 – 人人网FED博客

mikel阅读(1019)

来源: 从Chrome源码看浏览器如何构建DOM树 – 人人网FED博客

这几天下了Chrome的源码,安装了一个Debug版的Chromium研究了一下,虽然很多地方都一知半解,但是还是有一点收获,将在这篇文章介绍DOM树是如何构建的,看了本文应该可以回答以下问题:

  1. IE用的是Trident内核,Safari用的是Webkit,Chrome用的是Blink,到底什么是内核,它们的区别是什么?
  2. 如果没有声明<!DOCTYPE html>会造成什么影响?
  3. 浏览器如何处理自定义的标签,如写一个<data></data>?
  4. 查DOM的过程是怎么样的?

先说一下,怎么安装一个可以Debug的Chrome

1. 从源码安装Chrome

为了可以打断点debug,必须得从头编译(编译的时候带上debug参数)。所以要下载源码,Chrome把最新的代码更新到了Chromium的工程,是完全开源的,你可以把它整一个git工程下载下来。Chromium的下载安装可参考它的文档, 这里把一些关键点说一下,以Mac为例。你需要先下载它的安装脚本工具,然后下载源码:

–no-history的作用是不把整个git工程下载下来,那个实在是太大了。或者是直接执行git clone:

这个就是整一个git工程,下载下来有6.48GB(那时)。博主就是用的这样的方式,如果下载到最后提示出错了:

可以这样解决:

就不用重头开始clone,因为实在太大、太耗时了。

下载好之后生成build的文件:

–ide=xcode是为了能够使用苹果的XCode进行可视化进行调试。gn命令要下载Chrome的devtools包,文档里面有说明。

准备就绪之后就可以进行编译了:

在笔者的电脑上编译了3个小时,firfox的源码需要编译7、8个小时,所以相对来说已经快了很多,同时没报错,一次就过,相当顺利。编译组装好了之后,会在out/gn目录生成Chromium的可执行文件,具体路径是在:

运行这个就可以打开Chromium了:

那么怎么在可视化的XCode里面进行debug呢?

2. 在XCode里面Debug

在上面生成build文件的同时,会生成XCode的工程文件:sources.xcodeproj,具体路径是在:

双击这个文件,打开XCode,在上面的菜单栏里面点击Debug -> AttachToProcess -> Chromium,要先打开Chrome,才能在列表里面看到Chrome的进程。然后小试牛刀,打个断点试试,看会不会跑进来:

在左边的目录树,打开chrome/browser/devtools/devtools_protocol.cc这个文件,然后在这个文件的ParseCommand函数里面打一个断点,按照字面理解这个函数应该是解析控制台的命令。打开Chrome的控制台,输入一条命令,例如:new Date(),按回车可以看到断点生效了:

通过观察变量值,可以看到刚刚敲进去的命令。这就说明了我们安装成功,并且可以通过可视化的方式进行调试。

但是我们要debug页面渲染过程,Chrome的blink框架使用多进程技术,每打开一个tab都会新开一个进程,按上面的方式是debug不了构建DOM过程的,从Chromium的文档可以查到,需要在启动的时候带上一个参数:

Chrom的启动进程就会绪塞,并且提示它的渲染进程ID:

[7339:775:0102/210122.254760:ERROR:child_process.cc(145)] Renderer (7339) paused waiting for debugger to attach. Send SIGUSR1 to unpause.

7339就是它的渲染进程id,在XCode里面点 Debug -> AttachToProcess By Id or Name -> 填入id -> 确定,attach之后,Chrome进程就会恢复,然后就可以开始调试渲染页面的过程了。

content/renderer/render_view_impl.cc这个文件的1093行RenderViewImpl::Create函数里面打个断点,按照上面的方式,重新启动Chrome,在命令行带上某个html文件的路径,为了打开Chrome的时候就会同时打开这个文件,方便调试。执行完之后就可以看到断点生效了。可以说render_view_impl.cc这个文件是第一个具体开始渲染页面的文件——它会初始化页面的一些默认设置,如字体大小、默认的viewport等,响应关闭页面、OrientationChange等事件,而在它再往上的层主要是一些负责通信的类。

3. Chrome建DOM源码分析

先画出构建DOM的几个关键的类的UML图,如下所示:

第一个类HTMLDocumentParser负责解析html文本为tokens,一个token就是一个标签文本的序列化,并借助HTMLTreeBuilder对这些tokens分类处理,根据不同的标签类型、在文档不同位置,调用HTMLConstructionSite不同的函数构建DOM树。而HTMLConstructionSite借助一个工厂类对不同类型的标签创建不同的html元素,并建立起它们的父子兄弟关系,其中它有一个m_document的成员变量,这个变量就是这棵树的根结点,也是js里面的window.document对象。

为作说明,用一个简单的html文件一步步看这个DOM树是如何建立起来的:

然后按照上面第2点提到debug的方法,打开Chromium并开始debug:

我们先来研究一下Chrome的加载和解析机制

1. 加载机制

以发http请求去加载html文本做为我们分析的第一步,在此之前的一些初始化就不考虑了。Chrome是在DocumentLoader这个类里面的startLoadingMainResource函数里去加载url返回的数据,如访问一个网站则返回html文本:

把参数里的m_request打印出来,在这个函数里面加一行代码:

并重新编译Chrome运行,控制台输出:

[22731:775:0107/224014.494114:INFO:DocumentLoader.cpp(719)] request url is: “file:///Users/yincheng/demo.html”

可以看到,这个url确实是我们传进的参数。

发请求后,每次收到的数据块,会通过Blink封装的IPC进程间通信,触发DocumentLoader的dataReceived函数,里面会去调它commitData函数,开始处理具体业务逻辑:

这个函数关键行是最2行和第7行,ensureWriter这个函数会去初始化上面画的UML图的解析器HTMLDocumentParser (Parser),并实例化document对象,这些对象都是通过实例m_writer去带动的。也就是说,writer会去实例化Parser之后,第7行writer传递数据给Parser去解析。

检查一下收到的数据bytes是什么东西:

可以看到bytes就是请求返回的html文本。

在ensureWriter函数里面有个判断:

如果m_writer已经初始化过了,则直接返回。也就是说Parser和document只会初始化一次。

在上面的addData函数里面,会启动一条线程执行Parser的任务:

并把数据传递给这条线程进行解析,Parser一旦收到数据就会序列成tokens,再构建DOM树。

2. 构建tokens

这里我们只要关注序列化后的token是什么东西就好了,为此,写了一个函数,把tokens的一些关键信息打印出来:

打印出来的结果:

这些内容有标签名、类型、属性和innerText,标签之间的文本(换行和空白)也会被当作一个标签处理。Chrome总共定义了7种标签类型:

有了一个根结点document和一些格式化好的tokens,就可以构建dom树了。

3. 构建DOM树

 

(1)DOM结点

在研究这个过程之前,先来看一下一个DOM结点的数据结构是怎么样的。以p标签HTMLParagraphElement为例,画出它的UML图,如下所示:

Node是最顶层的父类,它有三个指针,两个指针分别指向它的前一个结点和后一个结点,一个指针指向它的父结点;

ContainerNode继承于Node,添加了两个指针,一个指向第一个子元素,另一个指向最后一个子元素;

Element又添加了获取dom结点属性、clientWidth、scrollTop等函数

HTMLElement又继续添加了Translate等控制,最后一级的子类HTMLParagraphElement只有一个创建的函数,但是它继承了所有父类的属性。

需要提到的是每个Node都组合了一个treeScope,这个treeScope记录了它属于哪个document(一个页面可能会嵌入iframe)。

构建DOM最关键的步骤应该是建立起每个结点的父子兄弟关系,即上面提到的成员指针的指向。

到这里我们可以先回答上面提出的第一个问题,什么是浏览器内核

(2)浏览器内核

浏览器内核也叫渲染引擎,上面已经看到了Chrome是如何实例化一个P标签的,而从firefox的源码里面P标签的依赖关系是这样的:

在代码实现上和Chrome没有任何关系。这就好像W3C出了道题,firefox给了一个解法,取名为Gecko,Safari也给了自己的答案,取名Webkit,Chrome觉得Safari的解法比较好直接拿过来用,又结合自身的基础又封装了一层,取名Blink。由于W3C出的这道题“开放性”比较大,出的时间比较晚,导致各家实现各有花样。

明白了这点后,继续DOM构建。下面开始不再说Chrome,叫Webkit或者Blink应该更准确一点

(3)处理开始步骤

Webkit把tokens序列好之后,传递给构建的线程。在HTMLDocumentParser::processTokenizedChunkFromBackgroundParser的这个函数里面会做一个循环,把解析好的tokens做一个遍历,依次调constructTreeFromCompactHTMLToken进行处理。

根据上面的输出,最开始处理的第一个token是docType的那个:

在那个函数里面,首先Parser会调TreeBuilder的函数:

然后在TreeBuilder里面根据token的类型做不同的处理:

它会对不同类型的结点做相应处理,从上往下依次是文本节点、doctype节点、开标签、闭标签。doctype这个结点比较特殊,单独作为一种类型处理

(3)DOCType处理

在Parser处理doctype的函数里面调了HTMLConstructionSite的插入doctype的函数:

在这个函数里面,它会先创建一个doctype的结点,再创建插dom的task,并设置文档类型:

我们来看一下不同的doctype对文档类型的设置有什么影响,如下:

如果tagName不是html,那么文档类型将会是怪异模式,以下两种就会是怪异模式:

而常用的html4写法:

在源码里面这个将是有限怪异模式:

上面的systemId就是”http://www.w3.org/TR/html4/loose.dtd”,它不是空的,所以判断成立。而如果systemId为空,则它将是怪异模式。如果既不是怪异模式,也不是有限怪异模式,那么它就是标准模式:

常用的html5的写法就是标准模式,如果连DOCType声明也没有呢?那么会默认设置为怪异模式:

这些模式有什么区别,从源码注释可窥探一二:

大意是说,怪异模式会模拟IE,同时CSS解析会比较宽松,例如数字单位可以省略,而有限怪异模式和标准模式的唯一区别在于在于对inline元素的行高处理不一样。标准模式将会让页面遵守文档规定。

怪异模式下的input和textarea的默认盒模型将会变成border-box:

标准模式下的文档高度是实际内容的高度:

而在怪异模式下的文档高度是窗口可视域的高度:

在有限怪异模式下,div里面的图片下方不会留空白,如下图左所示;而在标准模式下div下方会留点空白,如下图右所示:

 

 

 

 

 

这个空白是div的行高撑起来的,当把div的行高设置成0的时候,就没有下面的空白了。在怪异模和有限怪异模式下,为了计算行内子元素的最小高度,一个块级元素的行高必须被忽略。

这里的叙述虽然跟解读源码没有直接的关系(我们还没解读到CSS处理),但是很有必要提一下。

接下来我们开始正式说明DOM构建

(4)开标签处理

下一个遇到的开标签是<html>标签,处理这个标签的任务应该是实例化一个HTMLHtmlElement元素,然后把它的父元素指向document。Webkit源码里面使用了一个m_attachmentRoot的变量记录attach的根结点,初始化HTMLConstructionSite也会初始化这个变量,值为document:

所以html结点的父结点就是document,实际的操作过程是这样的:

第二行先创建一个html结点,第三行把它加到一个任务队列里面,传递两个参数,第一个参数是父结点,第二个参数是当前结点,第五行执行队列里面的任务。代码第四行会把它压到一个栈里面,这个栈存放了未遇到闭标签的所有开标签。

第三行attachLater是如何建立一个task的:

代码逻辑比较简单,比较有趣的是发现DOM树有一个最大的深度:maximumHTMLParserDOMTreeDepth,超过这个最大深度就会把它子元素当作父无素的同级节点,这个最大值是多少呢?512:

我们重点关注executeQueuedTasks干了些什么,它会根据task的类型执行不同的操作,由于本次是insert的,它会去执行一个插入的函数:

在插入里面它会先去检查父元素是否支持子元素,如果不支持,则直接返回,就像video标签不支持子元素。然后再去调具体的插入:

上面代码第二行,设置子元素的父结点,也就是会把html结点的父结点指向document,然后如果没有lastChild,会将这个子元素作为firstChild,由于上面已经有一个docype的子结点了,所以已经有lastChild了,因此会把这个子元素的previousSibling指向老的lastChild,老的lastChild的nexSibling指向它。最后倒数第二行再把子元素设置为当前ContainerNode(即document)的lastChild。这样就建立起了html结点的父子兄弟关系。

可以看到,借助上一次的m_lastChild建立起了兄弟关系

这个时候你可能会有一个问题,为什么要用一个task队列存放将要插入的结点呢,而不是直接插入呢?一个原因是放到task里面方便统一处理,并且有些task可能不能立即执行,要先存起来。不过在我们这个案例里面都是存完后下一步就执行了。

 

当遇到head标签的token时,也是先创建一个head结点,然后再创建一个task,插到队列里面:

attachLater传参的第一个参数为父结点,这个currentNode为开标签栈里面的最顶的元素:

我们刚刚把html元素压了进去,则栈顶元素为html元素,所以head的父结点就为html。所以每当遇到一个开标签时,就把它压起来,下一次再遇到一个开标签时,它的父元素就是上一个开标签。

所以,初步可以看到,借助一个栈建立起了父子关系

而当遇到一个闭标签呢?

(5)处理闭标签

当遇到一个闭标签时,会把栈里面的元素一直pop出来,直到pop到第一个和它标签名字一样的:

我们第一个遇到的是闭标签是head标签,它会把开的head标签pop出来,栈里面就剩下html元素了,所以当再遇到body时,html元素就是body的父元素了。

这个是栈的一个典型应用。

以下面的html为例来研究压栈和出栈的过程:

把push和pop打印出来是这样的:

这个过程确实和上面的描述一致,遇到一个闭标签就把一次的开标签pop出来。

并且可以发现遇到body闭标签后,并不会把body给pop出来,因为如果body闭标签后面又再写了标签的话,就会自动当成body的子元素。

假设上面的b标签的闭标签忘记写了,又会发生什么:

打印出来的结果是这样的:

同样地,在上面第3行,遇到P闭标签时,会把所有的开标签pop出来,直到遇到P标签。不同的是后续的过程中会不断地插入b标签,最后渲染的页面结构:

因为b等带有格式化的标签会特殊处理,遇到一个开标签时会它们放到一个列表里面:

遇到一个闭标签时,又会从这个列表里面删掉。每处理一个新标签时就会进行检查和这个列表和栈里的开标签是否对应,如果不对应则会reconstruct:重新插入一个开标签。因此b就不断地被重新插入,直到遇到下一个b的闭标签为止。

如果上面少写的是一个span,那么渲染之后的结果是正常的:

而对于文本节点是实例化了Text的对象,这里不再展开讨论。

(6)自定义标签的处理

在浏览器里面可以看到,自定义标签默认不会有任何的样式,并且它默认是一个行内元素:

初步观察它和span标签的表现是一样的:

在blink的源码里面,不认识的标签默认会被实例化成一个HTMLUnknownElement,这个类对外提供了一个create函数,这和HTMLSpanElement是一样的,只有一个create函数,并且大家都是继承于HTMLElement。并且创建span标签的时候和unknown一样,并没有做特殊处理,直接调的create。所以从本质上来说,可以把自定义的标签当作一个span看待。然后你可以再设置display: block改成块级元素之类的。

但是你可以用js定义一个自定义标签,定义它的属性等,Webkit会去读它的定义:

例如给自定义标签创建一个原生属性:

上面定义了一个country,为了可以直接获取这个属性:

注册一个自定义标签:

这个HighSchoolElement继承于HTMLElement:

就可以直接取到contry这个属性,而不用通过getAttribute的函数,并且可以在属性发生变化时更新元素的渲染,改变color等。详见Custom Elements – W3C.

通过这种方式创建的,它就不是一个HTMLUnknownElement了。blink通过V8引擎把js的构造函数转化成C++的函数,实例化一个HTMLElement的对象。

最后再来看查DOM的过程

4. 查DOM过程

(1)按ID查找

在页面添加一个script:

Chrome的V8引擎把js代码层层转化,最后会调:

而这个函数又会调TreeScope的getElementById的函数,TreeScope存储了一个m_map的哈希map,这个map以标签id字符串作为key值,Element为value值,我们可以把这个map打印出来:

html结构是这样的:

打印出来的结果为:

可以看到, 这个m_map把页面所有有id的标签都存了进来。由于map的查找时间复杂度为O(1),所以使用ID选择器可以说是最快的。

再来看一下类选择器:

(2)类选择器

js如下:

在执行第一行的时候,Webkit返回了一个ClassCollection的列表:

而这个列表并不是去查DOM获取的,它只是记录了className作为标志。这与我们的认知是一致的,这种HTMLCollection的数据结构都是在使用的时候才去查DOM,所以在上面第二行去获取它的length,就会触发它的查DOM,在nodeCount这个函数里面执行:

第一行先获取符合collection条件的第一个结点,然后不断获取下一个符合条件的结点,直到null,并把它存到一个cachedList里面,下次再获取这个collection的东西时便不用再重复查DOM,只要cached仍然是有效的:

怎么样找到有效的节点呢:

第一行先获取第一个节点,如果它没有match,则继续next,直到找到符合条件或者空为止。我们的重点在于,它是怎么遍历的,如何next获取下一个节点,核心代码:

第一行先判断当前节点有没有子元素,如果有的话返回它的第一个子元素,如果当前节点没有子元素,并且这个节点就是开始找的根元素(用document.getElement*,则为document),则说明没有下一个元素了,直接返回0/null。如果这个节点不是根元素了(例如已经到了子元素这一层),那么看它有没有相邻元素,如果有则返回下一个相邻元素,如果相邻无素也没有了,由于它是一个叶子结点(没有子元素),说明它已经到了最深的一层,并且是当前层的最后一个叶子结点,那么就返回它的父元素的下一个相邻节点,如果这个也没有了,则返回null,查找结束。可以看出这是一个深度优先的查找

(3)querySelector

a)先来看下selector为一个id时发生了什么:

它会调ContainerNode的querySelecotr函数:

先把输入的selector字符串序列化成一个selectorQuery,然后再queryFirst,通过打断点可以发现,它最后会调的TreeScope的getElementById:

b)如果selector为一个class:

它会从document开始遍历:

我们重点查看它是怎么遍历,即第一行的for循环。表面上看它好像把所有的元素取出来然后做个循环,其实不然,它是重载++操作符:

只要我们看下next是怎么操作的就可以得知它是怎么遍历,而这个next跟上面的讲解class时是调的同一个next。不一样的是match条件判断是:有className,并且className列表里面包含这个class,如上面代码第二行。

c)复杂选择器

例如写两个class:

最终也会转成一个遍历,只是判断是否match的条件不一样:

怎么判断是否match比较复杂,这里不再展开讨论。

同时在源码可以看到,如果是怪异模式,会调一个executeSlow的查询,并且判断match条件也不一样。不过遍历是一样的。

 

查看源码确实是一件很费时费力的工作,但是通过一番探索,能够了解浏览器的一些内在机制,至少已经可以回答上面提出来的几个问题。同时知道了Webkit/Blink借助一个栈,结合开闭标签,一步步构建DOM树,并对DOCType的标签、自定义标签的处理有了一定的了解。最后又讨论了查DOM的几种情况,明白了查找的过程。

通过上面的分析,对页面渲染的第一步构建DOM应该会有一个基础的了解。

 

Android中微信抢红包助手的实现 - CKTim - 博客园

mikel阅读(1015)

来源: Android中微信抢红包助手的实现 – CKTim – 博客园

参考(感谢作者):http://www.jianshu.com/p/cd1cd53909d7

http://blog.csdn.net/jiangwei0910410003/article/details/48895153

 

实现原理

通过利用AccessibilityService辅助服务,监测屏幕内容,如监听状态栏的信息,屏幕跳转等,以此来实现自动拆红包的功能。关于AccessibilityService辅助服务,可以自行百度了解更多。

 

代码基础:

1.首先声明一个RedPacketService继承自AccessibilityService,该服务类有两个方法必须重写,如下:

复制代码
/**
 * Created by cxk on 2017/2/3.
 * email:471497226@qq.com
 *
 * 抢红包服务类
 */

public class RedPacketService extends AccessibilityService {


    /**
     * 必须重写的方法:此方法用了接受系统发来的event。在你注册的event发生是被调用。在整个生命周期会被调用多次。
     */
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {

    }
    /**
     * 必须重写的方法:系统要中断此service返回的响应时会调用。在整个生命周期会被调用多次。
     */
    @Override
    public void onInterrupt() {
        Toast.makeText(this, "我快被终结了啊-----", Toast.LENGTH_SHORT).show();
    }
    /**
     * 服务已连接
     */
    @Override
    protected void onServiceConnected() {
        Toast.makeText(this, "抢红包服务开启", Toast.LENGTH_SHORT).show();
        super.onServiceConnected();
    }
    /**
     * 服务已断开
     */
    @Override
    public boolean onUnbind(Intent intent) {
        Toast.makeText(this, "抢红包服务已被关闭", Toast.LENGTH_SHORT).show();
        return super.onUnbind(intent);
    }
}
复制代码

2.对我们的RedPacketService进行一些配置,这里配置方法可以选择代码动态配置(onServiceConnected里配置),也可以直接在res/xml下新建.xml文件,没有xml文件夹就新建。这里我们将文件命名为redpacket_service_config.xml,代码如下:

复制代码
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:description="@string/desc"
    android:notificationTimeout="100"
    android:packageNames="com.tencent.mm" />
复制代码

accessibilityEventTypes:   

响应哪一种类型的事件,typeAllMask就是响应所有类型的事件了,另外还有单击、长按、滑动等。

accessibilityFeedbackType:  

用什么方式反馈给用户,有语音播出和振动。可以配置一些TTS引擎,让它实现发音。

packageNames:

指定响应哪个应用的事件。这里我们是写抢红包助手,就写微信的包名:com.tencent.mm,这样就可以监听微信产生的事件了。

notificationTimeout:

响应时间

description:

辅助服务的描述信息。

 

3.service是四大组件之一,需要在AndroidManifest进行配置,注意这里稍微有些不同:

复制代码
 <!--抢红包服务-->
        <service
            android:name=".RedPacketService"
            android:enabled="true"
            android:exported="true"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/redpacket_service_config"></meta-data>
        </service>
复制代码
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"  权限申请
android:resource="@xml/redpacket_service_config"  引用刚才的配置文件


核心代码:
我们的红包助手,核心思路分为三步走:
监听通知栏微信消息,如果弹出[微信红包]字样,模拟手指点击状态栏跳转到微信聊天界面→在微信聊天界面查找红包,如果找到则模拟手指点击打开,弹出打开红包界面→模拟手指点击红包“開”

1.监听通知栏消息,查看是否有[微信红包]字样,代码如下:
复制代码
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
            //通知栏来信息,判断是否含有微信红包字样,是的话跳转
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
                List<CharSequence> texts = event.getText();
                for (CharSequence text : texts) {
                    String content = text.toString();
                    if (!TextUtils.isEmpty(content)) {
                        //判断是否含有[微信红包]字样
                        if (content.contains("[微信红包]")) {
                            //如果有则打开微信红包页面
                            openWeChatPage(event);
                        }
                    }
                }
                break;
     }
 }

     /**
     * 开启红包所在的聊天页面
     */
    private void openWeChatPage(AccessibilityEvent event) {
        //A instanceof B 用来判断内存中实际对象A是不是B类型,常用于强制转换前的判断
        if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
            Notification notification = (Notification) event.getParcelableData();
            //打开对应的聊天界面
            PendingIntent pendingIntent = notification.contentIntent;
            try {
                pendingIntent.send();
            } catch (PendingIntent.CanceledException e) {
                e.printStackTrace();
            }
        }
    }
复制代码
2.判断当前是否在微信聊天页面,是的话遍历当前页面各个控件,找到含有微信红包或者领取红包的textview控件,然后逐层找到他的可点击父布局(图中绿色部分),模拟点击跳转到含有“開”的红包界面,代码如下:

复制代码
 @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
            //窗口发生改变时会调用该事件
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                String className = event.getClassName().toString();
                //判断是否是微信聊天界面
                if ("com.tencent.mm.ui.LauncherUI".equals(className)) {
                    //获取当前聊天页面的根布局
                    AccessibilityNodeInfo rootNode = getRootInActiveWindow();
                    //开始找红包
                    findRedPacket(rootNode);
                }
        }
    }
    /**
     * 遍历查找红包
     */
    private void findRedPacket(AccessibilityNodeInfo rootNode) {
        if (rootNode != null) {
            //从最后一行开始找起
            for (int i = rootNode.getChildCount() - 1; i >= 0; i--) {
                AccessibilityNodeInfo node = rootNode.getChild(i);
                //如果node为空则跳过该节点
                if (node == null) {
                    continue;
                }
                CharSequence text = node.getText();
                if (text != null && text.toString().equals("领取红包")) {
                    AccessibilityNodeInfo parent = node.getParent();
                    //while循环,遍历"领取红包"的各个父布局,直至找到可点击的为止
                    while (parent != null) {
                        if (parent.isClickable()) {
                            //模拟点击
                            parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                            //isOpenRP用于判断该红包是否点击过
                            isOpenRP = true;
                            break;
                        }
                        parent = parent.getParent();
                    }
                }
                //判断是否已经打开过那个最新的红包了,是的话就跳出for循环,不是的话继续遍历
                if (isOpenRP) {
                    break;
                } else {
                    findRedPacket(node);
                }

            }
        }
    }
复制代码

3.点击红包后,在模拟手指点击“開”以此开启红包,跳转到红包详情界面,方法与步骤二类似:

复制代码
 @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
            //窗口发生改变时会调用该事件
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                String className = event.getClassName().toString();
          
                //判断是否是显示‘开’的那个红包界面
                if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI".equals(className)) {
                    AccessibilityNodeInfo rootNode = getRootInActiveWindow();
                    //开始抢红包
                    openRedPacket(rootNode);
                }
                break;
        }
    }

    /**
     * 开始打开红包
     */
    private void openRedPacket(AccessibilityNodeInfo rootNode) {
        for (int i = 0; i < rootNode.getChildCount(); i++) {
            AccessibilityNodeInfo node = rootNode.getChild(i);
            if ("android.widget.Button".equals(node.getClassName())) {
                node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
            openRedPacket(node);
        }
    }
复制代码

结合以上三步,下面是完整代码,注释已经写的很清楚,直接看代码:

复制代码
package com.cxk.redpacket;

import android.accessibilityservice.AccessibilityService;
import android.app.Instrumentation;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;

import java.util.List;

/**
 * 抢红包Service,继承AccessibilityService
 */
public class RedPacketService extends AccessibilityService {
    /**
     * 微信几个页面的包名+地址。用于判断在哪个页面
     * LAUCHER-微信聊天界面
     * LUCKEY_MONEY_RECEIVER-点击红包弹出的界面
     * LUCKEY_MONEY_DETAIL-红包领取后的详情界面
     */
    private String LAUCHER = "com.tencent.mm.ui.LauncherUI";
    private String LUCKEY_MONEY_DETAIL = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI";
    private String LUCKEY_MONEY_RECEIVER = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI";

    /**
     * 用于判断是否点击过红包了
     */
    private boolean isOpenRP;

    private boolean isOpenDetail=false;

    /**
     * 用于判断是否屏幕是亮着的
     */
    private boolean isScreenOn;

    /**
     * 获取PowerManager.WakeLock对象
     */
    private  PowerManager.WakeLock wakeLock;

    /**KeyguardManager.KeyguardLock对象
     *
     */
    private KeyguardManager.KeyguardLock keyguardLock;

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
            //通知栏来信息,判断是否含有微信红包字样,是的话跳转
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
                Log.e("SSSSSS0","通知栏来信息TYPE_WINDOW_STATE_CHANGED");
                List<CharSequence> texts = event.getText();
                for (CharSequence text : texts) {
                    String content = text.toString();
                    if (!TextUtils.isEmpty(content)) {
                        //判断是否含有[微信红包]字样
                        if (content.contains("[微信红包]")) {
                            if(!isScreenOn()){
                                wakeUpScreen();
                            }
                            //如果有则打开微信红包页面
                            openWeChatPage(event);

                            isOpenRP = false;
                        }
                    }
                }
                break;
            //界面跳转的监听
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                String className = event.getClassName().toString();
                //判断是否是微信聊天界面
                if (LAUCHER.equals(className)) {
                    //获取当前聊天页面的根布局
                    AccessibilityNodeInfo rootNode = getRootInActiveWindow();
                    //开始找红包
                    findRedPacket(rootNode);
                }

                //判断是否是显示‘开’的那个红包界面
                if (LUCKEY_MONEY_RECEIVER.equals(className)) {
                    AccessibilityNodeInfo rootNode = getRootInActiveWindow();
                    //开始抢红包
                    openRedPacket(rootNode);
                }

                //判断是否是红包领取后的详情界面
                if (isOpenDetail&&LUCKEY_MONEY_DETAIL.equals(className)) {

                    isOpenDetail=false;
                    //返回桌面
                    back2Home();
                }
                break;
        }

        release();

    }

    /**
     * 开始打开红包
     */
    private void openRedPacket(AccessibilityNodeInfo rootNode) {
        for (int i = 0; i < rootNode.getChildCount(); i++) {
            AccessibilityNodeInfo node = rootNode.getChild(i);
            if ("android.widget.Button".equals(node.getClassName())) {
                node.performAction(AccessibilityNodeInfo.ACTION_CLICK);

                isOpenDetail=true;
            }
            openRedPacket(node);
        }
    }

    /**
     * 遍历查找红包
     */
    private void findRedPacket(AccessibilityNodeInfo rootNode) {
        if (rootNode != null) {
            //从最后一行开始找起
            for (int i = rootNode.getChildCount() - 1; i >= 0; i--) {
                AccessibilityNodeInfo node = rootNode.getChild(i);
                //如果node为空则跳过该节点
                if (node == null) {
                    continue;
                }
                CharSequence text = node.getText();
                if (text != null && text.toString().equals("领取红包")) {
                    AccessibilityNodeInfo parent = node.getParent();
                    //while循环,遍历"领取红包"的各个父布局,直至找到可点击的为止
                    while (parent != null) {
                        if (parent.isClickable()) {
                            //模拟点击
                            parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                            //isOpenRP用于判断该红包是否点击过
                            isOpenRP = true;
                            break;
                        }
                        parent = parent.getParent();
                    }
                }
                //判断是否已经打开过那个最新的红包了,是的话就跳出for循环,不是的话继续遍历
                if (isOpenRP) {
                    break;
                } else {
                    findRedPacket(node);
                }

            }
        }
    }

    /**
     * 开启红包所在的聊天页面
     */
    private void openWeChatPage(AccessibilityEvent event) {
        //A instanceof B 用来判断内存中实际对象A是不是B类型,常用于强制转换前的判断
        if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
            Notification notification = (Notification) event.getParcelableData();
            //打开对应的聊天界面
            PendingIntent pendingIntent = notification.contentIntent;
            try {
                pendingIntent.send();
            } catch (PendingIntent.CanceledException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 服务连接
     */
    @Override
    protected void onServiceConnected() {
        Toast.makeText(this, "抢红包服务开启", Toast.LENGTH_SHORT).show();
        super.onServiceConnected();
    }

    /**
     * 必须重写的方法:系统要中断此service返回的响应时会调用。在整个生命周期会被调用多次。
     */
    @Override
    public void onInterrupt() {
        Toast.makeText(this, "我快被终结了啊-----", Toast.LENGTH_SHORT).show();
    }

    /**
     * 服务断开
     */
    @Override
    public boolean onUnbind(Intent intent) {
        Toast.makeText(this, "抢红包服务已被关闭", Toast.LENGTH_SHORT).show();
        return super.onUnbind(intent);
    }

    /**
     * 返回桌面
     */
    private void back2Home() {
        Intent home = new Intent(Intent.ACTION_MAIN);
        home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        home.addCategory(Intent.CATEGORY_HOME);
        startActivity(home);
    }

    /**
     * 判断是否处于亮屏状态
     *
     * @return true-亮屏,false-暗屏
     */
    private boolean isScreenOn() {
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        isScreenOn = pm.isInteractive();
        return isScreenOn;
    }

    /**
     * 解锁屏幕
     */
    private void wakeUpScreen() {

        //获取电源管理器对象
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        //后面的参数|表示同时传入两个值,最后的是调试用的Tag
        wakeLock = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_DIM_WAKE_LOCK, "bright");

        //点亮屏幕
        wakeLock.acquire();

        //得到键盘锁管理器
        KeyguardManager km= (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
        keyguardLock=km.newKeyguardLock("unlock");

        //解锁
        keyguardLock.disableKeyguard();
    }

    /**
     * 释放keyguardLock和wakeLock
     */
    public void release(){
        if(wakeLock!=null){
            wakeLock.release();
            wakeLock=null;
        }
        if(keyguardLock!=null){
//            keyguardLock.reenableKeyguard();
            keyguardLock=null;
        }
    }

}
复制代码

 

使用方法:

设置-辅助功能-无障碍-点击RedPacket开启即可

 

已知问题:

1.聊天列表或者聊天界面中无法直接自动抢红包

Demo下载地址:https://github.com/CKTim/RedPacket

Android几行代码实现实时监听微信聊天 - CKTim - 博客园

mikel阅读(1376)

来源: Android几行代码实现实时监听微信聊天 – CKTim – 博客园

实现效果:

实时监听当前聊天页面的最新一条消息,如图:

          

 

实现原理:

同样是利用AccessibilityService辅助服务,关于这个服务类还不了解的同学可以先看下我上一篇关于抢红包的博客,原理都一样:

http://www.cnblogs.com/cxk1995/p/6363574.html

1.首先我们先来看一下微信聊天界面的布局,查看方法:

AndroidStudio–Tools–Android–Android Device Monitor,点击:

 

2.如图我们可以看到,其实每一条微信聊天记录都是一个RelativeLayout:

 

3.再往下看,我们又可以发现,其实每一个RelativeLayout下面,又包含了一个TextView,还有一个LinearLayout

TextView就是聊天的时间

LinearLayout下则包含了我们所需要的聊天对象以及聊天信息,目前我们只需要这个就行了。

 

4.分析完后,我们思路就有了:

首先遍历获取每个RelativeLayout下的LinearLayout,因为该LinearLayout存在resource-id(com.tencent.mm:id/o),所以我们可以很容易可以获取到,然后我们再在LinearLayout中查找含有聊天对象(resource-id:com.tencent.mm:id/i_)以及聊天内容(resource-id:com.tencent.mm:id/ib)。

注:关于resource-id直接在上一步的查看布局下发可看到,因为resource-id随着版本的迭代可能会发生改变,所以也导致了一些不稳定因素。

 

核心代码

代码不多,也加了注释,直接看代码即可:

复制代码
package com.cxk.wechatlog;

import android.accessibilityservice.AccessibilityService;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;

import java.util.List;

/**
 * Created by cxk on 2017/2/4.
 * email:471497226@qq.com
 * <p>
 * 获取即时微信聊天记录服务类
 */

public class WeChatLogService extends AccessibilityService {

    /**
     * 聊天对象
     */
    private String ChatName;
    /**
     * 聊天最新一条记录
     */
    private String ChatRecord = "test";

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
            //每次在聊天界面中有新消息到来时都出触发该事件
            case AccessibilityEvent.TYPE_VIEW_SCROLLED:
                //获取当前聊天页面的根布局
                AccessibilityNodeInfo rootNode = getRootInActiveWindow();
                //获取聊天信息
                getWeChatLog(rootNode);
                break;
        }

    }

    /**
     * 遍历
     *
     * @param rootNode
     */

    private void getWeChatLog(AccessibilityNodeInfo rootNode) {
        if (rootNode != null) {
            //获取所有聊天的线性布局
            List<AccessibilityNodeInfo> listChatRecord = rootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/o");
            if(listChatRecord.size()==0){
                return;
            }
            //获取最后一行聊天的线性布局(即是最新的那条消息)
            AccessibilityNodeInfo finalNode = listChatRecord.get(listChatRecord.size() - 1);
            //获取聊天对象list(其实只有size为1)
            List<AccessibilityNodeInfo> imageName = finalNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/i_");
            //获取聊天信息list(其实只有size为1)
            List<AccessibilityNodeInfo> record = finalNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/ib");
            if (imageName.size() != 0) {
                if (record.size() == 0) {
                    //判断当前这条消息是不是和上一条一样,防止重复
                    if (!ChatRecord.equals("对方发的是图片或者表情")) {
                        //获取聊天对象
                        ChatName = imageName.get(0).getContentDescription().toString().replace("头像", "");
                        //获取聊天信息
                        ChatRecord = "对方发的是图片或者表情";

                        Log.e("AAAA", ChatName + ":" + "对方发的是图片或者表情");
                        Toast.makeText(this, ChatName + ":" + ChatRecord, Toast.LENGTH_SHORT).show();
                    }
                } else {
                    //判断当前这条消息是不是和上一条一样,防止重复
                    if (!ChatRecord.equals(record.get(0).getText().toString())) {
                        //获取聊天对象
                        ChatName = imageName.get(0).getContentDescription().toString().replace("头像", "");
                        //获取聊天信息
                        ChatRecord = record.get(0).getText().toString();

                        Log.e("AAAA", ChatName + ":" + ChatRecord);
                        Toast.makeText(this, ChatName + ":" + ChatRecord, Toast.LENGTH_SHORT).show();
                    }
                }
            }
        }
    }

    /**
     * 必须重写的方法:系统要中断此service返回的响应时会调用。在整个生命周期会被调用多次。
     */
    @Override
    public void onInterrupt() {
        Toast.makeText(this, "我快被终结了啊-----", Toast.LENGTH_SHORT).show();
    }

    /**
     * 服务开始连接
     */
    @Override
    protected void onServiceConnected() {
        Toast.makeText(this, "服务已开启", Toast.LENGTH_SHORT).show();
        super.onServiceConnected();
    }

    /**
     * 服务断开
     *
     * @param intent
     * @return
     */
    @Override
    public boolean onUnbind(Intent intent) {
        Toast.makeText(this, "服务已被关闭", Toast.LENGTH_SHORT).show();
        return super.onUnbind(intent);
    }
}
复制代码

 

使用方法:

设置-辅助功能-无障碍-点击WeChatLog开启即可

 

写在最后:

个人兴趣研究,不建议用在非法途径上!!Demo想要的话留言我发给你嘻嘻….

WordPress出现“Error establishing a database connection”解决办法 | Lin's Space|Only

mikel阅读(1494)

来源: WordPress出现“Error establishing a database connection”解决办法 | Lin’s Space|Only

今天在访问可爱淘(keaitao.net)的时候出现“Error establishing a database connection ”,这段话意思是连接数据时出现错误。
于是就有了下面的解决过程。。

WordPress出现这种情况的原因可能是:

1、无法连接到Wordpress使用的数据库。

你的wp-config.php中的数据库用户名和密码错误,或者你的数据库所在服务器挂掉了!也有可能是服务商短时间内限制(访问量突然暴涨的博客或使用一些消耗数据库连接的WP插件)。

解决办法是,确认wp-config.php中的用户名和密码信息正确。确认WP使用的数据库服务器没有挂掉(可以ping或直接重启?!)。确认数据库连接方式,是远程连接还是本地连接,有些配置的远程连接的可能出现连不上数据库的情况,也就会出现这个“Error establishing a database connection”提示,一般虚拟空间(GD的空间使用的远程数据库连接)或合租空间都是使用的本地连接(localhost)。

2、若服务器数据库是刚刚升级到 mySQL 5.0 以上,之前WP博客正常可访问。

这可能是由于mySQL 5.0采用了一种新的密码验证机制,这需要客户端的版本要在4.0以上(PHP中的MYSQL客户端可以查看phpinfo得到),连接数据库时是用旧的密码机制,这会出现“Client does not support authentication protocol requested by server” 这样的错误提示,wordpress中就提示“Error establishing a database connection (可能密码错误)”。

解决办法:

方法1、在mysql中为wordpress数据库新建一个控制账户,然后修改wp-config.php中的数据库用户名和密码配置信息即可。方便简单!!

方法2、在mysql下:

mysql> SET PASSWORD FOR
-> ‘clin003’@’clin003.com’ = OLD_PASSWORD(‘newpwd’);
mysql> FLUSH PRIVILEGES;
这里的clin003替换成使用wordpress数据库的用户名,clin003.com替换成localhost或者某个域名,newpwd是该用户对应的密码。

还有一点是,查看phpinfo(),找MYSQL_SOCKET显示的mysql.sock的路径,确认mysql.sock的确在此路径下,否则需要做一个符号链接。比如phpinfo里显示MYSQL_SOCKET路径为/var/lib/mysql/mysql.sock,但实际上位于 /tmp/mysql.sock,可在命令行输入:

ln -s /tmp/mysql.sock /var/lib/mysql/mysql.sock

3、mysql服务设置不当,导致mysql连接数量超过限定值,或者应用程序设计有问题,导致大量休眠连接不能及时释放。

解决办法:
登陆mysql服务器

mysql -u root -p

使用命令

show processlist;

查看当前连接数量。
如果进程过多,或存在大量超时的休眠sleep连接,编辑/etc/mysql/my.cnf

vim /etc/mysql/my.cnf

在[mysqld]字段处,添加,或去掉注释

max_connections = 500 #增加mysql连接数
wait_timeout = 10 #断开超过10秒的连接

4、是wp_options数据库表错误,我把整个数据库表修复和优化了一次

wp_options中的表前缀wp根据自己实际情况查看!

WordPress出现错误时,可以通过 Debug 参数调试:

根据Wp-config.php的调试功能排错

1、在 wp-config.php 有个 Debug 的参数,打开这个参数,修改为:

define(‘WP_Debug’, ‘true’);

2、根据报错提示解决问题,比如以下错误:

WordPress database error: [Table ‘./clin003_com/wp_options’ is marked as crashed and should be repaired]
SELECT autoload FROM wp_options WHERE option_name = ‘_transient_doing_cron’

WordPress database error: [Table ‘./clin003_com/wp_options’ is marked as crashed and should be repaired]
SELECT autoload FROM wp_options WHERE option_name = ‘_transient_timeout_doing_cron’

WordPress database error: [Table ‘./clin003_com/wp_options’ is marked as crashed and should be repaired]
SELECT option_value FROM wp_options WHERE option_name = ‘_transient_doing_cron’ LIMIT 1

wp_options 表坏了,需要被修复,使用 mysql 客户端。
进 mysql 使用修复的命令修复:

mysql>repair table wp_options;
也可以通过phpMyAdmin来进行修复。

3、问题解决,关掉 DEBUG。

根据mysql的日志记录排错

在mysql的log日志文件

mysql/data/

里面找到一个.err结尾的文件,打开。看见了一排

‘.\clin003_com\wp_options’ is marked as crashed and should be repaired
‘.\clin003_com\wp_options’ is marked as crashed and should be repaired
‘.\clin003_com\wp_options’ is marked as crashed and should be repaired

修复办法参考上面!

参考文档:

站长百科 Error establishing a database connection

关于最近Error establishing a database connection的问题

烦人的Error establishing a database connection!

已解決error establishing a database connection

Error establishing a database connection的解决方法

开源OSS.Social微信项目进阶介绍 - KevinCC - 博客园

mikel阅读(1344)

来源: 开源OSS.Social微信项目进阶介绍 – KevinCC – 博客园

  在开源OSS.Social微信项目解析的随笔中,我简单给大家分享了进行中微信项目的概要设计,主要在讲述解决思路和过程,没有详细实现和使用介绍。本着不能马虎的态度,这篇文章我来给大家分解一下项目结构,使用方式以及后边的规划,希望能够让大家通达晓畅。同时,当前项目正在进行 .Net Standard 类库的过渡,相信一段时间之后就可以同时对 .net framework 和 .net core 提供支持。

这个项目是整个OSS系列开源项目之一,主要是对 社交化产品的接口包装,而微信项目是当前正在进行的项目,在整个微信的项目中,包含三个大模块(请见上文,或见模块划分思维导图),这里对三个模块做一个分步讲解。

一.实时消息模块

当前模块主要是对实时消息的处理,其概要设计想法和相关流程图已经在上文中做了介绍,这里对项目的具体实现做下分解演示。

首先我介绍下消息模块的文件结构

当前模块主要在项目中的Msg文件夹下,对应的实体则在 Msg/Mos 文件夹中,文件夹下有三个类:

WxMsgBaseHandler(基础消息类型处理)

WxMsgHandler(高级扩展消息处理)

Cryptography(微信安全模式加密补位方法处理)

在实体文件夹中,主要包含 基类(BaseMos), 普通消息类型实体(RecMsgMos-基础消息接收实体,RecEventMsgMos-基础事件消息接收实体) 和 消息响应实体 (ReplyMsgMos-响应消息实体)三个部分,这里面大家主要需要关心的是 BaseMos中的(BaseRecMsg,BaseRecEventMsg) 和 BaseReplyMsg,分别对应着 正常消息、事件消息,以及响应消息的基类。

了解基本结构之后我们来看下具体调用方式,因为这个模块提供了两种调用模式,这里分别介绍:

1. 基础消息模式,这个模式主要是对微信常见的消息类型的调用,在RecMsgMos和RecEventMsgMos文件中的实体也主要是对 基础模式接收消息的定义,当前模式的适用场景是开发者不需要考虑例如卡券等特殊事件消息,使用当前模式即可,调用演示如下:

a. 自定义消息处理类WxBasicMsgService,继承自WxBasicMsgHandler,只需要给相关的事件委托Handler(请注意和事件消息的事件做区分)添加处理事件即可,传入的消息实体会根据不同委托事件一一对应,例如TextHandler对应的是TextRecMsg

 

当前基础消息类型实现对应的事件为:

正常消息:

TextHandler(文本消息)

ImageHandler(图片消息)

VoiceHandler(语音消息)

VideoHandler(视频)

LocationHandler(位置消息)

LinkHandler(链接消息)

事件消息:

SubscribeEventHandler(订阅事件)

ScanEventHandler(扫描事件)

LocationEventHandler(上报位置事件)

ClickEventHandler(点击菜单事件)

ViewEventHandler(点击菜单跳转事件)

b. 声明配置信息

c. 页面调用

2. 高级消息模式,这个模式包含了基础模式的所有事件,主要适用场景是对有特殊事件消息有定制的情况。其调用方式和基础模式不同在于自定义消息处理类部分,这里展示一个简单自定义高级模式处理类:

高级模式需要调用注册方法,RegisterMsgHandler-注册正常消息,RegisterEventMsgHandler-注册事件消息。第一个参数是消息类型名称,如果是事件消息,则是事件名称。第三个参数是具体的逻辑处理委托。第二个参数则是接收消息的实体类型,这里是举例所以我直接使用了LocationRecEventMsg,当然你也可以定义你需要的接收实体,正常消息继承自BaseRecMsg,事件消息继承自BaseRecEventMsg,举个例子:

public class CustomRecMsg : BaseRecMsg
{
protected override void FormatPropertiesFromMsg()
{
base.FormatPropertiesFromMsg();
Content = this[“Content”];
}
public string Content { get; set; }
}

如果对于消息模块还有其他疑问,可以添加osscoder公众号提问,晚上我会统一解决。

二.功能接口模块

  这个模块主要是公号内的功能性接口,调用时需要调用全局的AccessToken,接口文件主要在Offcial文件夹下,根据功能对象分类下属又做了子模块分类,文件结构和消息模块下的类似,不过需要注意的是同一个文件夹下有几个类文件,但是类名都是同一个,例如Basic文件加下有User,Menu,Media等多个api文件,但类名同为:WxOffBasicApi。

这里使用了Partail关键字,保证结构的清晰,同时保证调用时的简单。详细的分类可以见下面的结构:

也就是功能模块接口主要有以下几个:WxOffBasicApi,WxOffCardApi,WxOffStatApi(统计,简写了),WxOffStoreApi,WxOffAssist,直接声明实例调用即可,当然当前接口还没有完全结束,主要集中在图中未标状态的部分,如果想要使用没有的接口,这里提供一个WxOffBaseApi基类中的方法调用示例,只需要返回一个WxBaseResp或者它的子类即可,WxBaseResp中有IsSuccess属性,可以通过它来判断当前的返回结果:

当然文件的命名也有一个简单规则:

请求实体:Wx+ 动作 + 对象/模块名称 + Req/Resp (如:WxAddMemberCardReq)
对象实体:Wx + 对象/模块名称 + Mo (如:WxMemberCardMo)
接口类:Wx+Off+模块名称+Api

三. 社交接口模块

当前模块主要集中在Sns文件夹下,功能是面向用户相关的,微信的这块功能主要集中在授权和获取授权用户的基本信息,文件组成比较简单,里面提供了两种页面授权模式,PC扫码授权和手机微信端页面授权。调用方式也十分的简单,如图:

以上是几个部分的结构和调用方式介绍,所有实例代码都在OSS.Social.Samples中,可以去github或者 开源中国 下载查看。 

四.  OSS项目的规划

当前已经开源的项目有 OSS.Common ,  OSS.Http, OSS.Social。 后期的计划还会有支付,客服,和用户三个项目,最近两周我的主要安排是完成OSS.Social 项目,同时完成 以上项目的.Net Standard 项目,完成对.net core 的支持,也欢迎有共同爱好的同学前来贡献代码,完成这个简单,基础,实用的系列项目。

 

续:这里把实时消息的处理流程再做一次回顾,可以和使用介绍再对照体会一下

当你看到这里的时候,你已经看完了,十分感谢你的耐心,如果还有其他疑问欢迎关注公众号(osscoder)进行提问