[转载]Lucene的并发性安全性以及锁 - 李克华 - 博客园

mikel阅读(1097)

[转载]Lucene的并发性安全性以及锁 – 李克华 – 博客园.

这 部分内容将介绍三个紧密联系的主题:索引文件的并发访问、IndexReader和IndexWriter的线程安全性,以及Lucene用于避免索引被 破坏而使用的锁机制。通常,Lucene的初学者们对这几个主题都存在一定的误解。而准确地理解这些内容是十分重要的,因为,当索引应用程序同时服务于大 量不同的用户时,或为了满足一些突发性的请求、而需要通过对某些操作进行并行处理时,这些内容会帮助你消除在构建应用程序过程中所遇到的疑问。

2.9.1  并发访问的规则

Lucene 提供了一些修改索引的方法,例如索引新文档、更新文档和删除文档;在执行这些操作时,为了避免对索引文件造成损坏,需要遵循一些特定的规则。这类问题通常 会在web应用程序中突显出来。因为web应用程序是同时为多个请求而服务的。Lucene的并发性规则虽然比较简单,但我们必须严格遵守:

— 任意数量的只读操作都可以同时执行。例如,多个线程或进程可以并行地对同一个索引进行搜索。

— 在索引正在被修改时,我们也可以同时执行任意数量的只读操作。例如,当某个索引文件正在被优化,或正在对索引执行文档的添加、更新或删除操作时,用户仍然可以对这个索引进行搜索。

— 在某一时刻,只允许执行一个修改索引的操作。也就是说,在同一时间,一个索引文件只能被一个IndexWriter或IndexReader对象打开。

基于以上的并发性规则,我们可以构造一些关于并发性的更全面的例子,如表2.2中所示。表中说明了是否允许我们对一个索引文件进行各种并发性的操作。

表2.2  是否允许对某个Lucene索引进行并发性操作的举例

操    作

是否允许

对同一个索引运行多个并行的搜索进程

允许

对一个正在生成、被优化或正在与另一索引合并的索引运行多个并行的搜索进程,或该索引正在进行删除、更新文档等操作时,对索引运行多个并行的搜索进程

允许

对同一个索引用多个IndexWriter对象执行添加、更新文档的操作

不允许

当一个从索引中删除文档的IndexReader对象没有成功关闭时,打开一个IndexWriter对象用于在这个索引中添加新的文档

不允许

IndexWriter对象向索引中添加新文档后,未成功关闭;在此之后,打开一个IndexReader对象用于从这个索引中删除文档

不允许

注:当正在修改一个索引时,请记住在某一时刻,在同一个索引上只能执行一个修改操作。

2.9.2  线程安全性

尽管 Lucene不允许使用多个IndexWriter或IndexReader实例同时对一个索引进行修改,如表2.2所示,但是这两个类都是线程安全 (thread-safe)的,了解这一点相当重要。因此,这两个类的实例都可以被多线程共享,Lucene会对各个线程中所有对索引进行修改的方法的调 用进行恰当的同步处理,以此来确保修改操作能一个接着一个地有序进行。图2.7描述了这样的一个场景:

图2.7  一个IndexWriter或IndexReader对象可以被多个线程所共享

应用程序不需要进行额外的同步处理。尽管IndexReader和IndexWriter这 两 个类都是线程安全的,使用Lucene的应用程序还是必须确保这两个类的对象对索引的修改操作不能重叠。也就是说,在使用IndexWriter对象将新 文档被添加至索引中之前,必须关闭所有已经完成在同一个索引上,进行删除操作的IndexReader实例。同样地,在IndexReader对象对索引 中的文档进行删除和更新之前,必须关闭此前已经打开该索引的 IndexWriter实例。

表2.3是一个关于并发操作的矩阵,它向我们展示了一些具体操作是否能并发地执行。该表假定 应 用程序只使用了一个IndexWriter或IndexReader实例。请注意,在此我们并没有将对索引的更新视为一个单独的操作列出,因为它实际上可 以被看成是在删除操作后再进行一个添加操作。

表2.3  使用同一个IndexWriter或IndexReader实例时的并发操作矩阵,表中打叉的部分表示两个操作不能同时执行

查找

读文档

添加

删除

优化

合并

查找

读文档

添加

×

删除

×

×

×

优化

×

合并

×

这个矩阵可以归纳为:

—  IndexReader对象在从索引中删除一个文档时,IndexWriter对象不能向其中添加文档。

—  IndexWriter对象在对索引进行优化时,IndexReader对象不能从其中删除文档。

—  IndexWriter对象在对索引进行合并时,IndexReader对象也不能从其中删除文档。

从上面的矩阵及其归纳中,我们可以得到这样一个使用模式:当IndexWriter对象在对 索 引进行修改操作时,IndexReader对象不能对索引进行修改。这个操作模式是对称的:当IndexReader对象正在对索引进行修改操作 时,IndexWriter对象同样也不能对索引进行修改。

这里读者可以感到,Lucene的并发性规则和社会中的那些良好的习惯以及合理的法规具有相 通 之处。我们不一定非得严格地遵守这些规则,但是如果违反这些规则将会造成相应的后果。在现实生活中,违反法律法规也许得锒铛入狱。在使用Lucene时, 违背这些规则,则会损坏你的索引文件。Lucene使用者可能会对并发性有错误的理解甚至误用,但Lucene的创造者们对此早已有所预料,因此他们通过 锁机制尽可能地避免应用程序对索引造成意外的损坏。本书将在2.9.3 节中对Lucene索引的锁机制进行进一步的介绍。

2.9.3  索引锁机制

在Lucene 中,锁机制是与并发性相关的一个主题。在同一时刻只允许执行单一进程的所有代码段中,Lucene都创建了基于文件的锁,以此来避免误用Lucene 的API造成对索引的损坏。各个索引都有其自身的锁文件集;在默认的情况下,所有的锁文件都会被创建在计算机的临时目录中,这个目录由Java的 java.io.tmpdir中的系统属性所指定。

如果在索引文档时,观察一下那个临时目录,就可以看到Lucene的write.lock文 件;在段(segment)进行合并时,还可以看到 commit.lock文件。你可以通过设定org.apache.lucene.lockDir中的系统属性,使锁文件存放的目录改至指定的位置。这个 系统属性可以通过使用Java API在程序中进行设定,还可以通过命令行进行设定,如:-Dorg.apache.lucene.lockDir= /path/to/lock/dir。若有多台计算机需要访问存储在共享磁盘中的同一个索引,则应该在程序中显式地设定锁目录,这样位于不同计算机上的应 用程序才能访问到彼此的锁文件。根据已知的锁文件以及网络文件系统(NFS)出现的问题,锁目录应该选择放在一个不依赖于网络的文件系统卷上。以下就是上 面提到的两个锁文件:

write.lock 文件用于阻止进程试图并发地修改一个索引。更精确地说,IndexWriter对象在实例化时获得write.lock文件,直到IndexWriter 对象关闭之后才释放。当IndexReader对象在删除、恢复删除文档或设定域规范时,也需要获得这个文件。因此,write.lock会在对索引进行 写操作时长时间地锁定索引。

当对段进行读或合并操作时,就需要用到commit.lock文件。在 IndexReader 对象读取段文件之前会获取commit.lock文件,在这个锁文件中对所有的索引段进行了命名,只有当IndexReader对象已经打开并读取完所有 的段后,Lucene才会释放这个锁文件。IndexWriter对象在创建新的段之前,也需要获得commit.lock文件,并一直对其进行维护,直 至该对象执行诸如段合并等操作,并将无用的索引文件移除完毕之后才释放。因此,commit.lock的创建可能比write.lock更为频繁,但 commit.lock决不能过长时间地锁定索引,因为在这个锁文件生存期内,索引文件都只能被打开或删除,并且只有一小部分的段文件被写入磁盘里。表 2.4对Lucene 中各种使用Lucene API来锁定索引的情况进行了概括。

表2.4  Lucene中所有锁及创建和释放锁的操作

锁文件

何时获取

何时释放

描述

write.lock

IndexWriter

构造函数

close()

在关闭IndexWriter对象时释放锁

write.lock

IndexReader

delete(int)

close()

在关闭IndexReader对象时释放锁

write.lock

IndexReader

undeleteAll(int)

close()

在关闭IndexReader对象时释放锁

write.lock

IndexReader

setNorms (int,String,byte)

close()

在关闭IndexReader对象时释放锁

commit.lock

IndexWriter

构造函数

构造函数

段信息被读取或写入后立即释放锁

commit.lock

