[转载]对.NET系统架构改造的一点经验和教训_IT新闻_博客园

mikel阅读(898)

[转载]对.NET系统架构改造的一点经验和教训_IT新闻_博客园.

  文/范凯

互联网行业,基于 Unix/Linux 的网站系统架构毫无疑问是当今主流的架构解决方案,这不仅仅是因为 Linux 本身足够的开放性,更因为围绕传统 Unix/Linux 社区有大量的成熟开源解决方案,覆盖了网站应用扩展的方方面面。

我记得十几年前第一波互联网浪潮的时代,采用 Windows/.NET 架构的大型网站是非常普及的,而如今采用 .NET 架构的知名网站已经凤毛麟角了。特别是除了微软自身旗下的网站 MSN 和 Hotmail,其他采用 .NET 架构的大型网站很多都面临了架构上的扩展问题,让 .NET 架构的扩展性成了一个比较有争议的问题:

例如国外的 SNS 网站 MySpace 网站面临过很严重的性能扩展问题,国内电商网站京东也不止一次受困于架构扩展带来了性能瓶颈。因此,一些 .NET 架构为主的网站,不得不考虑“去 .NET 化”,抛弃 .NET,全面迁移到以 Java 为主的架构上。

但是大型网站的架构迁移,本身也是伤筋动骨的事情,风险极高,成功案例尚不多见,失败案例俯拾皆是,这是因为:

  1. 架构迁移是在现有业务系统上进行的,并不是从容的开发新产品,有足够的时间测试和完善,相当于给正在高空飞行的客机换引擎,必须一次切换成功,没有 第二次机会,稍有差池,就会机毁人亡。而我们都知道,新开发一个大型系统,没有上线磨合和完善过,无法做到一次 100% 完美。
  2. 架构迁移意味着老的研发团队将被淘汰,而往往老团队体系随着公司壮大已经掌握了足够话语权,新架构的团队在公司又根基未稳,出于维护自身利益的本能,新旧团队之间很容易爆发政治斗争,相互排挤,导致迁移失败。

5173“去 .NET 化”的教训

5173 网站以游戏装备交易起家,曾经在客户端网络游戏发展黄金时期,迎来了业务爆发性的增长期。此时,用 .NET 架构开发的网站已经不堪重负,由于现有的 .NET 研发团队长期无法解决网站的性能问题,5173 决定将网站从 .NET 架构全面迁移到 Java 为主的架构上。为此,5173 花了很大的代价,从淘宝和 SUN 公司挖了很多工程师,组成了一个六七十人的 Java 架构研发部门。

新的 Java 研发部门开发新的 5173 网站,而老的 .NET 研发部门维护现有的 5173 网站,两个部门并行工作,两个版本的网站并行运行,这带来了公司内部激烈的政治斗争,新开发完成的 Java 版本的 5173 网站从未正式上线过,老的 .NET 研发团队在面临严重生存威胁的情况下,努力解决了一些核心的可用性和稳定性问题。同时随着端游黄金时代的结束,网站性能问题也逐渐显得不再重要。

在围绕新版本网站是否要正式替换老版本网站的问题上,各个利益方争执不下,反反复复拉锯战,而空降而来的女 CTO 不属于任何派系,态度模棱两可。最终斗争的结果 .NET 利益方战胜了 Java 利益方,Java 研发部门被清洗。

我的“去 .NET 化”的经验和教训

3 年前,我刚接手 CSDN 的产品和研发团队的时候,CSDN 的核心系统大约2/3 是 .NET 架构,1/3 是 LAMP 架构。研发人员当时也很少:只有 2 个 .NET 程序员,3 个 PHP 程序员,后来还有 1 个我带过来的 Ruby 程序员。当时的计划是:网站整体架构改造的方向是 Linux 架构,逐渐替换掉现有的 .NET 系统。因此不打算继续招聘和补充 .NET 程序员了,现有的 .NET 程序员负责老的核心系统维护工作。

但硕果仅存的 2 个 .NET 程序员在随后不到半年时间都走了:一个 .NET 程序员跟着微软出来的高管去创业,另一个 .NET 程序员跳槽去百度做了前端工程师。这中间的道理也很简单:既然公司要去 .NET 化,那 .NET 工程师就会担心等到将来 .NET 系统都换掉之后,自己在公司还有价值吗,不就彻底边缘化了吗?

当然我在制订架构迁移计划的时候,也考虑到了这一点:我给那个更资深的 .NET 工程师制订的是整个公司总架构师的培养计划,那个精通 JS 的 .NET 工程师制订的是未来前端团队 Leader 的培养计划。不过有不确性的承诺总是不如现实的威胁更迫切,所以我也特别能够理解 .NET 工程师的流失。

这个时候,我陷入了一个两难的处境:

  • 如果未来还是沿着“去 .NET 化”的计划往下走,就算重新招聘和搭建了 .NET 研发团队,由于 .NET 在公司是注定要被替换掉的,那么新的 .NET 团队是不可能稳定的,很可能来一两个月,一看形势不对,立马走人了。而当时 .NET 的核心系统占整个网站的比重很高,且极端复杂,一旦出问题,根本就束手无策,必须要有好手坐镇维护。当时我已经开始 review 核心系统的 .NET 代码,准备亲自上阵了。
  • 如果未来放弃“去 .NET 化”的计划,也许短期可以解决一些头疼的系统维护的问题,但是对整个网站长期的发展会带来很多不利的方面:例如网站的安全性问题,网站的架构扩展问题,网 站服务端软件全面正版化的成本问题等等。如果当时不下定决心去 .NET 化,那么将来再想做这个事情,代价只会越来越高。

当时,我最初的想法是:招聘 2 名水平尚可,关键是没有太大上进心,可以安于现状,踏踏实实工作的 .NET 程序员来维护老的 .NET 核心系统;同时招聘和搭建 Ruby 研发团队,以我过去用 Ruby 开发网站的惊人开发效率,争取时间,逐一重写老的 .NET 核心系统。但是这样做,风险也很大:

  1. 我来 CSDN 的时间不是很长,当时 CSDN 线上产品又多又杂,足有上百个之多,很多系统我都不清楚干什么的;
  2. 公司领导也不太支持我这么快动手,甚至很担心我大刀阔斧的改造网站,会把当时已经很脆弱的网站彻底搞休克;
  3. 我来北京以后,只带过来 1 个 Ruby 程序员,而搭建 Ruby 团队,磨合团队,开发核心系统,都不是一朝一夕的事情,想快也很难快起来;

幸运的是,我招聘的过程当中,面试到了两个相当不错的 .NET 工程师,有很强的上进心,编程功底和悟性都很好。虽然不符合我当时招聘想找安于现状的工程师的标准,但我又不太想错过好的人才,所以我临时改变了自己的想法,将他们招过来,组建了新的 .NET 团队。

