[转载]C#开发微信门户及应用(5)--用户分组信息管理 - 伍华聪 - 博客园

mikel阅读(1016)

[转载]C#开发微信门户及应用(5)–用户分组信息管理 – 伍华聪 – 博客园.

在上个月的对C#开发微信门户及应用做了介绍,写过了几篇的随笔进行分享,由于时间关系,间隔了一段时间没有继续写这个系列的博客了,并不是对这个 方面停止了研究,而是继续深入探索这方面的技术,为了更好的应用起来,专心做好底层的技术开发。本篇继续上一篇的介绍,主要介绍分组管理方面的开发应用, 这篇的内容和上一篇,作为一个完整的用户信息和分组信息管理的组合。

1、用户分组管理内容

用户分组的引入,主要是方便管理关注者列表,以及方便向不同的组别发送消息的操作的,一个公众账号,最多支持创建500个分组。

用户分组管理,包含下面几个方面的内容:

1 创建分组
2 查询所有分组
3 查询用户所在分组
4 修改分组名
5 移动用户分组

微信对于创建分组的定义如下所示。

http请求方式: POST(请使用https协议)
https://api.weixin.qq.com/cgi-bin/groups/create?access_token=ACCESS_TOKEN
POST数据格式:json
POST数据例子:{"group":{"name":"test"}}

正常返回的结果如下所示。

{
    "group": {
        "id": 107, 
        "name": "test"
    }
}

其他接口,也是类似的方式,通过POST一些参数进去URL里面,获取返回的Json数据。

前面随笔定义了GroupJson的实体类信息如下所示。

///
/// 分组信息
///

public class GroupJson : BaseJsonResult
{
///
/// 分组id,由微信分配
///

public int id { get; set; }

///
/// 分组名字,UTF8编码
///

public string name { get; set; }
}

根据以上几个接口的定义,我定义了几个接口,并把它们归纳到用户管理的API接口里面。

///
/// 查询所有分组
///

///调用接口凭证 ///
List GetGroupList(string accessToken);

///
/// 创建分组
///

///调用接口凭证 ///分组名称 ///
GroupJson CreateGroup(string accessToken, string name);

///
/// 查询用户所在分组
///

///调用接口凭证 ///用户的OpenID ///
int GetUserGroupId(string accessToken, string openid);

///
/// 修改分组名
///

///调用接口凭证 ///分组id,由微信分配 ///分组名字(30个字符以内) ///
CommonResult UpdateGroupName(string accessToken, int id, string name);

///
/// 移动用户分组
///

///调用接口凭证 ///用户的OpenID ///分组id ///
CommonResult MoveUserToGroup(string accessToken, string openid, int to_groupid);

2、用户分组管理接口的实现

2.1 创建用户分组

为了解析如何实现创建用户分组的POST数据操作,我们来一步步了解创建用户的具体过程。

首先需要创建一个动态定义的实体类信息,它包含几个需要提及的属性,如下所示。

string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/create?access_token={0}", accessToken);
var data = new
{
group = new
{
name = name
}
};
string postData = data.ToJson();

其中我们把对象转换为合适的Json数据操作,放到了扩展方法ToJson里面了,这个主要就是方便把动态定义的实体类转换Json内容,主要就是调用Json.NET的序列号操作。

///
/// 把对象为json字符串
///

///待序列号对象 ///
public static string ToJson(this object obj)
{
return JsonConvert.SerializeObject(obj, Formatting.Indented);
}

准备好Post的数据后,我们就进一步看看获取数据并转换为合适格式的操作代码。

GroupJson group = null;
CreateGroupResult result = JsonHelper.ConvertJson(url, postData);
if (result != null)
{
group = result.group;
}

其中POST数据并转换为合适格式实体类的操作,放在了ConvertJson方法里面,这个方法的定义如下所示,里面的HttpHelper是我公用类库的辅助类,主要就是调用底层的httpWebRequest对象方法,进行数据的提交,并获取返回结果。

///
/// 转换Json字符串到具体的对象
///

///返回Json数据的链接地址 ///POST提交的数据 ///
public static T ConvertJson(string url, string postData)
{
HttpHelper helper = new HttpHelper();
string content = helper.GetHtml(url, postData, true);
VerifyErrorCode(content);

T result = JsonConvert.DeserializeObject(content);
return result;
}

这样,完整的创建用户分组的操作函数如下所示。

///
/// 创建分组
///

///调用接口凭证 ///分组名称 ///
public GroupJson CreateGroup(string accessToken, string name)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/create?access_token={0}", accessToken);
var data = new
{
group = new
{
name = name
}
};
string postData = data.ToJson();

GroupJson group = null;
CreateGroupResult result = JsonHelper.ConvertJson(url, postData);
if (result != null)
{
group = result.group;
}
return group;
}

2.2 查询所有分组

查询所有分组,可以把服务器上的分组全部获取下来,也就是每个分组的ID和名称。

///
/// 查询所有分组
///

///调用接口凭证 ///
public List GetGroupList(string accessToken)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/get?access_token={0}", accessToken);

List list = new List();
GroupListJsonResult result = JsonHelper.ConvertJson(url);
if (result != null && result.groups != null)
{
list.AddRange(result.groups);
}

return list;
}

2.3 查询用户所在分组

每个用户都属于一个分组,默认在 未分组 这个分组里面,我们可以通过API获取用户的分组信息,也就是获取所在用户分组的ID。

///
/// 查询用户所在分组
///

///调用接口凭证 ///用户的OpenID ///
public int GetUserGroupId(string accessToken, string openid)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/getid?access_token={0}", accessToken);
var data = new
{
openid = openid
};
string postData = data.ToJson();

int groupId = -1;
GroupIdJsonResult result = JsonHelper.ConvertJson(url, postData);
if (result != null)
{
groupId = result.groupid;
}
return groupId;
}

2.4 修改分组名称

也可以在实际中,调整用户所在的分组,操作代码如下。

///
/// 修改分组名
///

///调用接口凭证 ///分组id,由微信分配 ///分组名字(30个字符以内) ///
public CommonResult UpdateGroupName(string accessToken, int id, string name)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/update?access_token={0}", accessToken);
var data = new
{
group = new
{
id = id,
name = name
}
};
string postData = data.ToJson();

return Helper.GetExecuteResult(url, postData);
}

这里的返回值CommonResult是,一个实体类,包含了bool的成功与否的标志,以及String类型的错误信息(如果有的话)。

对于这个GetExecuteResult函数体,里面主要就是提交数据,然后获取结果,并根据结果进行处理的函数。

///
/// 通用的操作结果
///

///网页地址 ///提交的数据内容 ///
public static CommonResult GetExecuteResult(string url, string postData = null)
{
CommonResult success = new CommonResult();
try
{
ErrorJsonResult result;
if (postData != null)
{
result = JsonHelper.ConvertJson(url, postData);
}
else
{
result = JsonHelper.ConvertJson(url);
}

if (result != null)
{
success.Success = (result.errcode == ReturnCode.请求成功);
success.ErrorMessage = result.errmsg;
}
}
catch (WeixinException ex)
{
success.ErrorMessage = ex.Message;
}

return success;
}
}

上面红色部分的意思,就是转换为实体类的时候,如果错误是微信里面定义的,那么记录错误信息,其他异常我不处理(也就是抛出去)。

2.5 移动用户到新的分组

移动用户到新的分组的操作和上面小节的差不多,具体看代码。

///
/// 移动用户分组
///

///调用接口凭证 ///用户的OpenID ///分组id ///
public CommonResult MoveUserToGroup(string accessToken, string openid, int to_groupid)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token={0}", accessToken);
var data = new
{
openid = openid,
to_groupid = to_groupid
};
string postData = data.ToJson();

return Helper.GetExecuteResult(url, postData);
}

3、用户分组接口的调用

上面小节,定义并实现了用户分组的各类接口,所有的用户相关的都已经毫无保留贴出代码,它的调用操作如下代码所示(测试代码)。

private void btnGetGroupList_Click(object sender, EventArgs e)
{
IUserApi userBLL = new UserApi();
List list = userBLL.GetGroupList(token);
foreach (GroupJson info in list)
{
string tips = string.Format("{0}:{1}", info.name, info.id);
Console.WriteLine(tips);
}
}