IndexWriter

addIndexes (IndexReader[])

addIndexes (IndexReader[])

写入新的段时获取锁文件

commit.lock

IndexWriter

addIndexes (Directory[])

addIndexes (Directory[])

写入新的段时获取锁文件

commit.lock

IndexWriter

mergeSegment (int)

mergeSegments (int))

写入新的段时获取锁文件

commit.lock

IndexReader

open(Direcory)

Open(Direcory)

所有段被读取后获取锁文件

commit.lock

SegmentReader

doClose()

doClose()

段的文件被写入或重写后获取锁文件

commit.lock

SegmentReader

undeleteAll()

undeleteAll()

移除段.del文件后获取锁文件

请注意另外两个与锁相关的方法:

—  IndexReader的isLocked(Directory) ——这个方法可以判断参数中指定的索引是否已经被上锁。在想要对索引进行某种修改操作之前,应用程序需要检查索引是否被锁保护时,通过使用这个方法可以很方便地得到结果。

—  IndexReader的unlock(Directory) ——该方法的作用正如其命名那样。尽管通过这个方法可以使你在任意时刻对任意的Lucene索引进行解锁,然而它的使用具有一定的危险性。因为 Lucene创建锁自有其理由,此外,在修改一个索引时对其解锁可能导致这个索引被损坏,从而使得这个索引失效。

虽然知道Lucene使用了哪些锁文件,何时、为什么要使用它们,以及在文件系统的何处存放 这 些锁文件,但是你不能直接在文件系统对它们进行操作。你应该通过 Lucene的API对它们进行操作。否则,如果将来Lucene开始启用一种不同的锁机制,或者Lucene改变了锁文件的命名或存储位置时,应用程序 可能会受到影响而不能顺利执行。

锁机制的实例

为了演示锁是如何使用的,程序2.7演示了Lucene如何利用锁来避免在同一时刻对同一索 引 文件进行多个修改操作。在testWriteLock( )方法中,Lucene对一个已经被IndexWriter对象打开的索引上锁,阻止第二个IndexWriter对象对这个索引进行修改。

如同我们先前提到的,Lucene的初学者们有时对这一章中介绍的并发性没有很好理解,从而陷 入到本小节中提到的关于锁的问题里,以至在程序中出现了上面所示的异常。在你的应用程序中如果出现了类似的异常,而索引的一致性对用户而言又十分重要,那 么请不要漠视这些异常。与锁相关的异常通常是误用了Lucene API的一个标志;若在应用程序中出现了这种异常,应该妥善地处理它们。

2.9.4  禁用索引锁

我们强烈地建议读者不要对Lucene的锁机制进行随意修改,不要漠视与锁相关的异常。然而 在 一些情况下,你也许想禁用Lucene当中的锁机制,并且这样做不会破坏索引文件。例如,应用程序可能需要访问存储在CD-ROM上的Lucene索引。 因为CD是一种只读介质,这意味着应用程序对索引的操作也是只读模式的。换句话说,该应用程序只使用Lucene来搜索索引而不需要对索引进行任何形式的 修改。尽管Lucene已经将锁文件保存在系统的临时目录(这个目录通常可以被系统的所有用户打开以用于写操作)中,但是你仍可以通过将 disableLuceneLocks这个系统属性设定为“true”,从而禁用 write.lock和commit.lock文件。

[转载]Lucene中创建索引的效率和删除索引的实现 - Alic - 博客园

mikel阅读(813)

转载Lucene中创建索引的效率和删除索引的实现 – Alic – 博客园.

越来越多的人利用开源组件 Lucene来开发自己的搜索引擎。在数据量不大的情况下,我们不会太关注创建索引的效率;但是,但数据达到一定的数量是,我们就不得不考虑如何提高创建索引的性能,以缩短索引创建的时间。

我们是用Lucene中提供的类IndexWriter来创建索引的,所以我们不妨先看一看IndexWriter类中关系到索引创建效率的几个方法。

一、SetMergeFactor(合并因子)

SetMergeFactor是控制segment合并频率的,其决定了一个索引块中包括多少个文档,当硬盘上的索引块达到多少时,将它们合并成一个较大的索引块。当MergeFactor值较大时,生成索引的速度较快。MergeFactor的默认值是10,建议在建立索引前将其设置的大一些。

二、SetMaxBufferedDocs(最大缓存文档数)

SetMaxBufferedDocs是控制写入一个新的segment前内存中保存的document的数目,设置较大的数目可以加快建索引速度,默认为10

三、SetMaxMergeDocs(最大合并文档数)

SetMaxMergeDocs是控制一个segment中可以保存的最大document数目,值较小有利于追加索引的速度,默认Integer.MAX_VALUE,无需修改。

   

在创建大量数据的索引时,我们会发现索引过程的瓶颈在于大量的磁盘操作,如果内存足够大的话,我们应当尽量使用内存,而非硬盘。可以通过SetMaxBufferedDocs来调整,增大Lucene使用内存的次数。

如果内存足够大的话,我们也可以在索引过程中完全避免使用硬盘。Lucene支持使用文件系统和内存两种方式创建索引,我们可以先把索引写入到RAMDirectory,达到一定数量时再批量写进FSDirectory,减少磁盘操作次数。相关的代码如下:

RAMDirectory rmd = new RAMDirectory();
IndexWriter writer = new IndexWriter(rmd, new StandardAnalyzer(), true);
while (not eof)             //
遍历
{
Document doc = new Document();

doc.Add(…); //Add Fields

writer.AddDocument(doc);
}

writer.SetUseCompoundFile(true);

writer.Optimize();

writer.Close();

另外,SetUseCompoundFile这个方法可以使Lucene在创建索引库时,会合并多个 Segments 文件到一个 .cfs 中。此方式有助于减少索引文件数量,对于将来搜索的效率有较大影响。

若需要从索引中删除某一个或者某一类文档,IndexReader提供了两种方法:

reader.DeleteDocument(int docNum)

reader.DeleteDocuments(Term term)

前者是根据文档的编号来删除该文档,docNum是该文档进入索引时Lucene的编号,是按照顺序编的;后者是删除满足某一个条件的多个文档。

在 执行了DeleteDocument或者DeleteDocuments方法后,系统会生成一个*.del的文件,该文件中记录了删除的文档,但并未从物 理上删除这些文档。此时,这些文档是受保护的,当使用Document doc = reader.Document(i)来访问这些受保护的文档时,Lucene会报“Attempt to access a deleted document”异常。如果一次需要删除多个文档时,可以用两种方法来解决:

1.               删除一个文档后,用IndexWriterOptimize方法来优化索引,这样我们就可以继续删除另一个文档;

2.               先扫描整个索引文件,记录下需要删除的文档在索引中的编号。然后,一次性调用DeleteDocument删除这些文档,再调用IndexWriterOptimize方法来优化索引。

[转载]SQLServer全文搜索 - 永春 - 博客园

mikel阅读(941)

[转载]SQLServer全文搜索 – 永春 – 博客园.

全文搜索的核心引擎建立在Microsoft Full-Text Engine for SQL Server (MSFTESQL) 服务提供支持

 

使用全文搜索可以快速、灵活地为存储在 Microsoft SQL Server 数据库中的文本数据的基于关键字的查询创建索引。与仅适用于字符模式的 LIKE 谓词不同,全文查询将根据特定语言的规则对词和短语进行操作,从而针对此数据执行语言搜索

 

Microsoft SQL Server 2005 中,全文搜索用于提供企业级搜索功能。由于在性能、可管理性和功能方面的显著增强,全文搜索可为任意大小的应用程序提供强大的搜索功能。

 

对大量非结构化的文本数据进行查询时,使用全文搜索获得的性能优势会得到充分的表现。对数百万行文本数据执行的 LIKE 查询可能需要花费几分钟时间才能返回结果;但对同样的数据,全文查询只需要几秒或更少的时间,具体取决于返回的行数。

 

可以对包含 charvarchar nvarchar 数据的列创建全文索引。也可以对包含格式化二进制数据(如存储在 varbinary(max) image 列中的 Microsoft Word 文档)的列创建全文索引。不能使用 LIKE 谓词来查询格式化的二进制数据

 

 

 

最简步骤:(以AdventureWorks数据库中的databaselog表中的event字段为例)

 

1、  启用全文索引:

 

use AdventureWorks
exec sp_fulltext_database ‘enable’

 

2、  全文索引是存储在指定的文件系统中的,而不是SQLServer中。

 

exec sp_fulltext_catalog ‘Cat_Desc’, ‘create’, ‘f:\ft’

 