为了避免出现之前 .NET 团队流失的问题,给新的 .NET 团队创造在公司发展的机会和空间,我想来想去,决定采取一个折衷的方案:即保留和沿用 .NET 编程语言和框架,但是网站整体架构仍然去 .NET 化,概要说来:

  1. 数据层放弃 SQL Server 数据库和存储过程,全部迁移到 Linux 平台上的 MySQL 数据库上;
  2. 缓存不再依赖 .NET 自身提供的缓存机制,迁移到部署在 Linux 平台上的分布式的 Redis 上;
  3. 服务之间的调用,避免使用 .NET 自身专有协议,改成 Restful 的 HTTP Web API 调用;
  4. 静态资源请求,不再让 IIS 自己处理,分离到 Linux 平台上的 Nginx 去处理;
  5. 需要读取的文件系统,也改成访问 Linux 平台上的分布式文件系统;
  6. 部署 .NET 代码的 Windows 服务器放在 LVS 后面,用 LVS 做负载均衡和故障切换。

简单说来,就是单纯让 .NET 做应用层的编程语言和框架,其他都交给 Linux 平台的开源解决方案。而 .NET 框架单纯做应用层,无论 ASP.NET MVC 的开发效率,还是 .NET CLR 虚拟机的运行效率都非常好,目前我们单台 Windows 服务器上跑几百万的动态请求毫无压力,而且应用层架构是可以横向扩展的:如果请求负载非常高,只需要添加更多 Windows 服务器即可。总之,做到了扬长避短。

此外,我也比较注重不同编程语言研发团队之间的交流,鼓励 .NET 工程师熟悉 Linux 操作系统,培养 .NET 工程师整体架构意识。我们现在的主力 .NET 骨干和我说,感觉来到这里以后技术上最大的提升就是视野一下被打开了。

在后来两年的整个网站改造过程中,也证明了这样的做法是相当成功的:

  1. .NET 团队稳定的延续了下来,而且成为整个研发部门表现一直非常突出的团队;
  2. 整个系统改造的过程非常稳健和平滑,没有碰到过什么风险;
  3. 对网站用户的冲击很小,基本上都是在潜移默化当中,不知不觉的完成了整个网站产品的翻新;
  4. 对公司线上业务也没有造成任何影响,而且随着系统不断改造,对业务的支持越来越好;

当网站架构全面 Linux 化,并且架构解决方案全部统一以后,其实在应用层用什么编程语言来写,就不是一件重要的事情了,我们目前应用层现有产品线,既有 .NET,也有 PHP 和 Ruby 写的,但是架构都是统一的,用什么编程语言,主要取决于研发团队资源的调配情况而定。

总之,以我的经验来说,一个传统上严重依赖微软解决方案架构的网站,如果要进行架构改造,迁移到 Linux 平台,甚至用其他编程语言重写,从来都不是一个单纯的技术问题,它至少涉及如下几个层面的问题:

  1. 如何保障旧系统的研发团队的利益问题,如何做到激励老团队参与架构改造,分享成功。这是最重要的,往往也是架构迁移最容易出现的致命问题,如果架构改造注定要牺牲老团队,完全不考虑和保障他们的利益,我认为一定会产生残酷的政治斗争,最终必然失败;
  2. 现有业务系统如何保持正常运转和平滑过渡的问题,如果架构改造影响到了业务,那一定会夭折;
  3. 如何保证网站用户体验的平滑过渡和改善的问题,如果架构改造影响了用户基本使用体验,那也一定会被叫停;
  4. 领导对架构改造当中出现风险的容忍度问题,以及领导对架构改造周期拉长以后的耐心问题;

一点题外话

我感觉我们互联网行业有一个不太好的现象:有些网站在促销期间瘫痪了,有些网站经常出现访问不稳定的现象,公司老板就喜欢跑到微博上来放狠话, 请下属喝茶,或者急就章的嚷嚷百万年薪招 CTO。这就好比一个人,平常生活习惯差,花天酒地,从不注意养生,结果长年累月下来,终于病倒了。在这个时候狠狠的挥舞支票嚷嚷,哪个名医能给我药到病 除,我给谁百万报酬。

所以,当一个网站出现严重的技术问题,其根源往往都不是单纯的技术问题,也不是单纯换个 CTO 就可以药到病除的,要反思公司企业文化是不是从来没有重视过研发,对技术团队的激励到位了吗?对架构师的意见重视过吗?对未来可能面临的技术门槛是否有过长期的研发投入?

关于这个现象,我记得 Fenng 说过一句很精辟的话:“技术问题,总是在短期被高估,在长期被低估”,我也想补充一句:“技术出现了问题,从来都不单纯是技术导致的问题”。

[转载]手机也能连VPN,再来个远程控制PC这种事你以为我会随便说么! - 牦牛 - 博客园

mikel阅读(1512)

[转载]手机也能连VPN,再来个远程控制PC这种事你以为我会随便说么! – 牦牛 – 博客园.

记得是前几周无意中看到说,在解决一次突发故障时,某公司的技术人员不得不在公车上用手机连上VPN,紧急处理了。。。当时觉得是挺有意思的,今天正好有时间,我们也来玩玩看看。
我的手机型号是iphone4,IOS 5.1.1,未越狱。先打开”通用” -> “网络” -> “VPN” -> “添加VPN配置”,

这里的话,需要根据你的目标VPN的配置来做设置,我们公司在外网PC上连VPN时,是选择的L2TP/IPSEC类型;但是在手机上如果选这2种类型,就会出现这个错误:

没办法,最后我只能再选择中间的”PPTP”试下。我去,没想到选择了这个VPN类型,居然能正常连接了。

恩,为了验证下确实连接上了,我找了个公司内网的站点访问了下,一切正常~~~

小小高兴了一下。不过现在只是能访问内网,手机上啥工具都木有,如果真出了问题不是也得照样干着急?由于是未越狱的系统,我只能寄希望于App Store上了。幸好,App Store上有个好评还不错的免费应用”PocketCloud”,安装后,选择”Remote Desktop”,然后按照平时那样输入远程设备IP、用户名、密码后,就能自动连接上机器了。看,我已经连上了我公司的Windows 2003的PC机。

  点开几个软件看看速度咋样,运行速度还不错,至少操作起来不卡;也能模拟鼠标键盘等,但毕竟触摸的还是不太方便,相信多玩个几次后,应该就能很熟练了。

[转载]wordpress分类方法体系Taxonomy的初步探讨_乌徒帮

mikel阅读(989)

[转载]wordpress分类方法体系Taxonomy的初步探讨_乌徒帮.

在wordpress中,我们很很奇怪的是,它的分类和我们理解中的分类存在一定的偏颇。我们理解中的分类只是简单的对文章内容的分类,而在 wordpress中,这种分类仅仅被定义为栏目、分类目录,而除此之外,wordpress存在一个我们不是很理解的“分类法”的概念,当然,如果简单 的从字面理解很简单,即按照一定的方法进行分类,可是用到程序中应该如何表达呢?Taxonomy便是wordpress的逻辑处理。

如果你进行wordpress插件开发,在调用数据库时,会非常奇特的和怪异的去琢磨wordpress的分类数据表,不熟练的开发者根本摸不清楚wordpress的文章分类如何进行数据管理。那么本文就对wordpress的Taxonomy分类方法进行初步的讲解。