private void btnFindUserGroup_Click(object sender, EventArgs e)
{
IUserApi userBLL = new UserApi();
int groupId = userBLL.GetUserGroupId(token, openId);

string tips = string.Format("GroupId:{0}", groupId);
Console.WriteLine(tips);
}

private void btnCreateGroup_Click(object sender, EventArgs e)
{
IUserApi userBLL = new UserApi();
GroupJson info = userBLL.CreateGroup(token, "创建测试分组");
if (info != null)
{
string tips = string.Format("GroupId:{0} GroupName:{1}", info.id, info.name);
Console.WriteLine(tips);

string newName = "创建测试修改";
CommonResult result = userBLL.UpdateGroupName(token, info.id, newName);
Console.WriteLine("修改分组名称:" + (result.Success ? "成功" : "失败:" + result.ErrorMessage));
}
}

private void btnUpdateGroup_Click(object sender, EventArgs e)
{
int groupId = 111;
string newName = "创建测试修改";

IUserApi userBLL = new UserApi();
CommonResult result = userBLL.UpdateGroupName(token, groupId, newName);
Console.WriteLine("修改分组名称:" + (result.Success ? "成功" : "失败:" + result.ErrorMessage));
}

private void btnMoveToGroup_Click(object sender, EventArgs e)
{
int togroup_id = 111;//输入分组ID

if (togroup_id > 0)
{
IUserApi userBLL = new UserApi();
CommonResult result = userBLL.MoveUserToGroup(token, openId, togroup_id);

Console.WriteLine("移动用户分组名称:" + (result.Success ? "成功" : "失败:" + result.ErrorMessage));
}
}

了解了上面的代码和调用规则,我们就能通过API进行用户分组信息的管理了。通过在应用程序中集成相关的接口代码,我们就能够很好的控制我们的关注用户列表和用户分组信息。从而为我们下一步用户的信息推送打好基础。

 

 

主要研究技术:代码生成工具、Visio二次开发、客户关系管理软件、送水管理软件等共享软件开发
专注于Winform开发框架Web开发框架、WCF开发框架的研究及应用。
转载请注明出处:
撰写人:伍华聪  http://www.iqidi.com

[转载][IIS]由安装IIS和.net framework先后顺序引发的问题,你中招了吗? - wolfy - 博客园

mikel阅读(1061)

[转载][IIS]由安装IIS和.net framework先后顺序引发的问题,你中招了吗? – wolfy – 博客园.

引言

最近帮别人做了一个小网站,在本机部署测试的时候,竟然浏览不了aspx后缀的页面,但可以浏览html页面,由此想到了IIS对静态页和动态页不同的处理方式。

http请求到达服务器

当服务器接收到一个http请求时,IIS需要决定如何去处理这个请求,根据文件的后缀名 来判别如何处理该请求,获得后缀名之后,会在服务端寻找可以处理这类后缀名的应用程序,如果IIS找不到可以处理此类文件的应用程序,并且这个文件并不受 服务器端的保护,那么IIS将直接把这个文件返回给客户端。(如:*.html,*.htm,js,*.jpg,*.png等)

能够处理各种后缀名的应用程序,通常成为ISAPI应用程序(Internet Server Applicatin Pogram Interface,互联网服务器应用程序编程接口)。虽然是应用程序,但实际上是一个接口,起到一个代理作用,它的主要工作是映射所请求的页面与此后缀 名相对应的实际的处理程序。

通过以上内容,以及实际部署中遇到的问题,可以猜想到是扩展程序的问题,扩展程序找不到对应的处理程序。

原因

为何会造成此问题?

最近刚重装过系统,系统装好后,先安装的vs2013后安装的IIS导致的,如果先安装IIS再安装vs,系统会自动配置IIS,无需手动配置。

解决方案

MSDN:http://msdn.microsoft.com/zh-cn/library/k6h9cz8h.aspx

可参考MSDN上的说法,重新注册.netframework。

打开cmd

找到framework的路径,我的vs默认安装在c盘,路径为:C:\Windows\Microsoft.NET\Framework\v4.0.30319

输入cd 进入该目录,输入命令aspnet_regiis.exe -i

 重新浏览,问题解决。

总结

这是在实际操作中出现的问题,这个问题还真不是一般人能遇到的,大家估计都是正常顺序安装的,很少能遇到,记录一下,这里希望能帮到遇到跟我一样问题的朋友。

[转载]ecshop中ajax的调用原理 - 计算机应用 - 博客频道 - CSDN.NET

mikel阅读(1003)

[转载]ecshop中ajax的调用原理 – 计算机应用 – 博客频道 – CSDN.NET.

1:首先ecshop是如何定义ajax对象的。

     ecshop中的ajax对象是在js/transport.js文件中定义的。里面是ajax对象文件。声明了一个var Ajax = Transport;对象和一个方法Ajax.call = Transport.run;

2:ecshop中ajax可以使用两种方式传递数据.一种是get方式,一种是post方式.

     Ajax.call( ‘user.php?act=is_registered’, ‘username=’ + username, registed_callback , ‘GET’, ‘TEXT’, true, true );

     Ajax.call(‘user.php?act=return_to_cart’, ‘order_id=’ + orderId, returnToCartResponse, ‘POST’, ‘JSON’);

3:ecshop中的 ajax可以是传递text数据,也可以是一个json对象。比如以下代码

   goods.quick    = quick;
  goods.spec     = spec_arr;
  goods.goods_id = goodsId;
  goods.number   = number;
  goods.parent   = (typeof(parentId) == “undefined”) ? 0 : parseInt(parentId);

  Ajax.call(‘flow.php?step=add_to_cart’, ‘goods=’ + goods.toJSONString(), addToCartResponse, ‘POST’, ‘JSON’);

   里面的goods就是对象.而且是靠json来传递的。返回的结果result也是对象.

4:ecshop ajax函数里面.第三个参数就是回掉函数的名称。比如以上代码addToCartResponse 这个函数就是ajax处理结果的回调函数.

5:在ecshop的php代码中,一般是通过get或者post方式来接受函数。比如以下例子,如果接受的是对象。还需要用json数据格式来处理.比如以下

   include_once(‘includes/cls_json.php’);
    $_POST[‘goods’] = json_str_iconv($_POST[‘goods’]);
   处理的返回结果,也需要是json格式发送给js

   die($json->encode($result));

例如:

HTML

<input type="button" name="search" value="{$lang.button_search}" onclick="searchUser();" />

<select name="user_search[]" id="user_search" size="15" style="width:260px" ondblclick="addUser()" multiple="true">
      </select>
/**
* 按用户名搜索用户
*/
function searchUser()
{
  var eles = document.forms['theForm'].elements;

  /* 填充列表 */
  var keywords = Utils.trim(eles['keyword'].value);
  if (keywords != '')
  {
    Ajax.call('bonus.php?is_ajax=1&act=search_users', 'keywords=' + keywords, searchUserResponse, "GET", "JSON");
  }
}

function searchUserResponse(result)
{
  var eles = document.forms['theForm'].elements;
  eles['user_search[]'].length = 0;

  if (result.error == 0)
  {
    for (i = 0; i < result.content.length; i++)
    {
      var opt = document.createElement('OPTION');
      opt.value = result.content[i].user_id;
      opt.text  = result.content[i].user_name;
      eles['user_search[]'].options.add(opt);
    }
  }
}

php代码

/*------------------------------------------------------ */
//-- 搜索用户
/*------------------------------------------------------ */
if ($_REQUEST['act'] == 'search_users')
{
    $keywords = json_str_iconv(trim($_GET['keywords']));

    $sql = "SELECT user_id, user_name FROM " . $ecs->table('users') .
            " WHERE user_name LIKE '%" . mysql_like_quote($keywords) . "%' OR user_id LIKE '%" . mysql_like_quote($keywords) . "%'";
    $row = $db->getAll($sql);

    make_json_result($row);
}

[转载]sql语句的优化分析 - 张龙豪 - 博客园

mikel阅读(1055)

[转载]sql语句的优化分析 – 张龙豪 – 博客园.

开门见山,问题所在