创建全文索引的目录

 

3、  对表创建全文索引

 

exec sp_fulltext_table ‘databaselog’, ‘create’, ‘Cat_Desc’,

 

‘PK_DatabaseLog_DatabaseLogID’

 

在已有的表上根据已有的索引创建全文索引

 

 

 

4、  对表中的列添加全文索引

 

exec sp_fulltext_column ‘databaselog’, ‘event’, ‘add’

 

 

 

5、  表启动完全填充

 

exec sp_fulltext_table ‘databaselog’, ‘start_full’

 

 

 

6、  执行全文检索

select * from freetexttable(databaselog, event,‘ALTER_TABLE’);

[转载]Windows下配置使用MemCached - 彼岸あ年華ツ - 博客园

mikel阅读(855)

[转载]Windows下配置使用MemCached – 彼岸あ年華ツ – 博客园.

  1. 安装配置MemCached服务端
    1. 下载memcached-1.2.6-win32-bin.zip ,解压后得到memcached.exe,就是memcached的主程序了。比如我们放到MemCached服务器下的C:\Program Files\MemCacheD下
下载安装Memcached Manager ,通过这个来管理memcached的服务端。

打开MemCacheD Manager,点击 add Server,填写服务器信息。我这里直接在本地安装了memcached。如图,填完后点击apply,成功的话右侧会出现服务器。

点击Add Instance添加memcached实例。这里有一些配置信息。Ip,端口,内存等等,不解释了。点击apply后会提示你是否现在启动,我们这里选是

成功后发现右侧已经有实例了,到此服务端配置完毕。

 

二、php安装Memcached模块支持

1、下载php_memcache.dll模块,

你可以从http://downloads.php.net/pierre/找到对应的版本,
php5.3对应php_memcache-2.2.6-5.3-vc9-x86.zip

将php_memcache.dll放到php\ext目录下,

2、修改php.ini来加入扩展,并并重启apache服务器

在php.ini加入一行引用扩展,代码如下:

extension=php_memcache.dll

接着在 php.ini 文件里加上:

[Memcache]
memcache.allow_failover = 1
memcache.max_failover_attempts=20
memcache.chunk_size =8192
memcache.default_port = 11211

最好就放在刚才写 “extension=php_memcache.dll” 的下面。(这是默认的一些配置),重启apache服务器,

然后查看一下phpinfo,如果有memcache,那么就说明安装成功!

 

测试windows下的Memcached
测试代码如下:

复制代码
<?php  
$mem = new Memcache;  
$mem->connect("127.0.0.1", 11211);  
$mem->set('key', 'Hello Memcached!', 0, 60);  
$val = $mem->get('key');  
echo $val;  
?>
复制代码

 

推荐地址:http://blog.csdn.net/yuhui_fish/article/details/7762299

http://down.chinaz.com/server/201111/1407_1.htm

[转载]介绍一款网站前台图片滚动利器之"switchable" - 鑫中有志 - 博客园

mikel阅读(982)

[转载]介绍一款网站前台图片滚动利器之”switchable” – 鑫中有志 – 博客园.

一、简单介绍:JQuery.Switchable是一款整合了 Tabs、Slide、Scrollable等常见UI组件的JQuery插件,在这里,简答说说他的Slide。像Tabs,在JQuery-UI和 jQuery-EasyUI中也有Tabs。在我的博客中也使用到了,请看完整“信息发布系统”系列,下面就是jQuery-EasyUI的Tabs。

 

 

下面 这些是使用Switchable实现的效果:

 

                                              

 

1、今天就说说前台图片滚动的“Slide”,大家应该并不陌生,因为大部分网站都用到了这种效果。如下:官方地址下载

 

 

2、今天就带着大家完成如上图的功能,包括添加完图片后,自动添加数字(1、2、3、4),导向相应的页面。

 

(1)项目结构如下:

 

 

(2)、首先得在Scripts文件夹下加入jquery.switchable[all].min.js。如果没有的话,请留言。

 

(3)、在Home/Index中View中:

 