一、wordpress中分类逻辑的探讨

我们在调用一些wordpress的分类函数时,经常会出现一个单词:term。没错,这个单词反映在数据库中会涉及到三张数据 表:wp_terms、wp_term_relationships、wp_term_taxonomy,它们保存了wordpress中所有分类的信 息,包括:文章分类、文章形式分类、标签分类、链接分类、其他分类等等,反映为英文即:category、post_format、post_tag、 link_category、nav_menu等,可在wp_term_taxonomy表的taxonomy字段看到。

wordpress的分类方法Taxonomy:wordpress中将系统中出现的分类进行归类,某一分类属于什么类型的分类,即Taxonomy。例如,标签“乌徒帮”下有很多文章,属于一个分类,它是按照“post_tag”的分类法进行分类的。

wordpress的分类体系:wordpress中的分类按照category、post_format、post_tag、link_category、nav_menu等方法进行分类。

wordpress分类体系在数据库中:wp_terms保存分类的名称、别名(包括分类法,如post-fomat- aside);wp_term_taxonomy保存的是分类法下分类的关系,包括分类关系ID,分类的ID,分类法taxonomy,和分类的 parent分类ID值,分类关系IDterm_taxonomy_id将在wp_term_relationships中被引用,此外,该表还保存了该 分类的描述description和分类下的文章数目count;wp_term_relationships表保存着文章和分类关系 term_taxonomy之间的关系,包括文章ID,term_taxonomy_id。

二、wordpress中分类法体系在数据库中是如何实现的

我们通过一个实例来看看数据表中这些数据内容之间的关系。

在wordpress中,如果你需要获取一个具体的分类,例如获取栏目“乌徒帮闲语”,首先根据文章ID在 wp_term_relationships表中获取term_taxonomy_id值,再在wp_term_taxonomy表中获取分类的 term_id和parent值等信息,最后再根据term_id在wp_terms获取分类的名称和别名,根据parent值在wp_terms中获取 父分类的名称和别名。这三个表的关系大抵如此,你也可以倒过来,根据分类的别名从wp_terms中获取term_id,再到 wp_term_taxonomy中获取它的描述和父分类ID,再返回wp_terms中获取父分类的别名和名称。

通过这样的举例,你就可以了解wordpress中分类方法体系的大致逻辑了。

我们再通过一个实例来看一下分类方法体系:

我们在写一篇文章时,插入一个新的标签或分类,以标签为例,首先,在表wp_terms中,创建一条记录,记录下标签的名称,返回term_id 值;然后在wp_term_taxonomy中创建一条记录,用到了该term_id,taxonomy字段的值为post_tag,由于是标签,其他字 段暂时无法补充,返回term_taxonomy_id;最后利用该term_taxonomy_id值,在wp_term_relationships 表中创建一条新的记录,把文章的ID值和该term_taxonomy_id值作为两个字段内容。

三、wordpress中有关分类法体系的函数有哪些

否子戈无法完全列出这些函数,如果你是开发者,可以直接看源码:/wp-includes/taxonomy.php、/wp-admin /includes/taxonomy.php中的函数,前者在wordpress主题中使用,后者在后台设计中使用,如果要在主题中使用需要 include_once进来。通过它们,你可以实现很多插入、删除、修改某一分类的功能。

wordpress中的category函数,你可以看源码/wp-includes/category.php、/wp-includes /category-template.php等文件中的函数,或参考wordpress开发文档。另外,在post.php等文件中也有几个相关的函 数,如wp_get_object_terms、wp_set_object_terms()wp_set_post_terms()等函数将会在插件中用到。

否子戈只是初步的对wordpress分类方法体系进行了探讨,其中更多的wordpress核心逻辑还需要开发者深入研究。

[转载]C# Winform简单模拟水果店结账系统 - SilverSky(SoFlash) - 博客园

mikel阅读(1076)

[转载]C# Winform简单模拟水果店结账系统 – SilverSky(SoFlash) – 博客园.

简介

现实生活中,结账系统无处不在,屡见不鲜,各种结账系统 例如:订餐结账系统,超市购物结账系统,酒店客房结账系统等等.

其实结账原理都大同小异,即将所有购买项的价格都相加,当然,那些复杂的系统所考虑的方面和功能就很多了,我们这里不考虑.

那么,今天我们这里简单的写个基于 C# winform 平台的水果店结账系统.

 

原理

这里用到了一个主窗体类和一个构造函数类(用于存取物品的属性) 以及一个写有物品属性的文本文件(目的是方便后期更改物品的价格,折扣等属性).

首先,通过FileStream的OpenRead方法来打开并读取文本文件, 然后调用StreamReader类来读取FileStream的字符并将里面的字节转换成字符串.

具体FileStream和StreamReader的用法,可以参考这里. 从文本里获取到物品的属性之后,将其存到一个具有物品属性的数据结构的泛型list里.

然后在各个物品按钮下调用不同物品,每个物品在泛型list里的索引也是不一样的.之后再用一个整型数组来存储每个物品按钮的点击次数,即为每个物品的购买次数.

还用到一个泛型list来装载每个物品的价格,在计算总价的时候遍历并相加, 得到一个总价.

这里还用到了一个优惠价,即每个物品都有不同的打折方式(代码里有详细注释), 用到了一些简单的算法,并且在结账的时候自动计算每项物品最终优惠了多少.

然后用 总价-优惠价=最终我们应该支付的价格,当然这里也实现了物品清空的功能.

 

界面设计

 

fruits.txt文本内容

 

代码如下

ProductAttribute.cs 类里的代码

class ProductAttribute
{
//初始化各值
string name = "";
float price = 0;
int code1 = 0;
int code2 = 0;
//构造方法
public ProductAttribute(string n, float p, int c1, int c2)
{
//实现ProductAttribute的数据结构
//里面可以传入如下参数
name = n;
price = p;
code1 = c1;
code2 = c2;
}
//可以读取ProductAttribute里的Name 属性
public string Name
{
get { return name; }
}
//可以读取ProductAttribute里的Price 属性
public float Price
{
get { return price; }
}
// 一个save方法 用于判断 不同的折扣方式 并返回一个减去的钱数
public float save(int n)
{
float m = 0; //m用于获取 打折后的 价格 符合的物品数量
switch (code1)
{
case 0: //code1为0 的时候
m = n; //买多少斤 最终价格还是 按多少斤算
break;
case 1: //code1为1的时候 有2种情况
if (code2 == 0) //code2为0 买1斤 第2斤免费 即买一送一
m = (float)((n / 2) + (n % 2)); //买1斤 第2斤免费 但买3斤 还是1斤免费
else if (code2 == 1) //code2为1 这里是买1斤 第2斤半价 即买2斤 1斤半的钱 买4斤 3斤的钱
m = (float)(1.5 * (n / 2) + (n % 2));
break;
case 2: //code1 为2的时候 也有2种方法
if (code2 == 0) //code1为2 code2 为0 买2斤 第3斤免费
m = (float)(n - (n / 3));
else if (code2 == 1) //code1为2 code2为1 即买2斤 第3斤半价
m = (float)(n - 0.5 * (n / 3));
break;
}

float save = (n - m) * price; //计算出一个最终的价格
return save; //返回省去的钱数
}
}