SQL语句性能达不到你的要求,执行效率让你忍无可忍,一般会时下面几种情况。

  • 网速不给力,不稳定。
  • 服务器内存不够,或者SQL 被分配的内存不够。
  • SQL语句设计不合理
  • 没有相应的索引,索引不合理
  • 没有有效的索引视图
  • 表数据过大没有有效的分区设计
  • 数据库设计太2,存在大量的数据冗余
  • 索引列上缺少相应的统计信息,或者统计信息过期
  • ….

那么我们如何给找出来导致性能慢的的原因呢?

  • 首先你要知道是否跟sql语句有关,确保不是机器开不开机,服务器硬件配置太差,没网你说p啊
  • 接着你使用我上一篇文章中提到的2柯南sql性能检测工具–sql server profiler,分析出sql慢的相关语句,就是执行时间过长,占用系统资源,cpu过多的
  • 然后是这篇文章要说的,sql优化方法跟技巧,避免一些不合理的sql语句,取暂优sql
  • 再然后判断是否使用啦,合理的统计信息。sql server中可以自动统计表中的数据分布信息,定时根据数据情况,更新统计信息,是很有必要的
  • 确认表中使用啦合理的索引,这个索引我前面博客中也有提过,不过那篇博客之后,还要进一步对索引写篇文章
  • 数据太多的表,要分区,缩小查找范围

分析比较执行时间计划读取情况

select * from dbo.Product

执行上面语句一般情况下只给你返回结果和执行行数,那么你怎么分析呢,怎么知道你优化之后跟没有优化的区别呢。

下面给你说几种方法。

1.查看执行时间和cpu占用时间

set statistics time on
select * from dbo.Product
set statistics time off

打开你查询之后的消息里面就能看到啦。

2.查看查询对I/0的操作情况

set statistics io on
select * from dbo.Product
set statistics io off

执行之后

 

扫描计数:索引或表扫描次数

逻辑读取:数据缓存中读取的页数

物理读取:从磁盘中读取的页数

预读:查询过程中,从磁盘放入缓存的页数

lob逻辑读取:从数据缓存中读取,image,text,ntext或大型数据的页数

lob物理读取:从磁盘中读取,image,text,ntext或大型数据的页数

lob预读:查询过程中,从磁盘放入缓存的image,text,ntext或大型数据的页数

 

如果物理读取次数和预读次说比较多,可以使用索引进行优化。

如果你不想使用sql语句命令来查看这些内容,方法也是有的,哥教你更简单的。

查询—>>查询选项—>>高级

被红圈套上的2个选上,去掉sql语句中的set statistics io/time on/off 试试效果。哦也,你成功啦。。

3.查看执行计划

选中查询语句,点击然后看消息里面,会出现下面的图例

首先我这个例子的语句太过简单,你整个复杂的,包涵啊。

分析鼠 标放在图标上会显示此步骤执行的详细内容,每个表下面都显示一个开销百分比,分析站百分比多的的一块,可以根据重新设计数据结构,或这重写sql语句,来 对此进行优化。如果存在扫描表,或者扫描聚集索引,这表示在当前查询中你的索引是不合适的,是没有起到作用的,那么你就要修改完善优化你的索引,具体怎么 做,你可以根据我上一篇文章中的sql优化利器–数据库引擎优化顾问对索引进行分析优化。

select查询艺术

1.保证不查询多余的列与行。

  • 尽量避免select * 的存在,使用具体的列代替*,避免多余的列
  • 使用where限定具体要查询的数据,避免多余的行
  • 使用top,distinct关键字减少多余重复的行

2.慎用distinct关键字

distinct在查询一个字段或者很少字段的情况下使用,会避免重复数据的出现,给查询带来优化效果。

但是查询字段很多的情况下使用,则会大大降低查询效率。

由这个图,分析下:

很明显带distinct的语句cpu时间和占用时间都高于不带distinct的语句。原因是当查询很多字段时,如果使用distinct,数据库引擎就会对数据进行比较,过滤掉重复数据,然而这个比较,过滤的过程则会毫不客气的占用系统资源,cpu时间。

3.慎用union关键字

此关键字主要功能是把各个查询语句的结果集合并到一个结果集中返回给你。用法

<select 语句1>
union
<select 语句2>
union
<select 语句3>
...

满足union的语句必须满足:1.列数相同。 2.对应列数的数据类型要保持兼容。

执行过程:

依次执行select语句–>>合并结果集—>>对结果集进行排序,过滤重复记录。

复制代码
select * from 
(( orde o  left join orderproduct op on o.orderNum=op.orderNum )
inner join product p on op.proNum=p.productnum)  where p.id<10000
union
select * from 
(( orde o  left join orderproduct op on o.orderNum=op.orderNum )
inner join product p on op.proNum=p.productnum)  where p.id<20000 and p.id>=10000
union
select * from 
(( orde o  left join orderproduct op on o.orderNum=op.orderNum )
inner join product p on op.proNum=p.productnum)  where p.id>20000   ---这里可以写p.id>100 结果一样,因为他筛选过啦

----------------------------------对比上下两个语句-----------------------------------------
select * from 
(( orde o  left join orderproduct op on o.orderNum=op.orderNum )
inner join product p on op.proNum=p.productnum)
复制代码

 

由此可见效率确实低,所以不是在必要情况下避 免使用。其实有他执行的第三部:对结果集进行排序,过滤重复记录。就能看出不是什么好鸟。然而不对结果集排序过滤,显然效率是比union高的,那么不排 序过滤的关键字有吗?答,有,他是union all,使用union all能对union进行一定的优化。。

4.判断表中是否存在数据

select count(*) from product 
select top(1) id from product

很显然下面完胜

5.连接查询的优化

首先你要弄明白你想要的数据是什么样子的,然后再做出决定使用哪一种连接,这很重要。

各种连接的取值大小为:

  • 内连接结果集大小取决于左右表满足条件的数量
  • 左连接取决与左表大小,右相反。
  • 完全连接和交叉连接取决与左右两个表的数据总数量
select * from 
( (select * from orde where OrderId>10000) o  left join orderproduct op on o.orderNum=op.orderNum )

select * from 
( orde o left join orderproduct op on o.orderNum=op.orderNum )
 where o.OrderId>10000

 

由此可见减少连接表的数据数量可以提高效率。

insert插入优化