1、$(“#trigger1”).switchable(“#slide1 > div > img”, { effect: “scroll”, speed: .2 }).autoplay(2).carousel();

 

属性的简单介绍:

 

effect:切换效果。内置效果有:default”:最简单的显/隐效果”还有“fade”,也可以自己手动添加效果

 

speed:动画的速度,单位是秒。默认值即700毫秒。

 

delay:触发延迟,单位是秒。默认值即100毫秒。

 

easing:”swing”:动画的效果。

 

circular:false,是否循环。

 

vertical:是否纵向切换。

 

方法的简答介绍:

 

autoplay:是否自动播放。

 

interval:自动播放间隔,单位是秒。默认值即3000毫秒。当插件的参数是数字时,就是配置这个属性。例如:
                                                  $(“trigger-container-selector”).switchable(“panel-selector”).autoplay(1.8);

 

autopause:通过该属性可以控制鼠标 mouseenter 进 panel 区域是否暂停动画。mouseleave 后将自动恢复动画。

 

                       

复制代码
@{
    ViewBag.Title = "主页";
}
<link href="@Url.Content("~/Content/SwitchTabs.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.switchable[all].min.js")" type="text/javascript"></script>

<script type="text/javascript">
    $(document).ready(function () {
        $("#trigger1").switchable("#slide1 > div > img", { effect: "scroll", speed: .2 }).autoplay(2).carousel(); //图片滚动
    });
    function SetVisable(index) {
        $(".nav_content li").eq(index).addClass("current").siblings().removeClass("current");
    }
</script>

<div id="slide1" class="slide-panel" style="border-style: none; width: 680px;">
    <div>
        @Html.Raw(Server.HtmlDecode(Html.Action("PictureRoll", "Extensions").ToHtmlString()))
    </div>
</div>
<div id="trigger1" class="slide-trigger" align="center">
    <!-- 自动创建 triggers -->
</div>

@using (Html.BeginForm("AddImage", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    <input type="file" name="photo" />   
    <input type="submit" value="添加" />
}

<div>

</div>
复制代码

 

2、因为有一个<input type=”file” />上传文件。所以得添加这种格式:{enctype = “multipart/form-data” },简单解释一下:是设置表单的MIME 编码。默认情况,这个编码格式是application/x-www-form-urlencoded,不能用于文件上传;只有使用了multipart /form-data,才能完整的传递文件数据,进行下面的操作。

 

3、 @Html.Raw(Server.HtmlDecode(Html.Action(“PictureRoll”, “Extensions”).ToHtmlString())), 图片这部分是在Extensions中添加了一个Action,渲染出来图片。下面看看Extensions/PictureRoll中的内容,使用的 是:XElement,进行XML的操作。读取XML的数据,不介绍它的使用了,都能看懂。使用System.Xml.Linq.最后 ViewBag.picture=picture。把图片渲染出来。前台进行接收。

 

                        

复制代码
        private const string picturePath = "~/XML/Pic.xml";
        public ActionResult PictureRoll()
        {
            string picture = string.Empty;
            XElement xe = XElement.Load(Server.MapPath(picturePath));
            var query = from value in xe.Elements("img")
                        select value;
            foreach (var item in query)
            {
                picture += "<img src=\"" + item.Element("path").Value + "\" title=\"" + item.Element("title").Value + "\" alt=\"" + item.Element("title").Value + "\" />";
            }
            ViewBag.picture = picture;
            return View();
        }
复制代码

 

                       

@{
    Layout = null;
}
@ViewBag.picture

 

4、还得添加一个CSS。改变图片和1234链接的样式。

 

               

复制代码
/* SwichTab图片滚动 */

.slide-trigger{position:relative;top:-25px;width:465px;padding-right:5px;}.slide-trigger a{display:inline-block;margin-right:3px;width:16px;height:16px;line-height:16px;text-align:center;color:#d94b01;background-color:#fff5e1;border:1px solid #f47500;outline:none;overflow:hidden;text-decoration:none;}.slide-trigger a:hover{text-decoration:none;}.slide-trigger a.current{width:18px;height:18px;line-height:18px;font-weight:700;color:#FFF;background:url(/images/t-bg.png) repeat-x;}.slide-panel{position:relative;width:950px;height:240px;overflow:hidden;border:1px solid #B6D1E6;}.slide-panel div{position:absolute;}.slide-panel div img{display:block;width:950px;height:240px;}
#slide1 div { width:2010em; /* 设置足够的宽度 */ }
#slide1 div img { float:left; }
/* SwichTab图片滚动 */
复制代码

 

5、现在运行代码:出现之前介绍的效果了吧。

 

 

(4)添加:上传图片的功能。

 

1、遍历上传上来的文件,使用HttpPostedFileBase进行接收。注意:参数 photo得和前台的name得一致。

 

2、判断上传文件的类型,防止一些恶意用户,上传木马。

 

3、item.SaveAs(Server.MapPath(“~/images/” + Path.GetFileName(item.FileName)));进行保存文件。

 

4、接下来又是XElement的使用,SetElementValue,为节点进行赋值。最后也得保存!Save();

 

5、出现上图效果!

 

                         AddImage Action

   二、总结:一个图片焦点轮换的效果的实现,如果对您有一点点帮助的话,右下角”推荐“支持一下,让更多的朋友来交流学习!如有问题,请留言!

[转载]c#实现QQ群成员列表导出及邮件群发之模拟QQ登陆 - 狼性法则 - 博客园

mikel阅读(948)

[转载]c#实现QQ群成员列表导出及邮件群发之模拟QQ登陆 – 狼性法则 – 博客园.

前言

本文的重要部分:

1、抓包获取QQ空间或者邮箱登陆地址,分析参数,用户名,密码,验证码,gtk,随即数。

2、获取每次登陆需要的验证码

3、用户名,密码,验证码加密得到登陆密码

4、Http模拟登陆拿cookie

本文实验的思路也可换另一种方式,抓包获取登陆的js,用代码操作js来计算密码 ,gtk等实现登陆。

抓包

1、我们来访问qq空间的地址http://i.qq.com/,打开网站,我们可以看到登陆的对话框,首先我们输入一个错误的账号和密码,抓起提交的地址。

2、输入用户名和密码之后我们用抓包工具看到有2个后台请求的地址,如下图

我们可以根据返回的代码可以看出第二个图的地址是QQ登陆后台get请求的地址,所以我们实现登陆就是像这个地址提交数据。第一张图则是空间登陆每 次返回的验证码产生的地址,通过多次输入正确用户名密码,第一张图地址返回类似ptui_checkVC(‘0′,’!JWE’,’\x00\x00 \x00\x00\x46\x86\xd2\x36′);代码,其中括号内参数与登陆请求地址中verifycode=!JWE每次相同,这个便是登陆的 验证码,登陆地址则返回类似ptuiCB(‘0′,’0′,’http://qzs.qq.com/qzone/v5 /loginsucc.html?para=izone’,’0′,’登录成功!’, ‘xxx’);返回此代码则说明登陆成功。

通过抓包我们提取出来的2个地址:

1、验证码获取地址(输入用户名时返回):

http://check.ptlogin2.qq.com/check?regmaster=&uin={0}&appid=549000912&js_ver=10038&js_type=1&login_sig=46Flu6g0o2A1hcewVAPUpItgSNJncoqujR4vudgX8ZRnruuCSOzMGcbd3CnQhw0y&u1=http%3A%2F%2Fqzs.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone&r={1}

参数0:QQ账号,参数1:随即数

说明:一般情况下是不会产生手动输入验证码的情况下,若是QQ号码不存在或者错误,此时会出现图片验证码,我们可以用一个处理一下显示在imge中手动输入。

2、登陆提交地址:

http://ptlogin2.qq.com/login?u={0}&p={1}&verifycode={2}&aid=549000912&u1=http%3A%2F%2Fqzs.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone&h=1&ptredirect=0&ptlang=2052&from_ui=1&dumy=&low_login_enable=0&regmaster=&fp=loginerroralert&action=11-52-1375668422981&mibao_css=&t=1&g=1&js_ver=10038&js_type=1&login_sig=46Flu6g0o2A1hcewVAPUpItgSNJncoqujR4vudgX8ZRnruuCSOzMGcbd3CnQhw0y

参数0:QQ号码,参数1:加密以后的密码,参数2:返回的验证码

本人文字功底实在太差,说不太清楚,抓包需要我多次试验,多次分析,才可取得正确的地址。登陆的最重要目的是获取请求页面的cookie。

代码部分

1、http请求帮助代码

复制代码
 1     public class HttpHelper
 2     {
 3         /// <summary>
 4         /// 获取字符流
 5         /// </summary>
 6         /// <param name="url"></param>
 7         /// <param name="cookieContainer"></param>
 8         /// <returns></returns>
 9         public static Stream GetStream(string url, CookieContainer cookieContainer)
10         {
11             HttpWebRequest httpWebRequest = null;
12             HttpWebResponse httpWebResponse = null;
13 
14             try
15             {
16                 httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url);
17                 httpWebRequest.CookieContainer = cookieContainer;
18                 httpWebRequest.ContentType = contentType;
19                 httpWebRequest.Referer = referer;
20                 httpWebRequest.Accept = accept;
21                 httpWebRequest.UserAgent = userAgent;
22                 httpWebRequest.Method = "GET";
23                 httpWebRequest.ServicePoint.ConnectionLimit = int.MaxValue;
24 
25                 httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
26                 Stream responseStream = httpWebResponse.GetResponseStream();
27 
28                 return responseStream;
29             }
30             catch (Exception)
31             {
32                 return null;
33             }
34 
35         }
36 
37         /// <summary>
38         /// 获取HTML
39         /// </summary>
40         /// <param name="url"></param>
41         /// <param name="cookieContainer"></param>
42         /// <returns></returns>
43         public static string GetHtml(string url, CookieContainer cookieContainer)
44         {
45             Thread.Sleep(1000);
46             HttpWebRequest httpWebRequest = null;
47             HttpWebResponse httpWebResponse = null;
48             try
49             {
50                 httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url);
51                 httpWebRequest.CookieContainer = cookieContainer;
52                 httpWebRequest.ContentType = contentType;
53                 httpWebRequest.Referer = referer;
54                 httpWebRequest.Accept = accept;
55                 httpWebRequest.UserAgent = userAgent;
56                 httpWebRequest.Method = "GET";
57                 httpWebRequest.ServicePoint.ConnectionLimit = int.MaxValue;
58 
59                 httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
60                 Stream responseStream = httpWebResponse.GetResponseStream();
61                 StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);
62                 string html = streamReader.ReadToEnd();
63 
64                 streamReader.Close();
65                 responseStream.Close();
66 
67                 httpWebRequest.Abort();
68                 httpWebResponse.Close();
69 
70                 return html;
71             }
72             catch (Exception)
73             {
74                 return string.Empty;
75             }
76 
77         }
78     }
复制代码

2、获取验证码

复制代码
    //取验证码
        public static string GetVerfiyCode(string qqnum,CookieContainer cookie)
        {
            Random rand = new Random();
            double r = rand.NextDouble();
            string checkcodeurl = string.Format(@"http://check.ptlogin2.qq.com/check?uin={0}&appid=1006102&r={1}", qqnum, r);
            Stream stream = HttpHelper.GetStream(checkcodeurl, cookie);
            StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
            return streamReader.ReadToEnd();
        }
        //取图片验证码,可将验证码直接输出在image中
        public static Stream GetVerfycodeImage(CookieContainer cookie, string qqnum)
        {
            Random rand = new Random();
            double r = rand.NextDouble();
            string codeimageurl = string.Format("http://captcha.qq.com/getimage?aid=1006102&r={0}&uin={1}", r, qqnum);
            return HttpHelper.GetStream(codeimageurl, cookie);
        }    
复制代码

通过GetVerfiyCode()的返回 值判断,是否需要手动输入验证码

复制代码
//得验证吗
            if (retString.Contains("ptui_checkVC('0','"))
            {
         //不需要手动输入
                this.txtverfiycode.Text = retString.Replace("ptui_checkVC('0','", "").Replace("'", "").Replace(")", "").Replace(";", "").Substring(0, 4);
            }
            else if (retString.Contains("ptui_checkVC('1',"))
            {
        //需要手动输入,将验证码输出在image中
                this.vefycodpicbox.Image = Image.FromStream(GetVerfycodeImage(retString, this.txtUseraccount.Text));
            }
复制代码

3、计算密码,密码的计算方法可以通过js计算,或者直接使用如下C#版的,总的来说腾讯的密码加密比较麻烦,需要QQ号码,密码,验证码三个参数,有兴趣的可自己研究下。

代码如下:

复制代码
 1 public static class PasswordHelper
 2     {
 3         /// <summary>
 4         /// 根据QQ号码和验证码加密密码
 5         /// </summary>
 6         /// <param name="qqNum">QQ号码</param>
 7         /// <param name="password">QQ密码</param>
 8         /// <param name="verifycode">验证码</param>
 9         /// <returns>密码密文</returns>
10         public static string GetPassword(string qqNum, string password, string verifycode)
11         {
12             //uin为QQ号码转换为16位的16进制
13             int qq;
14             int.TryParse(qqNum, out qq);
15 
16             qqNum = qq.ToString("x");
17             qqNum = qqNum.PadLeft(16, '0');
18 
19             String P = hexchar2bin(md5(password));
20             String U = md5(P + hexchar2bin(qqNum)).ToUpper();
21             String V = md5(U + verifycode.ToUpper()).ToUpper();
22             return V;
23         }
24 
25         public static string md5(string input)
26         {
27             byte[] buffer = MD5.Create().ComputeHash(Encoding.GetEncoding("ISO-8859-1").GetBytes(input));
28             return binl2hex(buffer);
29         }
30 
31         public static string binl2hex(byte[] buffer)
32         {
33             StringBuilder builder = new StringBuilder();
34             for (int i = 0; i < buffer.Length; i++)
35             {
36                 builder.Append(buffer[i].ToString("x2"));
37             }
38             return builder.ToString();
39         }
40 
41         public static string hexchar2bin(string passWord)
42         {
43             StringBuilder builder = new StringBuilder();
44             for (int i = 0; i < passWord.Length; i = i + 2)
45             {
46                 builder.Append(Convert.ToChar(Convert.ToInt32(passWord.Substring(i, 2), 16)));
47             }
48             return builder.ToString();
49         }
50     }
复制代码

到此为止,登陆需要的参数已经全了,下面就是实现登陆拿cookie。

4、登陆的方法与获取验证码一样

复制代码
//登录方法
        public static bool IsLogin(string qqnum,string code,string passwords,CookieContainer cookie)
        {
            string password = PasswordHelper.GetPassword(qqnum, passwords, code);
            string loginUrlstring = @"http://ptlogin2.qq.com/login?u=" + qqnum + "&p=" + password + "&verifycode=" + code + "&aid=1006102&u1=http%3A%2F%2Fid.qq.com%2Findex.html&h=1&ptredirect=1&ptlang=2052&from_ui=1&dumy=&fp=loginerroralert&action=8-29-82478035&mibao_css=&t=1&g=1";
            Stream stream = HttpHelper.GetStream(loginUrlstring, cookie);
            StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
            string retString = streamReader.ReadToEnd();//        retString    "ptuiCB('0','0','http://id.qq.com/index.html','1','登录成功!', 'xxx');\r\n"    string
            return retString.Contains("ptuiCB('0',") ? true : false;
        }
复制代码

我们可以直接调用此方法验证是否登陆成功,同时将cookie保存在一个全局变量中供以后使用。

到此为止,QQ用http模拟登陆已经成功,完成了获取群列表的第一步。下面一篇就到获取群列表,群成员。

 

临时写的代码,时间仓促,比较杂乱,有什么出错的地方欢迎指出。若资料有用,帮忙顶一下。

[转载]c#实现QQ群成员列表导出及邮件群发之群列表及群成员获取 - 狼性法则 - 博客园

mikel阅读(1137)

[转载]c#实现QQ群成员列表导出及邮件群发之群列表及群成员获取 – 狼性法则 – 博客园.

前言

通过前两篇的代码编写已经能正常模拟QQ登陆,拿到cookie也就是我们进行以后相关操作的金钥匙。这篇文章将通过代码的方式去获取登陆QQ账号的群列表,某群里面的群成员列表。

本文重点:

1、抓包获取QQ群列表访问地址

2、抓包获取QQ群成员列表

3、参数值计算,gtk的计算方法(网上几乎找不到的计算方法)

4、处理返回值

本文完成这一系列也基本算是完成了,到此篇为止,可正常获取群成员,当然也就是拿到了QQ邮箱,如果想进行其他操作的话,同样也可以用次方式来实现。

抓包

1、获取QQ群列表

首先我们用登陆成功的QQ去访问我们的群空间,群空间的头部有我的群,在此我们可以看到登陆QQ上面所有的群列表,也就是说此页面必有返回该列表的请求地址,做web开发的大体都差不多,这种东西多用Js处理,相信我们也可以抓到此地址,如下图所示。

相比而言这个地方的抓包不需要挨个去看了,这次给的JS请求地址很直观通过名字我们一眼就可以看出是群列表,在抓包过程中我们会发现有一条get_group_list的地址,不用说这个就是了,查看这个js返回的数据刚好便是群列表,如下图

右侧group便是登陆QQ上所有的qq群,包含总数目等信息。

相关请求地址:

http://qun.qzone.qq.com/cgi-bin/get_group_list?uin={0}&ua=Mozilla%2F5.0%20(Windows%20NT%206.1%3B
%20WOW64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F28.0.1500.95%20Safari
%2F537.36&random={1}&g_tk={2}

参数0:账号,参数1:随即数,参数2:此次访问的gtk值

2、获取访问群空间的成员列表

访问具体群空间,在群空间右侧有群成员选项,选择以后变回返回所有的群成员,如图

在点击群成员列表的时候我们通过抓包工具可抓起返回数据的地址,这个也很明显,很直观get_group_member,一看便知是返回群成员,查看返回的数据,下面给出fiddler和谷歌浏览器返回的数据格式

请求地址:

http://qun.qzone.qq.com/cgi-bin/get_group_member?
uin={0}&groupid={2}&random={3}&g_tk={4}

参数0:账号,参数1:群号,参数2:随即数,参数3:此次访问的gtk值

代码部分

1、gtk参数的计算(此参数的计算方法几乎没有)

  通过上述抓包我们看到,数据包主体部分最后一个参数就是g_tk值,一般是一串数字。那这个值到底怎么算出来的呢?因 为我们在网页登录QQ的时候,腾讯都会通过cookies里的skey值来计算,用js来算。既然在运算的时候执行了js脚本,那么我们就可以在抓包中获 得。那g_tk是通过什么算法算出来的?其实很简单,当我们得到skey后,循环取单字符的二进制并取左值.累加之后就得到后面的g_tk值了,这听上去 很复杂,不过算法不用我们自己写,我们只需要执行在腾讯网页登录的时候所执行的那个js脚本就可以了。在这里我已经将此算法转化成C#代码:

复制代码
/// <summary>
        /// 计算gtk
        /// </summary>
        /// <returns></returns>
        public static Int32 GetGTK(List<Cookie> cookies)
        {
            int gtk = 0;
            foreach (var item in cookies)
            {
                if (item.Name == "skey")
                {
                    int hash = 5381;
                    string str = item.Value;
                    for (int i = 0, len = str.Length; i < len; ++i)
                    {
                        hash += (hash << 5) + str.ElementAt(i);
                    }
                    gtk = hash & 0x7fffffff;
                }
            }
            return gtk;
        }
复制代码

同时也给出一个遍历CookieContainer的方法:

复制代码
/// <summary>
        /// 遍历CookieContainer
        /// </summary>
        /// <param name="cc"></param>
        /// <returns></returns>
        public static List<Cookie> GetAllCookies(CookieContainer cc)
        {
            List<Cookie> lstCookies = new List<Cookie>();

            Hashtable table = (Hashtable)cc.GetType().InvokeMember("m_domainTable",
                System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField |
                System.Reflection.BindingFlags.Instance, null, cc, new object[] { });

            foreach (object pathList in table.Values)
            {
                SortedList lstCookieCol = (SortedList)pathList.GetType().InvokeMember("m_list",
                    System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField
                    | System.Reflection.BindingFlags.Instance, null, pathList, new object[] { });
                foreach (CookieCollection colCookies in lstCookieCol.Values)
                    foreach (Cookie c in colCookies) lstCookies.Add(c);
            }
            return lstCookies;
        }
复制代码

唯一一个重要的参数解决以后便是请求地址,返回数据了,收获果实的时候了。

2、获取群列表

下面先写两个处理返回Json字符串的方法,本文没有用第三方的处理工具,二是用.net 3.5以后出现的System.Web.Extensions.dll中提供的序列化方法实现。

复制代码
//json解析群列表
    public class QQGropJson
    {
        public int code { get; set; }
        public int defaults { get; set; }
        public QQGrouplist data { get; set; }
        public string message { get; set; }
        public int subcode { get; set; }
    }
    public class QQGroupInfo
    {
        public int count { get; set; }
        public int flag { get; set; }
        public string groupid { get; set; }
        public string groupname { get; set; }
    }
    public class QQGrouplist
    {
        public List<QQGroupInfo> group { get; set; }
        public int guid { get; set; }
        public int total { get; set; }
    }
    //以下三个类解析群成员
    public class QQMember
    {
        public string iscreator { get; set; }
        public string ismanager { get; set; }
        public string nick { get; set; }
        public string uin { get; set; }
    }
    public class QQGroup
    {
        public int code { get; set; }
        public int subcode { get; set; }
        public string message { get; set; }
        public int defaults { get; set; }
        public QQInfo data { get; set; }
        public int level { get; set; }
        public string nick { get; set; }
        public int option { get; set; }
        public int total { get; set; }
    }
    public class QQInfo
    {
        public int alpha { set; get; }
        public int bbscount { get; set; }
        public int classs { get; set; }
        public string createtime { get; set; }
        public int filecount { get; set; }
        public string fingermemo { get; set; }
        public string groupmemo { get; set; }
        public string groupname { get; set; }
        public List<QQMember> item { get; set; }
    }
复制代码

过滤返回的_callback标签:

复制代码
/// <summary>
        /// 过滤返回的标签_callback();
        /// </summary>
        /// <param name="retstr"></param>
        /// <returns></returns>
        public static string FilterReturnstring(string retstr)
        {
            string result = retstr.Substring(10, retstr.Length - 12);
            return result;
        }
复制代码

解析返回的json数据

复制代码
//解析json字符串返回群信息
        public static List<QQGroupInfo> GetGropList(string jsonstring)
        {
            var js = new JavaScriptSerializer();
            QQGropJson grouplist = new QQGropJson();
            grouplist = js.Deserialize<QQGropJson>(jsonstring);
            return grouplist.data.group;
        }
复制代码

获取群列表

复制代码
var list = HttpHelper.GetAllCookies(_cookie);
            string gtk = HttpHelper.GetGTK(list).ToString();
            ////群列表
            string grouplisturl = string.Format(@"http://qun.qzone.qq.com/cgi-bin/get_group_list?uin={0}&random={1}&g_tk={2}",usernum,rand.NextDouble(),gtk);
            string tmp = HttpHelper.GetHtml(grouplisturl, _cookie);
            List<QQGroupInfo> grouplist = HttpHelper.GetGropList(HttpHelper.FilterReturnstring(tmp));
复制代码

解析群成员列表json的方法

复制代码
/// <summary>
        /// 群成员列表
        /// </summary>
        /// <param name="jsonstring"></param>
        /// <returns></returns>
        public static List<QQMember> GetMemberList(string jsonstring)
        {
            var js = new JavaScriptSerializer();
            QQGroup group = new QQGroup();
            group = js.Deserialize<QQGroup>(jsonstring);
            return group.data.item;
        }
复制代码

以上方法grouplist便是群列表账号

获取某一群的成员列表

复制代码
////群成员
            var list = HttpHelper.GetAllCookies(_cookie);
            string gtk = HttpHelper.GetGTK(list).ToString();
            string groupnumber = this.cmbQQgroup.SelectedValue.ToString();
            string groupmemberlist = string.Format(@"http://qun.qzone.qq.com/cgi-bin/get_group_member?uin={0}&groupid={1}&random={2}&g_tk={3}", usernum, groupnumber,rand.NextDouble(),gtk);
            string tmp = HttpHelper.GetHtml(groupmemberlist, _cookie);
            grouplist = HttpHelper.GetMemberList(HttpHelper.FilterReturnstring(tmp));//成员列表
复制代码

文章到此,群成员拿到了,同时也拿到了QQ邮箱。

结语

  本文的重点在于拿着cooke这把钥匙去开门,相对比较简单,唯一一个比较难的就是gtk参数的计算方法,这个在QQ空间对日志等操作的时候是必不可少的参数。

时间仓促,代码及文章比较杂乱,有什么出错的地方欢迎指出。若资料有用,帮忙顶一下。

[转载]开发者必备的6款源码搜索引擎 - 好男孩 - 博客园

mikel阅读(943)

[转载]开发者必备的6款源码搜索引擎 – 好男孩 – 博客园.

在推动技术变革上,开源运动发挥了非常显著的作用。而Linux成功地将开源转换成商务模式,给广大开源工作者带来了更大的信心和勇气。目前,开源已成为主流,在未来的几年内,它的足迹将会遍布前沿教育、航空航天(如无人驾驶飞机)等许多领域。

借鉴现有的开源项目或开源代码,对于初级开发者来说,不失为一种很好的编程手段,但千万不要单纯地“用”,更多地是理解与提升,这样才会进步。

下面为大家介绍6款源码搜索引擎,让你找到更适合你的源码。

1.Ohloh

  Ohloh Code是最大且非常全面的源码搜索引擎,根据FOSS目录来看,其拥有超过10亿行代码且在不断更新,它不仅是免费的,而且还可以通过社区进行免费编 辑。通过搜索可以索引到所有的文本文件,并且对43种编程语言都带有语法提示功能。此外,搜索语法非常灵活,可以为你查找到各种不同类型的类。搜索引擎目 前不支持正则表达式。

2.Krugle 

  Krugle是一个开源的搜索门户,基本上主流的开源代码都有收录,比如Apache、JavaDocs、SourceForge 等。你还可以搜索使用C++、Java、Perl、SQL、Ruby、XML、HTML等语言编写代码。Krugle还有一个非常高级的搜索功能,帮你快 速定位到正确的API、库、示例代码或文档。在搜索结果页面,你可以直接浏览项目源码。

3.SearchCode 

  SearchCode从Github、BitBucket、CodePlex、SourceForge、Fedora等代码仓库里筛选了近 160亿行开源代码,代码和文档由一位单独的开发者维护。你可以使用文件扩展、特定代码库名字、URL、正则表达式、特殊字符等过滤器对源码进行过滤,以 便搜到你想要的代码。具体示例和文档代码搜索支持,可以参照这个例子

4.NerdyData

  NerdyData.com是一个可以搜索网页源代码中内容的搜索引擎,它已经拥有超过140万个Web代码索引,如果你是一名Web开发人 员,你可以搜索到HTML标记、JavaScript代码或CSS样式代码段。NerdyData的目标不仅仅是做一个简单的源码搜索引擎,他们也在围绕 Web开发流程来开发一系列功能

5.Symbol Hound

  当涉及到搜索特殊符号时,谷歌和其他搜索引擎并不是最佳选择。例如谷歌,去掉了许多标点符号和特殊符号。如果你想搜索含有特殊字符的变量和错误代码,使用普通的搜索引擎可能不会返回你期望的结果。Symbol Hound的出现解决了这一问题。

6.Merobase

  Merobase搜索引擎与其他不一样,它并不是用来搜索源码,而是帮你搜索和定位软件组件,可以搜索Java、C++、C#等语言编写的组 件。Merobase还可以搜索基于查询的简单文本接口,你也可以搜索基于函数、面向对象、名称、测试驱动的查询,这也是它的一大亮点。

前人栽树,后人乘凉。看他人的代码并且学会使用也是一种很好的学习方法。随着开源事业的不断发展与壮大,你可以在这些搜索引擎上搜索到任何你想要的代码。

[转载]web前端学习笔记-瀑布流的算法分析与代码实现 - 苏若年 - 博客园

mikel阅读(962)

[转载]web前端学习笔记-瀑布流的算法分析与代码实现 – 苏若年 – 博客园.

瀑布流效果目前应用很广泛,像花瓣,新浪轻博,蘑菇街,美丽说等好多网站都有.也有好多支持该效果的前段框架,今天学习了一下这种效果的实现,不依赖插件,自己动手分析实现过程,为了便于叙述清楚,分析中的一些名词为自己拟定,不当之处还望见谅.

 

思路分析

步骤一:构建成行元素 + 寻找新增元素追加位置

瀑布流所有元素的宽度是固定的,我们用浏览器的宽度除以每个瀑布流块的宽度,就是每一行可容纳的瀑布流块的个数.因为,每个瀑布流块的高度不一,我们姑且把组成一行的这组元素称为成行元素,在成行元素放置完毕后,我们如果要再增加一个元素,那么它的位置应该这样找?

“获取成行元素集合中高度最低的那个元素,待放置的元素的top值应该是这个最低元素的高,left值应该是这个最低元素的left值”

这样,新增的这一个元素我们就找到了它存放的位置.这样成行元素中的最低高度值就变为了原来的高度+新增元素的高度.

 

步骤二:重复步骤一,依赖成行元素追加新元素

步骤一中我们已经实现了一次成行元素追加一个新的元素,这样新元素增加之后我们就构建了新的成行元素,之后的操作就是在新的成行元素中追加新元素,原理同步骤一.

步骤三:实现滚动位置监听,到底部时加载数据

代码实现

 

实现步骤一描述效果:

实现代码

复制代码
<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>瀑布流效果实现</title>
    <script type="text/javascript" src="scripts/jquery-1.8.2.min.js"></script>
    <script type="text/javascript" src="scripts/jquery.easydrag.handler.beta2.js"></script>
    <script type="text/javascript">
        window.onload=function(){
            //获取父级对象
            var oParent = document.getElementById("main");
            //获取父级[第一个参数]下的所有的子元素[按照第二个参数匹配]
            var aPin = getClassObject(oParent,"pin");
            //获取每一个块的宽度
            var iPinW = aPin[0].offsetWidth;
            // //计算每行放多少个pin(瀑布流块)页面的宽度/每一个瀑布流块的宽度
            var num = Math.floor(document.documentElement.clientWidth/iPinW);
            //重置父级的样式,这样保证图片整体居中
            oParent.style.cssText="width:" + num*iPinW + "px;margin:0 auto;";

            var compareArray = [];
            //将一整行的瀑布流块的高度压入一个数组
            for (var i = 0; i<num; i++) {
                compareArray[i] =     aPin[i].offsetHeight;
            }

            //获取该行瀑布流高度最低的值
            var minHeight = Math.min.apply('',compareArray);
            //alert(compareArray + ",min=" + minHeight);
            //获取改行高度值最小的瀑布流块的索引
            var minHkey = getMinHeightKey(compareArray,minHeight);

            //为新增的瀑布流块增加样式
            aPin[num].style.position = "absolute";
            aPin[num].style.top = minHeight + "px";
            //设定新增加的瀑布流块的top和left
            aPin[num].style.left =aPin[minHkey].offsetLeft + "px";

            //将该索引位置的高度改变为新增后的高度[原来瀑布流块的高度+新增的瀑布流块的高度]
            compareArray[minHkey] += aPin[num].offsetHeight;

        }
        /**
         *     获取parent下所有样式名为className的对象集合
         */
        function getClassObject(parent,className){
            var obj = parent.getElementsByTagName("*");
            var result = [];
            for(var i=0; i<obj.length;i++){
                //变量如果匹配className,将匹配的对象放入数组
                if(obj[i].className==className){
                    result.push(obj[i]);
                }
            }
            return result;
        }

        /**
         *    获取arr数组中值为minH的值在数组中的索引
         */
        function getMinHeightKey(arr,minH){
            for(key in arr){
                if(arr[key] == minH){
                    return key;
                }
            }
        }
    </script>
    <style type="text/css">
        /*设置每一个瀑布流块*/
        #main .pin{
            width:220px;
            height: auto;
            padding: 15px 0px 0px 15px; /*上 右 下 左*/
            float: left;
        }
        /*设置每一个瀑布流块中的图像样式*/
        #main .pin .box{
            width: 200px;
            height: auto;
            padding: 10px;
            background: #FFF;
            border: 1px solid #ccc;
            box-shadow: 0px 0px 6px #ccc; /*中间投影*/
            border-radius: 5px; /*圆角*/
        }
        #main .pin .box img{
            width: 200px;

        }
    </style>