主窗体类代码 frmFruitShopping.cs

public partial class frmFruitShopping : Form
{
public frmFruitShopping()
{
InitializeComponent();

this.Load += (s, e) => { ReadFruitsData(); FruitClick(); };
btnTotal.Click += (s, e) => { GetTotalCost(); };
btnEmpty.Click += (s, e) => { EmptyShoppingBox(); };
btnDeleteOne.Click += (s, e) => { DeleteOneShoppingItem(); };
}

ListproductList;

///

<summary> /// 读取txt文本数据
/// </summary>

&nbsp;

private void ReadFruitsData()
{
//创建一个水果属性的集合
productList = new List();
string strRead; //创建一个字符串 用来接受读取的数据
// 创建FileStream文件流 可以用来读取任何文件 其内部读取格式是 最原始的字节
FileStream fs = File.OpenRead("fruits.txt");
//通过流读取来读取fs文件里的东西,并将 文字格式设置为默认的ANSI编码
using (StreamReader sr = new StreamReader(fs, Encoding.Default))
{
//用while循环来 一行行读取 如果读到的内容不为空 则进入循环 否则不读
while ((strRead = sr.ReadLine()) != null)
{
//这里用到了个字符串分割 每行都有几个参数 通过'-' 隔开 分割后存到一个数组里
string[] part = strRead.Split('-');
//new一个 构造方法来装载 这些参数
ProductAttribute pa = new ProductAttribute(part[0], (float)Convert.ToDouble(part[1]), Convert.ToInt32(part[2]), Convert.ToInt32(part[3]));
//将 装载的参数 存到水果属性的list集合中
productList.Add(pa);
}
}
}

//创建一个TotalCost集合 来装载 所有的开销
List TotalCost = new List();
//定义一个 可以容纳12个整数的整型数组 用于装载 每样产品的点击数
int[] intQuantity = new int[12];

///

<summary> /// 记录并打印每项水果的花费
/// </summary>

&nbsp;

///物品的索引 public void printCost(int i)
{
intQuantity[i]++;
lstMenu.Items.Add("您购买了\t" + productList[i].Name + "\t " + productList[i].Price + "元/斤");
TotalCost.Add(productList[i].Price);
}

///

<summary> /// 实现所有水果按钮单击事件下触发的打印花费的方法
/// </summary>

&nbsp;

private void FruitClick()
{
//每个按钮下 都调用了一个 printCost();方法
//传入的整型参数 是购物品在TotalCost集合里的位置 同时也是intQuantity的索引位置
btnApple.Click += (s, e) =&gt; { printCost(0); };
btnOrange.Click += (s, e) =&gt; { printCost(1); };
btnPear.Click += (s, e) =&gt; { printCost(2); };
btnbanana.Click += (s, e) =&gt; { printCost(3); };
btnHawthorn.Click += (s, e) =&gt; { printCost(4); };
btnHoneyPeach.Click += (s, e) =&gt; { printCost(5); };
btnCoconut.Click += (s, e) =&gt; { printCost(6); };
btnFig.Click += (s, e) =&gt; { printCost(7); };
btnGrape.Click += (s, e) =&gt; { printCost(8); };
btnLitchi.Click += (s, e) =&gt; { printCost(9); };
btnPlum.Click += (s, e) =&gt; { printCost(10); };
btnPomelo.Click += (s, e) =&gt; { printCost(11); };
}

///

<summary> /// 计算总共花费
/// </summary>

&nbsp;

private void GetTotalCost()
{
//遍历 TotalCost里所有的值 全部加起来
float tCost = 0;
foreach (float cost in TotalCost)
{
tCost += cost;
}
lstMenu.Items.Add("------------------------");
lstMenu.Items.Add("优惠前总价为:\t\t " + Math.Round(tCost, 1) + "元"); //打印出总价(打折前的价格)
//调用Saving()方法 计算 总共节省的钱数
float totalSaving = Saving();
lstMenu.Items.Add("打折后最终您需要付款为\t " + Math.Round((tCost - totalSaving), 1) + "元");
}

float tSaving = 0;
///

<summary> /// 计算优惠额
/// </summary>

&nbsp;

/// 优惠额
public float Saving()
{
for (int i = 0; i = 2的时候 才有优惠
if (intQuantity[i] &gt;= 2)
{ //这里 苹果和无花果 是不存在优惠的
if (productList[i].Name == "苹果" || productList[i].Name == "无花果")
lstMenu.Items.Add("您购买了" + productList[i].Name + " 一共" + intQuantity[i] + "斤" + " 最近" + productList[i].Name + "无优惠");
else //否则其他水果 都有优惠
{
lstMenu.Items.Add("您购买了" + productList[i].Name + " 一共" + intQuantity[i] + "斤" + " 优惠" + Math.Round(productList[i].save(intQuantity[i]), 1) + "元");
tSaving += productList[i].save(intQuantity[i]);
}
}
//当购买单项 水果 小于2斤的时候 是没有优惠享受的
else if (1 {
lstMenu.Items.Add("您购买了" + productList[i].Name + " 只有" + intQuantity[i] + "斤" + " 没有优惠!");
}
}
//最终 打印出 所有物品节约的 总价
lstMenu.Items.Add("--------------------------------------");
lstMenu.Items.Add("所有水果一共优惠了\t " + tSaving + "元");

return tSaving;//返回优惠额
}

///

<summary> /// 清空购物车
/// </summary>

&nbsp;

private void EmptyShoppingBox()
{
lstMenu.Items.Clear(); //清空listMenu控件里面的所有项(表面清除)
TotalCost.Clear(); //清空TotalCost里的所有项(从系统中移除)
tSaving = 0; //把之前节省的钱 全部清空
//清空的时候 要将打折省去的钱数 也清掉
//这里用的是 清空所有购买水果的数量 当水果数为0的时候 就没有打折省去的钱数了
for (int i = 0; i &lt; intQuantity.Length; i++)
intQuantity[i] = 0;
}

///

<summary> /// 删除已购买的单项物品
/// </summary>

&nbsp;

private void DeleteOneShoppingItem()
{
//从listbox 里移除 购买项 (表面移除, 实际值需要从装载值的TotalCost集合里移除)
lstMenu.Items.Remove(lstMenu.SelectedItem);
//SelectedIndex 是从0开始的索引 但实际你删除项的索引是大于0的整数
//从TotalCost里移除购买项
TotalCost.RemoveAt(lstMenu.SelectedIndex + 1);
}
}

 

运行效果 如下

1.结账

 

2.清空购物车

 

3.再点结账 看下  确实所有值 都被清空了 (清空购物车功能 没问题)

 

4. 但是删除单项 这里出问题了 请看下面

 

5 删除2项 山楂后 总价虽然正常减少了 而且原先买了3斤山楂 现在删除了2斤山楂 剩1斤

应该是没有优惠了 但这里还是保持原先 买3斤时候的优惠

而且总优惠价格 也是不但没减少  还把之前的再次相加了一次 导致最终价格不正常 (可能还有一些问题没有发现)

这里思路是有 就是 根据删除项在listbox里的索引 相对应的也是在用来装载所有购买物品价格的泛型list(TotalCost)的索引

但是 怎样判断这个索引是哪个物品呢 这里是关键.

由于这里涉及到的判断比较多  个人能力有限  还请 对该程序感兴趣的大牛们 帮忙解决下

谢了!  🙂

这里附上源代码 水果店结账系统 点击下载

[转载]谈谈防止重复点击提交 - 金广国 - 博客园

mikel阅读(1217)

[转载]谈谈防止重复点击提交 – 金广国 – 博客园.

首先说说防止重复点击提交是什么意思。