复制代码
--创建临时表
create table #tb1
(
 id int,
 name nvarchar(30),
 createTime datetime
)
declare @i int
declare @sql varchar(1000)
set @i=0
while (@i<100000)  --循环插入10w条数据
begin
  set @i=@i+1
  set @sql=' insert into #tb1 values('+convert(varchar(10),@i)+',''erzi'+convert(nvarchar(30),@i)+''','''+convert(nvarchar(30),getdate())+''')'
  exec(@sql)
end
复制代码

我这里运行时间是51秒

复制代码
--创建临时表
create table #tb2
(
 id int,
 name nvarchar(30),
 createTime datetime
)

declare @i int
declare @sql varchar(8000)
declare @j int
set @i=0
while (@i<10000)  --循环插入10w条数据
begin 
 set @j=0
 set @sql=' insert into #tb2 select '+convert(varchar(10),@i*100+@j)+',''erzi'+convert(nvarchar(30),@i*100+@j)+''','''+convert(varchar(50),getdate())+''''
 set @i=@i+1
 while(@j<10)
 begin   
   set @sql=@sql+' union all select '+convert(varchar(10),@i*100+@j)+',''erzi'+convert(nvarchar(30),@i*100+@j)+''','''+convert(varchar(50),getdate())+''''
   set @j=@j+1
 end 
 exec(@sql)
end

drop table #tb2
select count(1) from #tb2
复制代码

我这里运行时间大概是20秒

分析说明:insert into select批量插入,明显提升效率。所以以后尽量避免一个个循环插入。

优化修改删除语句

如果你同时修改或删除过多数据,会造成cpu利用率过高从而影响别人对数据库的访问。

如果你删除或修改过多数据,采用单一循环操作,那么会是效率很低,也就是操作时间过程会很漫长。

这样你该怎么做呢?

折中的办法就是,分批操作数据。

delete product where id<1000
delete product where id>=1000 and id<2000
delete product where id>=2000 and id<3000
.....

当然这样的优化方式不一定是最优的选择,其实这三种方式都是可以的,这要根据你系统的访问热度来定夺,关键你要明白什么样的语句是什么样的效果。

 

 

总结:优化,最重要的是在于你平时设计语句,数据库的习惯,方式。如果你平时不在意,汇总到一块再做优化,你就需要耐心的分析,然而分析的过程就看你的悟性,需求,知识水平啦。

[转载]老湿不给你力啊:Java各种加密解密 - Jeff Li - 博客园

mikel阅读(1092)

[转载]老湿不给你力啊:各种加密解密 – Jeff Li – 博客园.

加密

  加密,是以某种特殊的算法改变原有的信息数据,使得未授权的用户即使获得了已加密的信息,但因不知解密的方法,仍然无法了解信息的内容。 在航空学中,指利用航空摄影像片上已知的少数控制点,通过对像片测量和计算的方法在像对或整条航摄带上增加控制点的作业。

 

分享下各种加密解密

package sedion.jeffli.wmuitp.util;



import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/** 编解码、加解密帮助类 */
public class EnCryptHelper
{
    /** AES 密匙长度 */
    public static final int AES_KEY_SIZE        = 128;
    /** DES 密匙长度 */
    public static final int DES_KEY_SIZE        = 56;
    /** 加密模式 */
    public static final int ENCRYPT_MODE        = Cipher.ENCRYPT_MODE;
    /** 解密模式 */
    public static final int DECRYPT_MODE        = Cipher.DECRYPT_MODE;
    /** 默认字符集(UTF-8) */
    public static final String DEFAULT_ENCODING    = "UTF-8";
    /** 加密方法:MD5 */
    public static final String MD5                = "MD5";
    /** 加密方法:SHA */
    public static final String SHA                = "SHA";
    /** 加密方法:AES */
    public static final String AES                = "AES";
    /** 加密方法:DES */
    public static final String DES                = "DES";
    
    private static final String SEC_RAN_ALG        = "SHA1PRNG";

    /** byte[] -> 十六进制字符串 (小写) */
    public final static String bytes2HexStr(byte[] bytes)
    {
        return bytes2HexStr(bytes, false);
    }
    
    /** byte[] -> 十六进制字符串 */
    public final static String bytes2HexStr(byte[] bytes, boolean capital)
    {
        StringBuilder sb = new StringBuilder();
        
        for(byte b : bytes)
            sb.append(byte2Hex(b, capital));
        
        return sb.toString();
    }

    /** byte -> 十六进制双字符 (小写) */
    public final static char[] byte2Hex(byte b)
    {
        return byte2Hex(b, false);
    }

    /** byte -> 十六进制双字符 */
    public final static char[] byte2Hex(byte b, boolean capital)
    {
        byte bh    = (byte)(b >>> 4 & 0xF);
        byte bl    = (byte)(b & 0xF);

        return new char[] {halfByte2Hex(bh, capital), halfByte2Hex(bl, capital)};
    }
    
    /** 半 byte -> 十六进制单字符 (小写) */
    public final static char halfByte2Hex(byte b)
    {
        return halfByte2Hex(b, false);
    }
    
    /** 半 byte -> 十六进制单字符 */
    public final static char halfByte2Hex(byte b, boolean capital)
    {
        return (char)(b <= 9 ? b + '0' : (capital ? b + 'A' - 0xA : b + 'a' - 0xA));
    }
    
    /** 十六进制字符串 -> byte[] */
    public final static byte[] hexStr2Bytes(String str)
    {
        int length = str.length();
        
        if(length % 2 != 0)
        {
            str = "0" + str;
            length = str.length();
        }
        
        byte[] bytes = new byte[length / 2];
        
        for(int i = 0; i < bytes.length; i++)
            bytes[i] = hex2Byte(str.charAt(2 * i), str.charAt(2 * i + 1));
        
        return bytes;
    }

    /** 十六进制双字符 -> byte */
    public final static byte hex2Byte(char ch, char cl)
    {
        byte bh    = hex2HalfByte(ch);
        byte bl    = hex2HalfByte(cl);
        
        return (byte)((bh << 4) + bl);
    }
    
    /** 十六进制单字符 -> 半 byte */
    public final static byte hex2HalfByte(char c)
    {
        return (byte)(c <= '9' ? c - '0' : (c <= 'F' ? c - 'A' + 0xA : c - 'a' + 0xA));
    }
    
    /** 使用默认字符集对字符串编码后再进行 MD5 加密 */
    public final static String md5(String input)
    {
        return md5(input, null);
    }
    
    /** 使用指定字符集对字符串编码后再进行 MD5 加密 */
    public final static String md5(String input, String charset)
    {
        return encode(getMd5Digest(), input, charset);
    }
    
    /** MD5 加密 */
    public final static byte[] md5(byte[] input)
    {
        MessageDigest algorithm = getMd5Digest();
        return encode(algorithm, input);
    }
    
    /** 使用默认字符集对字符串编码后再进行 SHA 加密 */
    public final static String sha(String input)
    {
        return sha(input, null);
    }
    
    /** 使用指定字符集对字符串编码后再进行 SHA 加密 */
    public final static String sha(String input, String charset)
    {
        return encode(getShaDigest(), input, charset);
    }
    
    /** 使用默认字符集对字符串编码后再进行 SHA-{X} 加密,其中 {X} 由 version 参数指定 */
    public final static String sha(String input, int version)
    {
        return sha(input, null, version);
    }
    
    /** 使用指定字符集对字符串编码后再进行 SHA-{X} 加密,其中 {X} 由 version 参数指定 */
    public final static String sha(String input, String charset, int version)
    {
        return encode(getShaDigest(version), input, charset);
    }
    
    /** SHA加密 */
    public final static byte[] sha(byte[] input)
    {
        MessageDigest algorithm = getShaDigest();
        return encode(algorithm, input);
    }
    
    /** SHA-{X} 加密,其中 {X} 由 version 参数指定 */
    public final static byte[] sha(byte[] input, int version)
    {
        MessageDigest algorithm = getShaDigest(version);
        return encode(algorithm, input);
    }
    
    /** 使用指定算法对字符串加密 */
    public final static String encode(MessageDigest algorithm, String input)
    {
        return encode(algorithm, input, null);
    }
    
    /** 使用指定字符集对字符串编码后再进行 SHA-{X} 加密,字符串的编码由 charset 参数指定 */
    public final static String encode(MessageDigest algorithm, String input, String charset)
    {
        try
        {
            byte[] bytes    = input.getBytes(safeCharset(charset));
            byte[] output    = encode(algorithm, bytes);
            
            return bytes2HexStr(output);
        }
        catch(UnsupportedEncodingException e)
        {
            throw new RuntimeException(e);
        }
    }
    
    /** 使用指定算法对 byte[] 加密 */
    public final static byte[] encode(MessageDigest algorithm, byte[] input)
    {
        return algorithm.digest(input);
    }
    
    /** 获取 MD5 加密摘要对象 */
    public final static MessageDigest getMd5Digest()
    {
        return getDigest(MD5);
    }
    
    /** 获取 SHA 加密摘要对象 */
    public final static MessageDigest getShaDigest()
    {
        return getDigest(SHA);
    }
    
    /** 获取 SHA-{X} 加密摘要对象,其中 {X} 由 version 参数指定 */
    public final static MessageDigest getShaDigest(int version)
    {
        String algorithm = String.format("%s-%d", SHA, version);
        return getDigest(algorithm);
    }
    
    /** 根据加密方法名称获取加密摘要对象 */
    public final static MessageDigest getDigest(String algorithm)
    {
        try
        {
            return MessageDigest.getInstance(algorithm);
        }
        catch(NoSuchAlgorithmException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** 根据加密方法名称和提供者获取加密摘要对象 */
    public final static MessageDigest getDigest(String algorithm, String provider)
    {
        try
        {
            return MessageDigest.getInstance(algorithm, provider);
        }
        catch(NoSuchAlgorithmException e)
        {
            throw new RuntimeException(e);
        }
        catch(NoSuchProviderException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** 根据加密方法名称和提供者获取加密摘要对象 */
    public final static MessageDigest getDigest(String algorithm, Provider provider)
    {
        try
        {
            return MessageDigest.getInstance(algorithm, provider);
        }
        catch(NoSuchAlgorithmException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** URL编码 (使用默认字符集) */
    public final static String urlEncode(String url)
    {
        return urlEncode(url, null);
    }
    
    /** URL编码 (使用指定字符集) */
    public final static String urlEncode(String url, String charset)
    {
        try
        {
            return URLEncoder.encode(url, safeCharset(charset));
        }
        catch(UnsupportedEncodingException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** URL解码 (使用默认字符集) */
    public final static String urlDecode(String url)
    {
        return urlDecode(url, null);
    }
    
    /** URL解码 (使用指定字符集) */
    public final static String urlDecode(String url, String enc)
    {
        try
        {
            return URLDecoder.decode(url, safeCharset(enc));
        }
        catch(UnsupportedEncodingException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** base 64 编码 */
    public final static byte[] base64Encode(byte[] bytes)
    {
        return Base64.encode(bytes);
    }

    /** base 64 编码(到达指定字符数后换行) */
    public final static byte[] base64Encode(byte[] bytes, int wrapAt)
    {
        return Base64.encode(bytes, wrapAt);
    }

    /** base 64 编码 */
    public final static void base64Encode(File source, File target)
    {
        try
        {
            Base64.encode(source, target);
        }
        catch(IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** base 64 编码(到达指定字符数后换行) */
    public final static void base64Encode(File source, File target, int wrapAt)
    {
        try
        {
            Base64.encode(source, target, wrapAt);
        }
        catch(IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** base 64 编码 */
    public final static void base64Encode(InputStream is, OutputStream os)
    {
        try
        {
            Base64.encode(is, os);
        }
        catch(IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** base 64 编码(到达指定字符数后换行) */
    public final static void base64Encode(InputStream is, OutputStream os, int wrapAt)
    {
        try
        {
            Base64.encode(is, os, wrapAt);
        }
        catch(IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** 使用默认字符集对字符串进行 base 64 编码 */
    public final static String base64Encode(String str)
    {
        return Base64.encode(str, DEFAULT_ENCODING);
    }

    /** 使用指定字符集对字符串进行 base 64 编码 */
    public final static String base64Encode(String str, String charset)
    {
        return Base64.encode(str, charset);
    }

    /** base 64 解码 */
    public final static byte[] base64Decode(byte[] bytes)
    {
        return Base64.decode(bytes);
    }

    /** base 64 解码 */
    public final static void base64Decode(File source, File target)
    {
        try
        {
            Base64.decode(source, target);
        }
        catch(IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** base 64 解码 */
    public final static void base64Decode(InputStream is, OutputStream os)
    {
        try
        {
            Base64.decode(is, os);
        }
        catch(IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    /** 使用默认字符集对字符串进行 base 64 解码 */
    public final static String base64Decode(String str)
    {
        return Base64.decode(str, DEFAULT_ENCODING);
    }

    /** 使用指定字符集对字符串进行 base 64 解码 */
    public final static String base64Decode(String str, String charset)
    {
        return Base64.decode(str, charset);
    }

    /** 使用默认字符集对字符串编码后再进行 AES 加密 */
    public final static String aesEncrypt(String content, String password) throws GeneralSecurityException
    {
        return aesEncrypt(content, null, password);
    }
    
    /** 使用指定字符集对字符串编码后再进行 AES 加密,字符串的编码由 charset 参数指定 */
    public final static String aesEncrypt(String content, String charset, String password) throws GeneralSecurityException
    {
        return encrypt(AES, AES_KEY_SIZE, content, charset, password);
    }
    
    /** AES 加密 */
    public final static byte[] aesEncrypt(byte[] content, String password) throws GeneralSecurityException
    {
        return crypt(AES, ENCRYPT_MODE, AES_KEY_SIZE, content, password);
    }
    
    /** AES 解密,并使用默认字符集生成解密后的字符串 */
    public final static String aesDecrypt(String content, String password) throws GeneralSecurityException
    {
        return aesDecrypt(content, null, password);
    }
    
    /** AES 解密,并使用指定字符集生成解密后的字符串,字符串的编码由 charset 参数指定 */
    public final static String aesDecrypt(String content, String charset, String password) throws GeneralSecurityException
    {
        return decrypt(AES, AES_KEY_SIZE, content, charset, password);
    }

    /** AES 解密 */
    public final static byte[] aesDecrypt(byte[] content, String password) throws GeneralSecurityException
    {
        return crypt(AES, DECRYPT_MODE, AES_KEY_SIZE, content, password);
    }

    /** 使用默认字符集对字符串编码后再进行 DES 加密 */
    public final static String desEncrypt(String content, String password) throws GeneralSecurityException
    {
        return desEncrypt(content, null, password);
    }
    
    /** 使用指定字符集对字符串编码后再进行 DES 加密,字符串的编码由 charset 参数指定 */
    public final static String desEncrypt(String content, String charset, String password) throws GeneralSecurityException
    {
        return encrypt(DES, DES_KEY_SIZE, content, charset, password);
    }
    
    /** DES 加密 */
    public final static byte[] desEncrypt(byte[] content, String password) throws GeneralSecurityException
    {
        return crypt(DES, ENCRYPT_MODE, DES_KEY_SIZE, content, password);
    }

    /** DES 解密,并使用默认字符集生成解密后的字符串 */
    public final static String desDecrypt(String content, String password) throws GeneralSecurityException
    {
        return desDecrypt(content, null, password);
    }
    
    /** DES 解密,并使用指定字符集生成解密后的字符串,字符串的编码由 charset 参数指定 */
    public final static String desDecrypt(String content, String charset, String password) throws GeneralSecurityException
    {
        return decrypt(DES, DES_KEY_SIZE, content, charset, password);
    }

    /** DES 解密 */
    public final static byte[] desDecrypt(byte[] content, String password) throws GeneralSecurityException
    {
        return crypt(DES, DECRYPT_MODE, DES_KEY_SIZE, content, password);
    }

    /**
     * 加密字符串
     * 
     * @param method    :加密方法(AES、DES)
     * @param keysize    :密匙长度
     * @param content    :要加密的内容
     * @param charset    :加密内容的编码字符集
     * @param password    :密码
     * @return            :加解密结果
     * @throws GeneralSecurityException    加密失败抛出异常
     */
    public final static String encrypt(String method, int keysize, String content, String charset, String password) throws GeneralSecurityException
    {
        try
        {
            byte[] bytes    = content.getBytes(safeCharset(charset));
            byte[] output    = crypt(method, ENCRYPT_MODE, keysize, bytes, password);
            
            return bytes2HexStr(output);
        }
        catch(UnsupportedEncodingException e)
        {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 解密字符串
     * 
     * @param method    :解密方法(AES、DES)
     * @param keysize    :密匙长度
     * @param content    :要解密的内容
     * @param charset    :解密结果的编码字符集
     * @param password    :密码
     * @return            :加解密结果
     * @throws GeneralSecurityException    解密失败抛出异常
     */
    public final static String decrypt(String method, int keysize, String content, String charset, String password) throws GeneralSecurityException
    {
        try
        {
            byte[] bytes    = hexStr2Bytes(content);
            byte[] output    = crypt(method, DECRYPT_MODE, keysize, bytes, password);
            
            return new String(output, safeCharset(charset));
        }
        catch(UnsupportedEncodingException e)
        {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 加/解密
     * 
     * @param method    :加/解密方法(AES、DES)
     * @param mode        :模式(加密/解密)
     * @param keysize    :密匙长度
     * @param content    :要加/解密的内容
     * @param password    :密码
     * @return            :加解密结果
     * @throws GeneralSecurityException    解密失败抛出异常
     */
    public final static byte[] crypt(String method, int mode, int keysize, byte[] content, String password) throws GeneralSecurityException
    {
            KeyGenerator kgen    = KeyGenerator.getInstance(method);
            SecureRandom secure    = SecureRandom.getInstance(SEC_RAN_ALG);
            String seed            = GeneralHelper.safeString(password);
            
            secure.setSeed(seed.getBytes());
            kgen.init(keysize, secure);

            SecretKey secretKey    = kgen.generateKey();
            byte[] enCodeFormat    = secretKey.getEncoded();
            SecretKeySpec key    = new SecretKeySpec(enCodeFormat, method);
            Cipher cipher        = Cipher.getInstance(method);
            
            cipher.init(mode, key);
            return cipher.doFinal(content);
    }
    
    private final static String safeCharset(String charset)
    {
        if(GeneralHelper.isStrEmpty(charset))
            charset = DEFAULT_ENCODING;
        
        return charset;
    }
}

[转载]PHP的COMET实践笔记 - hackboy - 博客园

mikel阅读(1177)

[转载]COMET实践笔记 – hackboy – 博客园.

这几天在给公司的一个点对点聊天系统升级,之前只是使用简单的ajax轮询方式实现,每5秒钟取一次数据,延时太长,用户体验不是很好,因此打算采用服务器推送技术,故此整理了以下文档,将自己找到的一些资料及心得与大家在此分享。本文主要综述了Comet相关的概念、应用场景、常用的两种实现模型、及PHP实现代码

概    念:Comet,基于 HTTP 长连接的“服务器推”技术,是一种 Web 应用程序的架构。基于这种架构开发的应用中,服务器端会主动以异步的方式向客户端程序推送数据,而不需要客户端显式的发出请求。

 

其他别名:服务器推送技术(Server Push),反向Ajax

 

应用场景:Comet 架构非常适合事件驱动的 Web 应用以及对交互性和实时性要求很强的应用,如实时监控、股票交易行情分析、聊天室和 Web 版在线游戏等。

 

实现模型:基于 Comet 架构的 Web 应用使用客户端和服务器端之间的 HTTP 长连接来作为数据传输的通道。每当服务器端的数据因为外部的事件而发生改变时,服务器端就能够及时把相关的数据推送给客户端。通常来说,有两种实现长连接的模型:

1. 基于 Iframe 的流方式(streaming)
基本原理: 在页面中加入一个Iframe标签,Iframesrc发起到服务器的连接,服务器端将其搁置,这样就建立了一个服务器端到客户端的通道,每当服务器端有数据时直接将数据经由此通道发送。

数据处理:这种方式服务器端返回的数据一般为类似“<script type=”text/JavaScript”>js_func(“json_data”)</script>”的JS脚本,其中js_func为写在母页面中的一个用来处理返回结果的回调函数,服务器端将返回的数据作为回调函数的参数,浏览器在收到数据后就会执行这段JS脚本。

优点: 具有高兼容特性

缺点: 使用 iframe 请求一个长连接有一个很明显的不足之处是有些浏览器会显示加载没有完成,一直处加页面加载中。Google 的天才们使用一个称为“htmlfile”的 ActiveX 解决了在 IE 中的加载显示问题。Zeitoun 网站提供的 comet-iframe.tar.gz,封装了一个基于 iframe 和 htmlfile 的 JavaScript comet 对象,支持 IEMozilla Firefox 浏览器,可以作为参考。

2. 基于 AJAX 长轮询方式(long-polling)
基本原理: 客户端发起一个ajax请求,服务器端将该请求搁置(pending)或者说挂起,直到服务器端有数据需要推送时返回数据并断开连接,客户端在接收ajax返回后处理数据,同时再次发起下一个ajax请求。

数据处理:通过 ajax 的回调函数来进行数据处理。

优点: 兼容性较高,实现简单

缺点: 对于php这种语言来说,如果要做到实时,那么服务端就要承受大得多的压力,因为搁置到什么时候往往是不确定的,这就要php脚本每次搁置都进行一个while循环。

注意: 浏览器有连接数限制。我得出的结论是如果当前页面上有一个ajax请求处于等待返回状态,那么其他ajax请求都会被搁置(Chrome, Firefox已测)。如果页面有一般ajax需求怎么办?解决方法是开个框架,框架中使在另一个域名下进行Comet长轮询,需要注意跨域问题。

 

未来方向:HTML5标准中,定义了客户端和服务器通讯的WebSocket方式,在得到浏览器支持以后,WebSocket将会取代Comet成为服务器推送的方法,目前chromeFirefoxOperaSafari等主流版本均支持,Internet Explorer10开始支持。

 

注意事项:对于一个实际的应用而言,系统的稳定性和性能是非常重要的。将 HTTP 长连接用于实际应用,很多细节需要考虑。

 

1. 不要在同一客户端同时使用超过两个的 HTTP 长连接

HTTP 1.1 规范中规定,客户端不应该与服务器端建立超过两个的 HTTP 连接,超过之后,新的连接会被阻塞。HTTP 1.1 对两个长连接的限制,会对使用了长连接的 Web 应用带来如下现象:在客户端如果打开超过两个的 IE 窗口去访问同一个使用了长连接的 Web 服务器,第三个 IE 窗口的 HTTP 请求被前两个窗口的长连接阻塞。所以在开发长连接的应用时, 必须注意在使用了多个 frame 的页面中,不要为每个 frame 的页面都建立一个 HTTP 长连接,这样会阻塞其它的 HTTP 请求,在设计上考虑让多个 frame 的更新共用一个长连接。

 

2. 控制信息与数据信息使用不同的 HTTP 连接

使用长连接时,存在一个很常见的场景:客户端需要关闭页 面,而服务器端还处在读取数据的阻塞状态,客户端需要及时通知服务器端关闭数据连接。服务器在收到关闭请求后首先要从读取数据的阻塞状态唤醒,然后释放为 这个客户端分配的资源,再关闭连接。所以在设计上,我们需要使客户端的控制请求和数据请求使用不同的 HTTP 连接,才能使控制请求不会被阻塞。在实现上,如果是基于 iframe 流方式的长连接,客户端页面需要使用两个 iframe,一个是控制帧,用于往服务器端发送控制请求,控制请求能很快收到响应,不会被阻塞;一个是显示帧,用于往服务器端发送长连接请求。如果是基于 AJAX 的长轮询方式,客户端可以异步地发出一个 XMLHttpRequest 请求,通知服务器端关闭数据连接。

 

3. SESSION锁定问题

对于上述第二点并不是十分理解,如何通过向服务器端发送控制请求?如何通知服务器端关闭数据连接?服务器端又怎么样关闭数据连接?经过我的实践发现,在服务器端如果使用了session,刷新页面时,页面会阻塞直到长连接超时返回方能重新加载页面,不知道这是不是上一点中所描述的问题?在PHP手册中查到一段英文,翻译过来大致是这样子的:默认情况下,session会在脚本执行完毕之后自动进行存储,脚本在操作sessionsession会被锁定,这意味着在同一时刻只有一个脚本可以操作session。由于该锁定的存在,当长连接被服务器端搁置期间,session会被一直锁定占用,从而导至其它请求无法打开session,从而造成页面阻塞。你当然可以关闭网站session功能,但这不现实,那么有没有一种方法可以在session操作完之后就及时将其存储并关闭呢?通过session_write_close函数,你可以在对session的操作完成之后但脚本尚未执行完毕之前及时将session存储并释放。于是在服务器端程序进行搁置前加入session_write_close函数之后成功解决了长连接导致的页面阻塞问题。

 

4. 在客户和服务器之间保持心跳信息

在浏览器与服务器之间维持一个长连接会为通信带来一些不确 定性,因为数据传输是随机的,客户端不知道何时服务器才有数据传送。服务器端需要确保当客户端不再工作时,释放为这个客户端分配的资源。因此需要一种机制 使双方知道大家都在正常运行。在实现上:服务器端在阻塞读时会设置一个时限,超时后阻塞读调用会返回,同时发给客户端没有新数据到达的心跳信息。此时如果 客户端已经关闭,服务器往通道写数据会出现异常,服务器端就会及时释放为这个客户端分配的资源。如果客户端使用的是基于 AJAX 的长轮询方式;服务器端返回数据、关闭连接后,经过某个时限没有收到客户端的再次请求,会认为客户端不能正常工作,会释放为这个客户端分配、维护的资源。当服务器处理信息出现异常情况,需要发送错误信息通知客户端,同时释放资源、关闭连接。

代码示例1.1 Comet-ajax-backend.php(ajax长轮询服务器端代码)

Comet demo<script src="jquery.js"></script><script>// <![CDATA[
    var comet = {
        url:'backend.php',
        error:false,
        connect : function(){
            $.ajax({
                url: comet.url,
                type: 'post',
                dataType: 'json',
                timeout: 0,
                success: function (response) {
                    comet.error = false;
                    $("#content").append('

<div>' + response.msg + '</div>


');
                },

                error: function () {
                    comet.error = true;
                },

                complete: function () {
                    if (comet.error) {
                        setTimeout(function () {
                            comet.connect();
                        }, 5000);
                    } else {
                        //alert(comet.timestamp);
                        comet.connect();
                    }
                }
            })
        }
    }

    // 发送消息函数
    function send(msg) {
        $.ajax({
            data: {'msg': msg},
            type: 'get',
            url: comet.url
        })
    }

    $(document).ready(function () {
        //页面加载完毕创建长连接
        comet.connect();
    });
    
// ]]></script>

&nbsp;

&nbsp;
<div id="content"></div>
<form action="" method="get"><input id="word" name="word" type="text" value="" /> <input name="submit" type="submit" value="Send" /></form>

代码示例2.1 Comet-iframe-backend.php(iframe服务器端代码)

<!--?php header("Cache-Control: no-cache, must-revalidate"); header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); //关闭PHP缓存 ob_end_flush(); //将内容强制冲刷到浏览器 flush(); //不限制超时时间 set_time_limit(0); //在开启session的应用中这个函数非常重要,防止页面因session占用阻塞 session_write_close(); ?-->


Comet php backend

<script>// <![CDATA[
  // KHTML browser don't share javascripts between iframes
  var is_khtml = navigator.appName.match("Konqueror") || navigator.appVersion.match("KHTML");
  if (is_khtml)
  {
    var prototypejs = document.createElement('script');
    prototypejs.setAttribute('type','text/javascript');
    prototypejs.setAttribute('src','prototype.js');
    var head = document.getElementsByTagName('head');
    head[0].appendChild(prototypejs);
  }
  // load the comet object
  var comet = window.parent.comet;
  
// ]]></script>

<!--?php </p-->

while(1) {
echo '<script>// <![CDATA[
';
    echo 'comet.printServerTime('.time().');';
    echo '
// ]]></script>';

// 强制将数据发送到浏览器
flush();
// 休息一秒钟
sleep(1);
}

?&gt;

代码示例2.2 Comet-iframe-index.html(iframe客户端代码)



Comet-iframe-demo

<script src="jquery.js"></script>

<div id="content">The server time will be shown here</div>

<script>// <![CDATA[
  var comet = {
    connection   : false,
    iframediv    : false,
    iframesrc    : './backend.php',

    initialize: function() {
      if (navigator.appVersion.indexOf("MSIE") != -1) {

      // 创建争取IE浏览器的iframe
      comet.connection = new ActiveXObject("htmlfile");
      comet.connection.open();
      comet.connection.write("<html>");
      comet.connection.write("<script>document.domain = '"+document.domain+"'");
      comet.connection.write("</html>");
      comet.connection.close();
      comet.iframediv = comet.connection.createElement("div");
      comet.connection.appendChild(comet.iframediv);
      comet.connection.parentWindow.comet = comet;
      comet.iframediv.innerHTML = "<iframe id='comet_iframe' src='"+comet.iframesrc+"'></iframe>";

    } else if (navigator.appVersion.indexOf("KHTML") != -1) {

      // 创建争对KHTML浏览器的iframe
      comet.connection = document.createElement('iframe');
      comet.connection.setAttribute('id',     'comet_iframe');
      comet.connection.setAttribute('src',    comet.iframesrc);
      with (comet.connection.style) {
        position   = "absolute";
        left       = top   = "-100px";
        height     = width = "1px";
        visibility = "hidden";
      }
      document.body.appendChild(comet.connection);

    } else {
      
      // 创建争对其它浏览器的iframe
      comet.connection = document.createElement('iframe');
      comet.connection.setAttribute('id',     'comet_iframe');
      with (comet.connection.style) {
        left       = top   = "-100px";
        height     = width = "1px";
        visibility = "hidden";
        display    = 'none';
      }
      comet.iframediv = document.createElement('iframe');
      comet.iframediv.setAttribute('src', comet.iframesrc);
      comet.connection.appendChild(comet.iframediv);
      document.body.appendChild(comet.connection);

    }
  },

  // 重新加载页面时释放iframe,防止IE浏览器出错
  onUnload: function() {
    if (comet.connection) {
      comet.connection = false; 
    }
  },

  // 数据处理回调函数  
  printServerTime: function (time) {
    $('#content').html(time);
  }

}

$(function(){
  //页面加载完毕后创建iframe到服务器的长连接
  comet.initialize();
  $(window).unload(function(){comet.onUnload();});
})
// ]]></script>

&nbsp;

本文文档及代码示例下载:http://pan.baidu.com/s/1mgqg8CK

 

[转载]Android手机间无线互传功能探索及实现 - 热气球 - 博客园

mikel阅读(990)

[转载]Android手机间无线互传功能探索及实现 – 热气球 – 博客园.

年前研究了一下Android如何实现无线互传的功能,写了个小demo,最近无事,遂整理一下,与各位共享。前期调研发现,Android想 要实现无线互传有以下几种技术:1,Bluetooth通行已久,简单易用,无奈速度奇慢是硬伤;2,NFC,官方从API 9开始支持,但各厂商对此技术持观望态度,比如我的三星就没有相应NFC硬件,NFC适用近场少量数据传输,尚未测试数据量的表现,不过NFC所谓的近场 对距离要求相当苛刻,基本是要贴着才能传;3,WIFI-P2P,传输速度快,且P2P使得两台设备互为C/S,传输可双向进行,官方从API 14开始支持,考虑到目前API 14以下的设备保养量巨大,只能暂时放弃此技术。

权衡一下,参考目前各类无线快传软件,最终确定使用Wifi AP方式,自测兼容API 8设备,传输速度快,但只能单向传输。Client端通过Wifi连接到Server端开启的Wifi AP,连接成功后Client通过HttpEngine向Server发起Http请求,Server通过一个简易的HttpServer向Client 提供响应服务,下面用一个非标准的流程图来大致示意一下方案流程:

   在上传工程之前,对源代码中几个重要的类简单解释一下:

Client端

WifiApClientManager:对WifiManager的接口进行了封装,以便于进行开启、连接等操作

HttpEngineLite:发起Http请求的”引擎”类

WifiApClientRequest:请求抽象类,子类 WifiApClientGetRecvListRequest,WifiApClientGetFileRequest分别表示获取获取Server端 待传文件列表请求及获取Server端指定文件请求,使用HttpEngineLite执行具体操作

NanoHTTPClient:提供获取Server端待传文件列表,获取Server端指定文件等操作接口,使用线程池管理WifiApClientRequest

 

Server端

WifiApServerManager:对WifiManager的接口进行了封装,以便于进行开启AP,配置AP等操作

WifiApServerUtil:定义了Android AP通用网关IP

NanoHTTPD:此类为引用的开源代码(A simple, tiny, nicely embeddable HTTP server in Java)

NanoHTTPServer:NanoHTTPD的子类,实现其接口,返回相应的HttpResponse

 

下面就是源代码时间了,Read the fucking source code:

http://files.cnblogs.com/zealotrouge/WifiAPDemo.7z

[转载]SqlServer性能检测和优化工具使用详细 - 张龙豪 - 博客园

mikel阅读(1281)

[转载]SqlServer性能检测和优化工具使用详细 – 张龙豪 – 博客园.

工具概要   

    如果你的数据库应用系统中,存在有大量表,视图,索引,触发器,函数,存储过程,SQL语句等等,又性能低下,而苦逼的你又要对其优化,那么你该怎么办? 哥教你,首先你要知道问题出在哪里?如果想知道问题出在哪里,并且找到他,咱们可以借助本文中要讲述的性能检测工具–SQL server profiler(处在sql安装文件–性能工具–sql server profiler)

    如果知道啦问题出现在哪里,如果你又是绝世高手,当然可以直中要害,写段代码给处理解决掉,但是如果你不行,你做不到,那么也无所谓,可以借助哥的力量给 你解决问题。哥给你的武功的秘诀心法是—数据库引擎优化顾问(处在sql安装文件–性能工具–数据库引擎优化顾问)

sql server profiler功能

    此工具比柯南还柯南,因为他能检测到数据库中的一举一动,即便你不动他,他也在监视你,他很贱的。他不但监视,还监视的很详细,有多详细一会再说,还把监 视的内容记录到数据库或者是文件中,给你媳妇告状说你把数据库哪里的性能搞的多么不好,不过他也会把好的给你记录下来,好与不好这当然需要你来分析,其实 他也是个很2的柯南。

数据库引擎优化顾问功能

    此武功,乃上乘武功。像张无忌的乾坤大挪移,先是接受sql server profiler检测出来的sql,视图,存储过程,数据结构等等,然后他再自己分析,然后再在怀中转两圈,感觉自己转的差不多啦,就给抛出来个威力更 炫,更好的索引,统计,分区等等建议信息。让你承受不住,happly致死。。下面听哥给你先讲讲咱们的很2柯南。

sql server profiler的使用

打开系统主菜单–SQLServer几—性能工具—>>sql server profiler;笨样儿,找到没?哥等你会儿,给你上张打开他后的图,让你看看。。

然后文件–新建跟踪–显示跟踪属性窗口

首先那个select%是个筛选监测的TextData。那个%是个通配符,他的意思就是筛选select开口的语句。当然这你自己可以随便定义,如update%,delete%….。

把那个排除不包含值的行也给带上,然后确定,运行。然后在数据库中运行一句select。你会发现他检测到啦。

每列以此向右,从EventClass开始,我给你讲讲都是什么。

事件分类,申请了语句,应用程序名称,操作系统用户,数据库用户,cpu占用率,读数据库次数,写数据库次说,执行脚本用时,应用程序进程号,开始时间,结束时间等。

事件选择,你就把鼠标放上去,他下面有中文的注释。自己好好看看,然后根据你自己的需要把事件勾选上来。

然后文件–>>另存为,可以把这些监测到的数据保存为文件,或数据表。

分析:

1.查找持续时间最长的查询

一般情况下,最长查询时间的查询语句就是最影 响性能的原因存在。它不仅占用数据库引擎大量的时间,还浪费系统资源,还影响数据库应用系统的交互速度。再对数据用应用系统进行优化时,先找出他,对其优 化,在创建跟踪时,勾上TSQL-SQL:BatchCompleted.跟Stored Procedures-RPC:completed。这样就能找出来这个最长时间查询然后对其进行分析优化。

select TextData,Duration,CPU from <跟踪的表>
where EventClass=12 -- 等于12表示BatchCompleted事件
and CPU<(0.4*Duration)  --如果cpu的占用时间,小于执行sql语句时间的40%,说明该语句等待时间过长

2.最占用系统资源的查询

就是占用cpu时间,跟读写IO的次数。建议事件包含Connect、Disconnect、ExistingConnection、SQL:BatchCompleted、RPC:completed,列包含writes,reads,cpu。

3.检测死锁

在访问量,并发量都很大的数据库中,如果设计 稍不合理,就有可能造成死锁,给系统性能带来影响。事件包含:RPC:Starting、SQL:BatchStarting、 Lock:DeadLock(死锁事件)、Lock:DeadLockChaining(死锁的事件序列)。

使用数据库引擎优化顾问分析解决数据库性能

打开系统主菜单–SQLServer几—性能工具—>>数据库引擎优化顾问,界面如下

 

 

打开之后,你在上一个工具中保存的的文件,你就在这里的工作负荷中选文件,表就选表。选后别急。

把要分析的数据库跟数据库的表选上,也就是下面的用于工作负荷分析的数据库选择,跟下面的要优化的数据库和表,慢慢扣,把他选对。

然后选则你想要的优化选项

根据需要,选上,高级选项里面通常可以默认。确定。。

然后点左上角有一个开始分析。

分析完成

 

 

说到这里

我感觉这本武功绝学你已经拥有啦7成功力,以后就看你们自己的造化啦。哥哥嘱托你多用,勤用,用脑,别用力,多喝六个核桃脑白金,开盖不要用牙咬

哥哥去啦,生亦何欢,死亦何苦。。

[转载]微信开发之本地接口调试(非80端口,无需上传服务器服务器访问) - Myna Wang - 博客园

mikel阅读(1222)

[转载]微信开发之本地接口调试(非80端口,无需上传服务器服务器访问) – Myna Wang – 博客园.

前言:

本文是总结在开发微信接口时,为方便开发所采取的一些快捷步骤,已节省开发人员难度开发时间从而提高开发效率。

本地测试:

(提醒,在本地模拟微信get或post数据时先将校验参数注释掉)

利用微信模拟器weixinPost进行模拟发送xml数据

由于这个软件是微信没公开高级接口时公布的,所以对于高级接口的调试,笔者利用的是Fiddler这个抓包工具模拟post数据

 

高端测试:

利用反向代理软件ngrok访问本地项目:

ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。(wiki百科和baidu百科暂无此软件的解释)

笔者以windows为例子来讲解:

1.软件下载地址:https://ngrok.com/download

2.在该网站注册一个自己的账号获取authtoken

注册成功后在下面:

笔者将下载的ngrok放在了d盘

接下来cmd命令进入ngrok

出现以下界面表示authtoken设置成功按CTRL+C返回:

接下来输入如下命令行设定域名前缀mynawechat:

设定成功后显示如下

在项目中测试如下:

测试成功!!!

如此一来,接下去在开发微信项目中直接可以访问本地的项目,免去了将运行的项目拷贝到服务器再在服务器上重启服务的费时的步骤。

总结:

求上文所述软件者请加入QQ群,软件就在群共享中,群主是本人,有什么问题大家可以一起交流,一起分享!

 

如果你觉得赞就给本博推荐下吧,希望能上精华,如果你是大神那就别搭理本博了

本文只是自我的一个总结,如果对你有所帮助是我的荣幸,文章不妥之处希望指正,大神勿喷请通过留言或关注微信公众帐号codenewbie支持小八哥!若有不妥之处,欢迎指点。

转帖请注明本文出自小八哥的博客(http://www.cnblogs.com/Codenewbie),请尊重他人的辛勤劳动成果,谢谢!

[转载]C#联通新版验证码识别的实现 - 幸福海 - 博客园

mikel阅读(1089)

[转载]C#联通新版验证码识别的实现 – 幸福海 – 博客园.

以前写了篇 联通充值卡自动充值的实现,最近发现联通官网改版了,随便看了下发现新版的验证码和以前的不同,发了点时间研究了下他的识别码,它现在的验证码如下

现在将识别步骤说下

1,转换灰度图片

2,清除2px的边框

3,分割验证码个数 (4)

4,生成字模库

经过以上步骤,可以得到下面这个效果

下面为部分实现代码

public String GetCheckString(Bitmap bitmap) {
UnCodebase ud = new UnCodebase(bitmap);
ud.GrayByPixels();
ud.ClearPicBorder(2);
ud.CutMap(14, 15, 0, 0);
bitmap = ud.bmpobj;
// bitmap = ud.ClearNoise(128, 1);
String chkcode = "";
Bitmap[] arrmap = ud.SplitImg(bitmap, 4, 1);
foreach (Bitmap item in arrmap) {
String str = ud.GetCodebybitmap(item, 128);
Boolean isEques = false;
foreach (String strss in code) {
String[] a = strss.Split(':');
if (str == a[1]) {
chkcode += a[0];
isEques = true;
break;
}
}
if (!isEques) {
String strCurrent = "";
double max = 0.0;
foreach (String strss in code) {
int len1, len2, min, count = 0;
String[] a = strss.Split(':');
len1 = a[1].Length;
len2 = str.Length;
min = len1;
if (min &gt; len2) {
min = len2;
}
for (int i = 0; i &lt; min; i++) {
if (str[i] == a[1][i]) {
count++;
}
}
double currrent = (count * 100.0 / min);
if (max &lt; currrent) {
max = currrent;
strCurrent = a[0].Trim();
}
}
chkcode += strCurrent.Trim();
}
}
return chkcode;
}

通过这些处理后,识别成功率在90+%以上,

下面附上测试地址,代码  100%C#实现,方便ASP.NET调用,如果是C/C++实现 ASP.NET 调非托管的有些麻烦,非得放到System32 或是一个绝对路径下麻烦

测试地址

http://www.fox163.com/UniconTest.aspx

–幸福海

博客地址:http://www.cnblogs.com/ningqhai/