</head>
<body>
    <div id="main">
        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012110120000859759.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012072300483800466.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012101912011350194.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012102421195356552.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012072312335411883.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012082910221472225.jpg">
            </div>
        </div>

    </div>
</body>
</html>
复制代码

实现步骤二描述效果

实现代码

复制代码
<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>瀑布流效果实现</title>
    <script type="text/javascript" src="scripts/jquery-1.8.2.min.js"></script>
    <script type="text/javascript" src="scripts/jquery.easydrag.handler.beta2.js"></script>
    <script type="text/javascript">
        window.onload=function(){
            //获取父级对象
            var oParent = document.getElementById("main");
            //获取父级[第一个参数]下的所有的子元素[按照第二个参数匹配]
            var aPin = getClassObject(oParent,"pin");
            //获取每一个块的宽度
            var iPinW = aPin[0].offsetWidth;
            // //计算每行放多少个pin(瀑布流块)页面的宽度/每一个瀑布流块的宽度
            var num = Math.floor(document.documentElement.clientWidth/iPinW);
            //重置父级的样式,这样保证图片整体居中
            oParent.style.cssText="width:" + num*iPinW + "px;margin:0 auto;";

            var compareArray = [];
            //遍历获取到的所有瀑布流块
            for (var i = 0; i<aPin.length; i++) {
                if(i<num){
                    //成行元素
                    compareArray[i] = aPin[i].offsetHeight;
                }else{
                    //获取成行元素中高度最低的值
                    var minHeight = Math.min.apply('',compareArray);
                    //alert(compareArray + ",min=" + minHeight);
                    //获取成行元素中高度最低元素的索引
                    var minHkey = getMinHeightKey(compareArray,minHeight);
                    //为新增的瀑布流块设置定位
                    aPin[i].style.position = "absolute";
                    aPin[i].style.top = minHeight + "px";
                    //设定新增加的瀑布流块的top和left
                    aPin[i].style.left =aPin[minHkey].offsetLeft + "px";
                    //将该索引位置的高度改变为新增后的高度[原来瀑布流块的高度+新增的瀑布流块的高度]
                    compareArray[minHkey] += aPin[i].offsetHeight;
                }

            }

        }
        /**
         *     获取parent下所有样式名为className的对象集合
         */
        function getClassObject(parent,className){
            var obj = parent.getElementsByTagName("*");
            var result = [];
            for(var i=0; i<obj.length;i++){
                //变量如果匹配className,将匹配的对象放入数组
                if(obj[i].className==className){
                    result.push(obj[i]);
                }
            }
            return result;
        }

        /**
         *    获取arr数组中值为minH的值在数组中的索引
         */
        function getMinHeightKey(arr,minH){
            for(key in arr){
                if(arr[key] == minH){
                    return key;
                }
            }
        }
    </script>
    <style type="text/css">
        /*设置每一个瀑布流块*/
        #main .pin{
            width:220px;
            height: auto;
            padding: 15px 0px 0px 15px; /*上 右 下 左*/
            float: left;
        }
        /*设置每一个瀑布流块中的图像样式*/
        #main .pin .box{
            width: 200px;
            height: auto;
            padding: 10px;
            background: #FFF;
            border: 1px solid #ccc;
            box-shadow: 0px 0px 6px #ccc; /*中间投影*/
            border-radius: 5px; /*圆角*/
        }
        #main .pin .box img{
            width: 200px;

        }
    </style>