  我们在访问有的网站,输入表单完成以后,单击提交按钮进行提交以后,提交按钮就会变为灰色,用户不能再单击第二次,直到重新加载页面或者跳转。这样,可以一定程度上防止用户重复提交导致应用程序上逻辑错误。

  不妨引深来看,它不一定发生在表单的提交事件上,同样可以发生在ajax的异步请求上。有效地在web客户端采用一定机制去防止重复点击提交,将大大减轻服务器端压力。

  那么,我们就不妨从表单提交及ajax的两种不同请求的处理过程中,来试试如何防止重复点击提交。

  一、表单提交

  就以登录表单为例,代码如下:

<form action="login.do" method="post">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="submit" onclick="this.disabled=true; this.value='登录中...'; this.form.submit();" value="登录" />
</form>

  单击登录按钮进行提交以后,提交按钮就会变为灰色,并且给用户一个友好提示(登录中…),用户不能再单击第二次,直到重新加载页面或者跳转。

  可以发现,我们不需要给这个按钮恢复到可以再次登录的状态,仅仅源于页面重新进行了加载或者跳转。

  但是,针对ajax的请求上,我们又该如何处理呢?

  二、ajax请求

(function ($) {

$('.J-login').click(function () {

var loginBtn = this;

//1.先进行表单验证
//......

//2.让提交按钮失效,以实现防止按钮重复点击
$(loginBtn).attr('disabled', 'disabled');

//3.给用户提供友好状态提示
$(loginBtn).text('登录中...');

//4.异步提交
$.ajax({
url: 'login.do',
data: $(this).closest('form[name="loginForm"]').serialize(),
type: 'post',
success: function(msg){

if (msg === 'ok') {
alert('登录成功!');

//TODO 其他操作...
} else {
alert('登录失败,请重新登录!');

//5.让登陆按钮重新有效
$(loginBtn).removeAttr('disabled');
}

}
});

});

})(jQuery);

  可以发现,当登录失败后,需要重新让登录按钮具有登录事件。

当然,我们可以用一个更加优雅的方式来代替之。

(function ($) {

$('.J-login').click(function () {

var loginBtn = this;

//1.先进行表单验证
//......

//2.异步提交
$.ajax({
url: 'login.do',
data: $(this).closest('form[name="loginForm"]').serialize(),
type: 'post',
beforeSend: function () {
//3.让提交按钮失效,以实现防止按钮重复点击
$(loginBtn).attr('disabled', 'disabled');

//4.给用户提供友好状态提示
$(loginBtn).text('登录中...');
},
complete: function () {
//5.让登陆按钮重新有效
$(loginBtn).removeAttr('disabled');
},
success: function(msg){

if (msg === 'ok') {
alert('登录成功!');

//TODO 其他操作...
} else {
alert('登录失败,请重新登录!');
}

}
});

});

})(jQuery);

在这里,我仅仅举了一个最为简单的例子,还有很多其他的方式进行防止重复点击提交,如

1> 定义标志位:

点击触发请求后,标志位为false量;请求(或者包括请求后具体的业务流程处理)后,标志位为true量。通过标志位来判断用户点击是否具备应有的响应。

2> 卸载及重载绑定事件:

点击触发请求后,卸载点击事件;请求(或者包括请求后具体的业务流程处理)后,重新载入绑定事件。

3> 替换按钮DOM

点击触发请求后,将按钮DOM对象替换掉,自然而然此时不在具备点击事件;请求(或者包括请求后具体的业务流程处理)后,给新的按钮DOM定义点击事件。

当然,还有其他的方式进行实现,欢迎各位博友补充。

三、请求频度

相信大家碰到过这样的业务,我们允许它重复点击(或者其他用户事件),但是不允许在一定的时间内超过次数XX次。这从用户友好体验及服务器承受压力选取了一个折中方案。

最合适不过的例子,莫过于关键字搜索匹配了。

相信大家定然首先想到节流函数了。

var timer = null;

$(input).keyup(function(){

var value = $(this).val();

clearTimeout(time);

//如果键盘敲击速度太快,小于100毫秒的话就不会向后台发请求,但是最后总会进行一次请求的。
timer = setTimeout(function() {
//触发请求
$.ajax({
url: 'typeahead.do',
type: 'get',
data: value,
success: function () {
//显示匹配结果
//......
}
});
},100);

});