</head>
<body>
    <div id="main">
        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012110120000859759.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012072300483800466.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012101912011350194.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012102421195356552.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012072312335411883.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012082910221472225.jpg">
            </div>
        </div>

        <!--每一个小块-->
        <div class="pin">
            <div class="box">
                <img src="images/2012082910024626515.jpg">
            </div>
        </div>

    </div>
</body>
</html>
复制代码

会看到新增的瀑布流块在新的成行元素中自动寻找高度最低的那个元素块的相对位置进行追加.添加更多元素查看效果

步骤三:实现滚动到底部时加载数据
该部分没有什么功能,只是检测滚动条的位置距离浏览器底部的相对距离进行数据加载,加载数据时创建对应的瀑布流块.判断相对距离的实现逻辑如下

复制代码
        function checkScrollSite(){
            var oParent = document.getElementById("main");

            var aPin = getClassObject(oParent,"pin");
            //加载数据依赖最后一个瀑布流块变化
            var lastPinHeight = aPin[aPin.length-1].offsetTop + Math.floor(aPin[aPin.length-1].offsetHeight/2) ;
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            //浏览器高度
            var documentH = document.documentElement.clientHeight;

            if(lastPinHeight<documentH + scrollTop){
                //请求数据
                return true;
            }
            return false;
        }
复制代码

在此感谢,郑印在学习上给予的指导.

转载请注明出处:[http://www.cnblogs.com/dennisit/p/3244987.html]

[转载]Unity3D学习:结合Kinect进行游戏开发 | 孤舟博客

mikel阅读(1889)

[转载]Unity3D学习:结合Kinect进行游戏开发 | 孤舟博客.

最近需要学习Unity3D和Kinect交互进行开发。查阅网上的资料,一直没有找到详尽而又简单的方案。今天终于摸索出来在Unity3D中使用Kinect的方法,特此做个笔记。

一、Unity和Kinect交互的环境配置

我所使用的Unity3D的版本是Unity3.5,使用的Kinect SDK版本是SDK1.6,令Kinect和Unity交互的方法是使用CMU的封装,但是这个方法支持的Kinect SDK版本是SDK1.0,要想使用1.5或1.6版本的kinect SDK,就要做一些相应的修改。完整的方案如下:

1、安装Uinty3D

我使用的Unity3D版本是3.5,也可以在Unity3D官网上下载free版本的Unity。

2、安装Kinect SDK

我使用的SDK版本是1.6,可以在Kinect for Windows官方网站上下载。

3、安装Kinect Wrapper Package for Unity3D

这个就是CMU封装的用于Kinect开发的Unity3D导入包了。以后在Unity3D工程中,如果需要使用和kinect交互,那么就需要在这个工程中导入这个包。点击这里下载KinectWrapperPackage

4、运行示例工程

在CMU提供的KinectWrapperPackage包中,已经包含了一个叫做KinectExample.untiy的Unity示例工程。 在工程中导入KinectWrapperPackage包之后,在Unity的project面板上找到 Scences->KinectExample,双击就可以打开这个示例工程。

或者是单独下载CMU提供的示例工程:newKinect,这个示例工程和导入的包中包含的示例工程是一样的。

打开示例工程后,在Hierarchy面板中,可以看到这些内容:

5、修改导入包中的相关文件

CMU提供的unity导入包支持的kinect SDK版本是1.0,如果想使用其他版本的SDK,就要下载并替换导入包中的一些文件。具体来说就是KinectInterop.cs和KinectSensor.cs这两个文件。

点击这里下载适用于Kinect SDK1.6版本的替换文件

下载完成之后,将下载之后的文件替换掉上面面板中所标识的这两个文件就可以了。然后就可以运行示例工程,体验在unity创建的场景中使用kinect控制人物模型及小球的运动。

使用修改后的导入包

如果你觉得每次导入资源包,然后再修改两个文件实在麻烦,我已经将修改后的资源包导出。以后在每次开发工程时,只需要导入这个修改之后的资源包就行了。

点击这里下载适用于Kinect SDK1.6的资源包

二、用于kinect开发的包中包含的资源

用于kinect开发的包中(Kinect Wrapper Package for Unity3D)包含了所有我们开发unity+kinect需要用到的脚本。

导入包之后,所有我们需要的资源都会出现在project面板中,具体来说是以下的一些资源:

Scenes

  • KinectExample – 示例场景,这个例子向你展示了如何通过在每一个kinect 可以追踪的骨骼点上放置小球来产生骨骼运动。以及怎样使用kinect来控制模型。

Prefabs

  • Kinect_Prefab– 这个预设(prefab)包含了我们开发unity+kinect程序所必需的所有脚本。但是它并没有包含你需要的用于控制模型的控制器(controller)。
  • KinectPointMan – 这个预设(prefab)向你展示了KinectPointController这个控制器是怎样使用的。

Kinect

  • KinectModelControllerV2 – 你需要将这个脚本拖放到你想要应用kinect控制的模型上。为了让模型能够跟上人的节奏,你需要将模型上控制模型动作的关键骨骼拖放到这个脚本暴漏的合 适的变量中 ,也就是将模型中的骨骼与kincet识别到的人的骨骼绑定起来。另外这个脚本暴漏的变量中,还有一个变量时标识模型是受哪个玩家控制。
  • KinectPointController – 你也需要将这个脚本拖放到场景中的游戏物体上。但是这个游戏物体不是模型,而是由一系列分别代表头部、肩部、手等人体部位的点组成。你需要将游戏物体中的 这些关键点都拖放到这个脚本暴漏的外部变量中。这样就可以使用kinect控制游戏物体了,游戏物体是由一系列的点组成的人体。
  • DisplayDepth – 这个脚本得到深度图像。
  • DisplayColor – 这个脚本得到RGB图像。
  • KinectRecorder – 这个脚本用于记录你的动作,并为kinect模拟器(emulator)产生回放文件。
  • KinectEmulator – 这个脚本模拟kinect设备.和KinectRecorder产生的回放文件一起工作。
  • KinectSensor – 这个脚本从Kinect设备中取得数据。需要替换这个文件使用特用版本的SDK.
  • DeviceOrEmulator – 这个脚本设置使用kinect物理设备还是kinect模拟设备.
  • SkeletonWrapper – 这个脚本抓取骨骼数据.
  • DepthWrapper – 这个脚本抓取深度图像数据.
  • KinectInterop – 这个脚本从Microsoft Kinect SDK中抓取数据.
  • Recordings/playbackDefault – 这是为kinect模拟设备准备的默认的回放文件.

Models and Materials

  • rainbowMan
这是示例模型。你需要将KinectModelControllerV2这个脚本拖放在这个模型上,并将组成模型的骨骼与脚本中暴漏的骨骼部位变量绑定在一块。然后就开始测试吧。

三、在自己的工程中使用KinectWrapperPackage包

要想在自己的工程中,使用KinectWrapperPackage包来达到unity3d和kinect交互。首先需要导入这个包到unity中,然后经过下面的步骤完成交互。

1、设置Kinect_Prefab

前面提到过了,如果在Unity中和Kinect交互,Kinect_Prefab这个游戏物体是必不可少的。 这个物体需要存在于你的场景中的某 处。并且在KinectWrapper 脚本中调用了DontDestroyOnLoad这个函数,所以当你切换场景时,它仍旧存在着,因此你不必在每个场景中都实例一个 Kinect_Prefab。

在Unity中,将project面板中的Kinect_Prefab直接拖拽到Hierarchy面板就可以了。

2、控制游戏中的角色

为了控制场景中的角色的移动,你需要将KinectModelControllerV2这个脚本拖拽到场景中的模型上,并绑定骨骼;或者你为了控制一系列的游戏物体,你需要将KinectPointController拖拽这些物体上。下面分别说明这个脚本的使用。

模型控制器:KinectModelControllerV2

为使用模型控制器,请按照以下步骤:

  1. 拖拽脚本资源KinectModelControllerV2到场景中的模型中。
  2. 选择场景中的模型。找到模型中的暴漏变量Sw(它代表Skeleton Wrapper). 并将当前场景中的Kinect_Prefab拖拽给Sw这个变量。
  3. 详细展开你的模型,让模型的每一块骨骼在hierarchy面板中可见。
  4. 一个接一个地把模型中的骨骼拖拽到脚本中暴漏的对应的变量中.确保每一个骨骼都对应到了正确的变量上。
  5. 当模型中所有的骨骼都放置好了之后,改变暴漏的Player变量的值,这个变量表明该模型是受哪个玩家控制, 0代表第一个玩家,1 代表第二个玩家。
  6. 接下来要设置暴漏的Mask变量的值。设置合适的值,以决定是所有的骨骼都受Kinect控制,还是仅仅一部分骨骼受Kinect控制.如果这些 受Kinect控制的骨骼都不是你想要的,你可以自己写一个控制模型的脚本来代替KinectModelControllerV2。
  7. 当游戏玩家在控制模型时,如果你想要该模型同时播放自带的动画,那么你需要在暴漏的变量中选中animated选项,并设置BlendWeight变量的值,来决定模型受自带模型动画和Kinect驱动动作的最终混合效果。该变量取值范围为0到1之间。
例如示例程序中骨骼和变量的对应关系如下:
Project面板所示的模型的骨骼层次Inspector面板中绑定骨骼

一系列点的控制器:KinectPointController

为了使用该控制器控制一系列的点,请遵从下面几步:

  1. 在场景中创建一个空物体对象,作为你想要控制的一系列点的容器。
  2. 将其他的物体拖拽到刚刚创建的空物体中,并放置到相同的位置。使刚才创建的空物体成为这些物体的父节点。
  3. 拖拽KinectPointController脚本到这些物体的父节点上。
  4. 选中父节点,在Inspector面板中暴漏的变量中,找到Sw变量 (该变量表示Skeleton Wrapper).拖拽当前场景中Kinect_Prefab赋给这个变量。
  5. 拖拽你想利用Kinect控制的每一个物体到Inspector面板对应的合适的变量中。
  6. 设置Player变量(代表玩家)和Mask变量(代表哪些骨骼收到监控).

示例程序中,不同的物体和变量的对应关系如下:

仔细研究包中自带的例子,很容易就明白如何在Unity中结合Kinect开发了。

*********************************************

参考资料:
unity3d Kinect SDK v1.5+Unity3D 体感游戏开发
配置unity3D 3.5 with Kinect SDK1.6
CMU WIKI:Microsoft Kinect – Microsoft SDK