 四、总结

从宏观意义上来讲,我们需要对每一个按钮去做”防止重复点击提交“,面对这种情况,我们便可以采用一定策略来对其进行封装实现(如定义通用按钮类绑定事件)。

从具体情况上来讲,我们并不需要对每一个按钮都去做”防止重复点击提交“,仅仅需要对某些可能具有复杂后台业务逻辑、或者文件上传、或者调用其 他非本工程接口导致网络延迟等等情况需要去做”防止重复点击提交“。与此同时,我们必须要给予用户友好提示(如文本提示、渲染loading条、显示文件 上传进度条等等)。两者需要一起来看、一起来做。当然,我们可以单独提取状态显示这个实现逻辑。代码如下——

//全站ajax加载提示
(function ($) {

var str = '
<div class="ajax-status" style="display: none;">'
+ '
<div class="ajax"><img src="' + publicPath + 'img/loading.gif" alt="" width="20" height="20" />数据加载中...</div>
'
+'

</div>
';

var dom = $(str).prependTo('body');

$(document).ajaxStart(function(){
dom.stop(true,false).queue(function(){
$(this).show().dequeue();
});
});

$(document).ajaxStop(function(){
dom.queue(function(){
$(this).hide().dequeue();
});
});

})(jQuery);

总之,”防止重复点击提交“的应用场景及实现方式有很多,需要根据具体项目情况具体来定。

[转载]响应式商品展示列表的实现 | TYStudio-专注WEB前端开发

mikel阅读(984)

转载响应式商品展示列表的实现 | TYStudio-专注WEB前端开发.

介绍一个最新的响应式商品展示列表,同时带有一些社会化和商品细节的显示。当点击旋转按钮的时候会显示商品的背面,使用Media queries实现响应式显示商品。当浏览器支持Flexbox的时候使用Flexbox。

响应式商品展示列表

查看演示  源码下载

很遗憾目前的版本还不支持IE系列浏览器。
HTML标签结构

<div id="cbp-pgcontainer" class="cbp-pgcontainer">
<ul class="cbp-pggrid">
	<li>
<div class="cbp-pgcontent"><span class="cbp-pgrotate" title="旋转">旋转</span>
<div class="cbp-pgitem">
<div class="cbp-pgitem-flip"><img src="images/1_front.png" alt="" />
<img src="images/1_back.png" alt="" /></div>
</div>
<!-- /cbp-pgitem -->
<ul class="cbp-pgoptions">
	<li class="cbp-pgoptcompare" title="比较">比较</li>
	<li class="cbp-pgoptfav" title="喜欢">喜欢</li>
	<li class="cbp-pgoptsize"><span title="XL号" data-size="XL">XL</span>
<div class="cbp-pgopttooltip"><span title="XL号" data-size="XL">XL</span>
<span title="L号" data-size="L">L</span>
<span title="M号" data-size="M">M</span>
<span title="S号" data-size="S">S</span></div></li>
	<li class="cbp-pgoptcolor"><span title="蓝色" data-color="c1">蓝色</span>
<div class="cbp-pgopttooltip"><span title="蓝色" data-color="c1">蓝色</span>
<span title="粉色" data-color="c2">粉色</span>
<span title="桔色" data-color="c3">桔色</span>
<span title="绿色" data-color="c4">绿色</span></div></li>
	<li class="cbp-pgoptcart" title="加入购物车"></li>
</ul>
<!-- cbp-pgoptions -->

</div>
<!-- cbp-pgcontent -->
<div class="cbp-pginfo">
<h3>T恤一</h3>
<span class="cbp-pgprice">¥29</span>

</div></li>
	<li><!-- ... --></li>
	<li><!-- ... --></li>
</ul>
</div>

CSS样式

@font-face {
font-family: 'pgicons';
src:url('../fonts/pgicons/pgicons.eot');
src:url('../fonts/pgicons/pgicons.eot?#iefix') format('embedded-opentype'),
url('../fonts/pgicons/pgicons.woff') format('woff'),
url('../fonts/pgicons/pgicons.ttf') format('truetype'),
url('../fonts/pgicons/pgicons.svg#pgicons') format('svg');
font-weight: normal;
font-style: normal;
}

.cbp-pgcontainer {
position: relative;
width: 100%;
padding: 0 30px 100px 30px;
}

.cbp-pgcontainer ul,
.cbp-pgcontainer li {
padding: 0;
margin: 0;
list-style-type: none;
}

.cbp-pggrid {
position: relative;
text-align: center;
}
/* 如果支持flexbox,将使用flexbox来布局列表 */
.flexbox .cbp-pggrid {
display: -webkit-flex;
display: -moz-flex;
display: -ms-flex;
display: flex;
-webkit-flex-flow: row wrap;
-moz-flex-flow: row wrap;
-ms-flex-flow: row wrap;
flex-flow: row wrap;
-webkit-justify-content: center;
-moz-justify-content: center;
-ms-justify-content: center;
}

.cbp-pggrid &gt; li {
display: inline-block;
vertical-align: top;
position: relative;
width: 33%;
min-width: 340px;
max-width: 555px;
padding: 20px 2% 50px 2%;
text-align: left;
float:left;
}

.flexbox .cbp-pggrid &gt; li {
display: block;
}

.cbp-pgcontent {
border: 3px solid #47a3da;
position: relative;
}

.cbp-pgrotate {
width: 36px;
height: 36px;
position: absolute;
display: block;
color: transparent;
font-size: 0;
z-index: 100;
border-bottom: 3px solid #47a3da;
border-left: 3px solid #47a3da;
right: 0px;
top: 0px;
cursor: pointer;
text-align: center;
}

.cbp-pgrotate:before {
font-size: 18px;
line-height: 32px;
color: #47a3da;
}

.no-touch .cbp-pgrotate:hover,
.cbp-pgrotate.cbp-pgrotate-active {
background: #47a3da;
}

.no-touch .cbp-pgrotate:hover:before,
.cbp-pgrotate.cbp-pgrotate-active:before {
color: #fff;
}

/* The item with the images will have perspective */
.cbp-pgitem {
width: 100%;
position: relative;
padding: 2em;
-webkit-perspective: 1400px;
-moz-perspective: 1000px;
perspective: 1000px;
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
backface-visibility: hidden;
}

/* The flip container */
.cbp-pgitem-flip {
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-transition: -webkit-transform .4s ease-out;
-moz-transition: -moz-transform .4s ease-out;
transition: transform .4s ease-out;
}

/* 如果你想要实现当鼠标hover到图片的时候自动翻转的效果,你可以使用下面的代码:
.cbp-pgrotate:hover + .cbp-pgitem .cbp-pgitem-flip
*/
.cbp-pgitem.cbp-pgitem-showback .cbp-pgitem-flip {
-webkit-transform: rotateY(180deg);
-moz-transform: rotateY(180deg);
-ms-transform:rotate(180deg);
transform: rotateY(180deg);
}

.cbp-pgitem-flip img {
display: block;
margin: 0 auto;
max-width: 100%;
max-height: 100%;
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
backface-visibility: hidden;
}

.cbp-pgitem img:first-child {
position: relative;
}

/* 为了显示背面,第二个图片将被旋转 */
.cbp-pgitem img:nth-child(2) {
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translateX(-50%) translateY(-50%) rotateY(-180deg);
-moz-transform: translateX(-50%) translateY(-50%) rotateY(-180deg);
-ms-transform: translateX(-50%) translateY(-50%) rotateY(-180deg);
transform: translateX(-50%) translateY(-50%) rotateY(-180deg);
}

/* 浏览器不支持3dtransform的时候使用 */
.no-csstransforms3d .cbp-pgitem img:nth-child(2) {
position: relative;
top: 0;
left: 0;
display: none;
}

.no-csstransforms3d .cbp-pgitem.cbp-pgitem-showback img:first-child {
display: none;
}

.no-csstransforms3d .cbp-pgitem.cbp-pgitem-showback img:nth-child(2) {
display: block;
}

/* 商品详细信息样式 */
.cbp-pgoptions {
height: 60px;
width: 100%;
border-top: 3px solid #47a3da;
}

.cbp-pgoptions &gt; li {
width: 20%;
height: 100%;
float: left;
position: relative;
display: block;
cursor: pointer;
color: transparent;
font-size: 0;
border-left: 3px solid #47a3da;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

.cbp-pgoptions &gt; li:first-child {
border-left: none;
}

.no-touch .cbp-pgoptions li {
color: #47a3da;
}

.no-touch .cbp-pgoptions li:hover,
.cbp-pgoptions li.cbp-pgoption-active {
background: #47a3da;
}

.cbp-pgoptions li:before,
.cbp-pgoptions li &gt; span {
font-size: 22px;
line-height: 60px;
text-indent: 0;
text-align: center;
color: #47a3da;
}

.no-touch .cbp-pgoptions li:hover:before,
.no-touch .cbp-pgoptions li:hover &gt; span,
.cbp-pgoptions li.cbp-pgoption-active &gt; span {
color: #fff;
}

.cbp-pgoptions li.cbp-pgoptsize &gt; span {
font-size: 22px;
}

.cbp-pgoptions li &gt; span {
display: block;
}

.cbp-pgoptions li:before {
position: absolute;
width: 100%;
height: 100%;
}

/* 图标 */
.cbp-pgoptcompare,
.cbp-pgoptcart,
.cbp-pgoptfav,
.cbp-pgrotate {
font-family: 'pgicons';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
}

.cbp-pgoptcompare:before {
content: "\e001";
}

.cbp-pgoptfav:before {
content: "\e003";
}

.cbp-pgoptfav.cbp-pgoptfav-selected:before {
content: "\e002";
color: #ee73b8;
}

.cbp-pgoptfav.cbp-pgoptfav-selected:hover:before {
color: #f9c0e0;
}

.cbp-pgoptcart:before {
content: "\e000";
}

.cbp-pgrotate:before {
content: "\e004";
}

/* 提示信息 */
.cbp-pgopttooltip {
position: absolute;
bottom: 180%;
margin-bottom: 0px;
background: #fff;
padding: 25px;
width: 100px;
left: 50%;
margin-left: -50px;
border: 3px solid #47a3da;
opacity: 0;
z-index: 1000;
visibility: hidden;
pointer-events: none;
-webkit-transition: visibility 0s 0.3s, opacity 0.3s, bottom 0.3s;
-moz-transition: visibility 0s 0.3s, opacity 0.3s, bottom 0.3s;
transition: visibility 0s 0.3s, opacity 0.3s, bottom 0.3s;
}

.cbp-pgoptions li:hover .cbp-pgopttooltip,
.cbp-pgoptions li.cbp-pgoption-active .cbp-pgopttooltip {
visibility: visible;
opacity: 1;
-webkit-transition-delay: 0s;
-moz-transition-delay: 0s;
transition-delay: 0s;
bottom: 100%;
pointer-events: auto;
}

.cbp-pgopttooltip:after,
.cbp-pgopttooltip:before {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}

.cbp-pgopttooltip:after {
border-color: transparent;
border-top-color: #fff;
border-width: 10px;
left: 50%;
margin-left: -10px;
}

.cbp-pgopttooltip:before {
border-color: transparent;
border-top-color: #47a3da;
border-width: 14px;
left: 50%;
margin-left: -14px;
}

/* 尺寸提示 */
.cbp-pgoptsize .cbp-pgopttooltip {
margin-left: -50px;
}

.cbp-pgoptsize .cbp-pgopttooltip span {
display: block;
text-indent: 0;
background: url(../images/tshirt.svg) no-repeat center center;
background-size: 100%;
margin: 0 auto 4px;
text-align: center;
font-size: 12px;
font-weight: 700;
color: #65b3e2;
}

.cbp-pgoptsize .cbp-pgopttooltip span:hover {
color: #0968a1;
transform: scale(1.1);
-webkit-transform: scale(1.1);
-o-transform:scale(1.1);
-moz-transform: scale(1.1);
-ms-transform:scale(1.1);
}

.cbp-pgoptsize .cbp-pgopttooltip span[data-size="XL"] {
width: 44px;
height: 44px;
line-height: 44px;
}

.cbp-pgoptsize .cbp-pgopttooltip span[data-size="L"] {
width: 40px;
height: 40px;
line-height: 40px;
}

.cbp-pgoptsize .cbp-pgopttooltip span[data-size="M"] {
width: 34px;
height: 34px;
line-height: 34px;
}

.cbp-pgoptsize .cbp-pgopttooltip span[data-size="S"] {
width: 30px;
height: 30px;
line-height: 30px;
}

/* 颜色提示 */
.cbp-pgoptcolor .cbp-pgopttooltip {
padding: 5px;
}

.cbp-pgoptions li.cbp-pgoptcolor &gt; span,
.cbp-pgoptcolor .cbp-pgopttooltip span {
display: block;
margin: 12px auto 0;
text-indent: -900em;
}

.cbp-pgoptions li.cbp-pgoptcolor &gt; span {
width: 36px;
height: 36px;
border: 3px solid #fff;
}

.cbp-pgoptcolor .cbp-pgopttooltip span {
float: left;
margin: 4px;
width: 34px;
height: 34px;
}

.no-touch .cbp-pgoptcolor .cbp-pgopttooltip span:hover {
transform: scale(1.1);
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-ms-transform:scale(1.1);
}

.cbp-pgoptcolor span[data-color="c1"] {
background: #72bbe9;
}

.cbp-pgoptcolor span[data-color="c2"] {
background: #e577aa;
}

.cbp-pgoptcolor span[data-color="c3"] {
background: #e5b178;
}

.cbp-pgoptcolor span[data-color="c4"] {
background: #7abe93;
}

.cbp-pginfo {
padding-top: 10px;
}

.cbp-pginfo:before,
.cbp-pginfo:after {
content: " ";
display: table;
}

.cbp-pginfo:after {
clear: both;
}

.cbp-pginfo h3,
.cbp-pginfo span {
float: left;
width: 50%;
font-size: 1.8em;
padding: 10px 5px;
margin: 0;
}

.cbp-pginfo h3 {
font-weight: 300;
}

.cbp-pginfo span {
font-weight: 700;
text-align: right;
}

/* Media Queries */

@media screen and (max-width: 68.125em) {
.cbp-pggrid &gt; li {
width: 48%;
}
}

@media screen and (max-width: 46.125em) {
.cbp-pggrid &gt; li {
width: 100%;
}
}

JavaScript

/**
* cbpShop.js v1.0.0
* http://www.codrops.com
*
* Licensed under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright 2013, Codrops
* http://www.codrops.com
*/
;( function( window ) {

'use strict';

function cbpShop( el ) {
this.el = el;
this._init();
}

cbpShop.prototype = {
_init : function() {
var self = this;

this.touch = Modernizr.touch;

this.products = this.el.querySelectorAll( 'ul.cbp-pggrid &gt; li' );
Array.prototype.slice.call( this.products ).forEach( function( el, i ) {
var content = el.querySelector( 'div.cbp-pgcontent' ),
item = content.querySelector( 'div.cbp-pgitem' ),
rotate = content.querySelector( 'span.cbp-pgrotate' );

if( self.touch ) {

rotate.addEventListener( 'touchstart', function() { self._rotateItem( this, item ); } );

var options = content.querySelector( 'ul.cbp-pgoptions' ),
size = options.querySelector( 'li.cbp-pgoptsize &gt; span' ),
color = options.querySelector( 'li.cbp-pgoptcolor &gt; span' );

size.addEventListener( 'touchstart', function() { self._showItemOptions( this ); } );
color.addEventListener( 'touchstart', function() { self._showItemOptions( this ); } );
}
else {
rotate.addEventListener( 'click', function() { self._rotateItem( this, item ); } );
}
} );
},
_rotateItem : function( trigger, item ) {
if( item.getAttribute( 'data-open' ) === 'open' ) {
item.setAttribute( 'data-open', '' );
trigger.className = trigger.className.replace(/\b cbp-pgrotate-active\b/,'');
item.className = item.className.replace(/\b cbp-pgitem-showback\b/,'');
}
else {
item.setAttribute( 'data-open', 'open' );
trigger.className += ' cbp-pgrotate-active';
item.className += ' cbp-pgitem-showback';
}
},
_showItemOptions : function( trigger ) {
if( trigger.getAttribute( 'data-open' ) === 'open' ) {
trigger.setAttribute( 'data-open', '' );
trigger.parentNode.className = trigger.parentNode.className.replace(/\b cbp-pgoption-active\b/,'');
}
else {
trigger.setAttribute( 'data-open', 'open' );
trigger.parentNode.className += ' cbp-pgoption-active';
}
},
/*
other functions..
*/
}

window.cbpShop = cbpShop;

} )( window );

英文原文链接:PRODUCT GRID LAYOUT

[转载]在IE使用javascript 数组的indexOf - x123jing的专栏 - 博客频道 - CSDN.NET

mikel阅读(1028)

[转载]在IE使用javascript 数组的indexOf – x123jing的专栏 – 博客频道 – CSDN.NET.

最近在使用JavaScript数组时,为了获取数组中元素的索引,使用了indexOf.在firefox中调试ok,但是放到ie中,总是出错,无法执行。查查资料,发现原来在ie中不支持indexOf。如果要使用,需要插入下面的一段代码:

if(!Array.indexOf)
{
Array.prototype.indexOf = function(obj)
{
for(var i=0; i {
if(this[i]==obj)
{
return i;
}
}
return -1;
}
}

例如:
var a=new Array( 'morning', 'lunch', 'afternoon', 'night');
var flag_inout = 'lunch'
//for ie can't support indexof
if(!Array.indexOf){
Array.prototype.indexOf = function(obj){
for(var i=0; i if(this[i]==obj){
return i;
}
}
return -1;
}
}

var index_id = a.indexOf(flag_inout);

 

备注:其实ie支持字符串的indexOf,可以将数组转换为字符串在用indexOf来判断就可以了

如下

var flag_inout=flag_inout.join(“,”);

 

[转载]Java构造和解析Json数据的两种方法详解 - Alexia(minmin) - 博客园

mikel阅读(1053)

[转载]Java构造和解析Json数据的两种方法详解 – Alexia(minmin) – 博客园.

www.json.org上公布了很多JAVA下的json构造和解析工具,其中org.json和json-lib比较简单,两者使用上差不多但还是有些区别。下面首先介绍用json-lib构造和解析Json数据的方法示例。

一、介绍

JSON-lib包是一个beans,collections,maps,java arrays 和XML和JSON互相转换的包

二、下载jar依赖包:可以去这里下载

三、演示示例

package com.json;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

/**
* 使用json-lib构造和解析Json数据
*
* @author Alexia
* @date 2013/5/23
*
*/
public class JsonTest {

/**
* 构造Json数据
*
* @return
*/
public static String BuildJson() {

// JSON格式数据解析对象
JSONObject jo = new JSONObject();

// 下面构造两个map、一个list和一个Employee对象
Map map1 = new HashMap();
map1.put("name", "Alexia");
map1.put("sex", "female");
map1.put("age", "23");

Mapmap2 = new HashMap();
map2.put("name", "Edward");
map2.put("sex", "male");
map2.put("age", "24");

List<map> list = new ArrayList</map><map>();</map>
<map> list.add(map1);</map>
<map> list.add(map2);</map>

Employee employee = new Employee();
employee.setName("wjl");
employee.setSex("female");
employee.setAge(24);

// 将Map转换为JSONArray数据
JSONArray ja1 = JSONArray.fromObject(map1);
// 将List转换为JSONArray数据
JSONArray ja2 = JSONArray.fromObject(list);
// 将Bean转换为JSONArray数据
JSONArray ja3 = JSONArray.fromObject(employee);

System.out.println("JSONArray对象数据格式:");
System.out.println(ja1.toString());
System.out.println(ja2.toString());
System.out.println(ja3.toString());

// 构造Json数据,包括一个map和一个Employee对象
jo.put("map", ja1);
jo.put("employee", ja2);
System.out.println("\n最终构造的JSON数据格式:");
System.out.println(jo.toString());

return jo.toString();

}

/**
* 解析Json数据
*
* @param jsonString Json数据字符串
*/
public static void ParseJson(String jsonString) {

// 以employee为例解析,map类似
JSONObject jb = JSONObject.fromObject(jsonString);
JSONArray ja = jb.getJSONArray("employee");

ListempList = new ArrayList();

// 循环添加Employee对象(可能有多个)
for (int i = 0; i &lt; ja.size(); i++) {
Employee employee = new Employee();

employee.setName(ja.getJSONObject(i).getString("name"));
employee.setSex(ja.getJSONObject(i).getString("sex"));
employee.setAge(ja.getJSONObject(i).getInt("age"));

empList.add(employee);
}

System.out.println("\n将Json数据转换为Employee对象:");
for (int i = 0; i &lt; empList.size(); i++) {
Employee emp = empList.get(i);
System.out.println("name: " + emp.getName() + " sex: "
+ emp.getSex() + " age: " + emp.getAge());
}

}

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub

ParseJson(BuildJson());
}

}

运行结果如下

四、与org.json比较

两者的使用几乎是相同的,但org.json比json-lib要轻量得多,前者没有任何依赖,而后者要依赖ezmorph和commons的lang、logging、beanutils、collections等组件。

[转载]lumanager(LUM)自己的路修改默认管理原账号密码? - 景安网络代理,河南景安VPS8折优惠

mikel阅读(956)

[转载]lumanager(LUM)自己的路修改默认管理原账号密码? – 景安网络代理,河南景安VPS8折优惠.

 SSH的windows客户端软件下载

昨天晚上以一景安vps客户操作失误,没记住刚修改的lumanager管理密码,真是悲催,我随即发工单让景安技术帮助处理,大约40分钟左右,终于修复好密码。

lumanager(LUM)自己的路怎么修改默认管理原账号密码?根据网上的教程,首先我们要进PM,也就是phpmyadmin,地址是 http://116.255.0.2/PM (将IP换成的你vps的真实ip,注意字母要大写),然后用root用户登录(默认账号root密码zijidelu),就可以看到下面的图片。

 

忘记lumanager后台密码怎么办?

按照图片上的红色框,我们依次点击lumanager这个数据库>lu_user这个数据表>编辑表中password字段,修改为dd8eb031789b6a0664709455e7d512ce即可,然后退出PM,回到lumanager的登录界面,就可以用默认账号zijidelu默认密码zijidelu登录了。

我们还可以修改lumanager的默认账号?

如图,修改user字段内容即可。

当然,一切的前提是PM能用,如果PM不能用,就只能远程登陆VPS,然后在服务器上操作了。