月度归档:2014年12月

eBay API 之 Feedback

一个eBay账户可以卖东西也买东西,分别扮演卖家和买家。eBay对每一个产品的付款称为一个交易,如果购买了多个产品就有多次交易。eBay的Feedback是针对交易的(不是针对订单),这个必须清楚。作为买家和卖家,都可以给对方留Feedback和接收对方的Feedback(作为卖家接收Feedback,作为买家接收Feedback),也可以回复对方的Feedback,针对回复,作为买卖家,还可以Followup。

以下分开解释:
作为卖家,买家留评(卖家接收到评价),卖家针对买家的评价(好中差评)进行回复,买家收到回复后还可以辩解一下,针对回复来一次Followup。

作为买家,卖家留评(买家接收到评价),买家针对卖家的评价(好中差评)进行回复,卖家收到回复后还可以辩解一下,针对回复来一次Followup。

作为卖家,一般会在付款完成之后就会给买家好评,买家一般都不会回复这个评价,真正关心评价的是作为卖家,所以只需要实现卖家这边的功能即可,虽然eBay的Feedback API实现了双向交互。

那么流程就是如下:
1 以卖家的身份获取评价(买家留评,卖家接收到评价)

<?xml version="1.0" encoding="utf-8"?> 
<GetFeedbackRequest xmlns="urn:ebay:apis:eBLBaseComponents"> 
  <DetailLevel>ReturnAll</DetailLevel> 
  <FeedbackType>FeedbackReceivedAsSeller</FeedbackType> 
  <CommentType>Positive</CommentType> 
  <RequesterCredentials> 
    <eBayAuthToken>SellerToken</eBayAuthToken> 
  </RequesterCredentials> 
</GetFeedbackRequest>

CommentType用来过滤好中差评,FeedbackType用来过滤接收到的评价的接收实体是卖家还是买家。如果不区分好中差评,去掉CommentType过滤条件即可。如果不区分接收实体是卖家还是买家,去掉FeedbackType过滤条件即可。实际上,返回的具体的Feedback中,有一个Role的字段,用来表明这个Feedback接收主体,一般应该使用FeedbackType来过滤Feedback,返回结果的Role字段可以直接忽略它,因为我们是做卖家工具,不是做买家工具,如果是做买家工具,获取卖家给买家的Feedback才有意义。

2 回复买家给的评价(一般是在中差评时回复)
参考文档(就发送一条信息)。

关于Feedback同步,GetFeedback并不支持时间段过滤,最新的评价总会排在GetFeedback结果的前面,所以只要每天同步前面几页Feedback即可,Feedback一旦下载,回复的时候可以把回复的信息也写入到本地,这样就相当于本地和远程都同步了。

eBay的Feedback还有以下更细的内容,比如评价是否被屏蔽等,可以参考API文档。

jQuery UI 实践

jQuery UI基于jQuery,提供了一系列可用UI组件,使用非常方便,这些组件的外观是可定制的(虽然只能改下颜色),在它的下载页http://jqueryui.com/download/,可以选择你需要的组件,最后还可以选择UI样式,然后下载下来。如果对这些颜色不满意,可以到http://jqueryui.com/themeroller自定义外观。

在http://jqueryui.com/demos/中提供了每个UI的例子,可以参考。

下载的jQuery UI文件夹中:
jquery ui

使用jQuery UI的方法:

<link href="jquery-ui.css" rel="stylesheet">
<script src=jquery.js"></script>
<script src="jquery-ui.js"></script>

CSS文件需要使用到images中的图片,images文件夹应该和CSS在同一级目录。后面添加了min的表示是去空白压缩之后的版本。实际上,先引用了jQuery库后,在引用jquery-ui.min.css和jquery-ui.min.js即可。


Dialog就是一个对话框,在jQuery中,需要定义一块HTML内容对应这个对话框的内容,对话框的外围(比如标题,关闭按钮,外边框样式等)是JQuery提供的,看例子:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
body{
font: 62.5% "Trebuchet MS", sans-serif;
margin: 50px;
}
</style>
<link href="jquery-ui.css" rel="stylesheet">
<script src="jquery.js"></script>
<script src="jquery-ui.js"></script>
<script>
jQuery(function(){
	$( "#dialog" ).dialog({
		autoOpen: false,
		width: 800,
		buttons: [
			{
				text: "提交",
				click: function() {
					$( this ).dialog( "close" );
				}
			},
			{
				text: "取消",
				click: function() {
					$( this ).dialog( "close" );
				}
			}
		]
	});	
	// Link to open the dialog
	$( "#dialog-link" ).click(function( event ) {
		$( "#dialog" ).dialog( "open" );
		event.preventDefault();
	});
	
});
</script>
</head>
<body>
<p>
	<a href="#" id="dialog-link" class="ui-state-default ui-corner-all">
		<span class="ui-icon ui-icon-newwin"></span>Open Dialog
	</a>
</p>

<!-- ui-dialog -->
<div id="dialog" title="Dialog Title">
	<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</body>
</html>

代码$( “#dialog” ).dialog()传入一个配置对象(jQuery UI组件都是这个操作方式),它定义了这个组件的行为,比如在按下提交按钮时将要做的操作(如可以通过Ajax提交数据)。一种非常有用的交互方式是:在触发窗口弹出来时先通过Ajax获取数据填充到这个对话框中(对话框对应的HTML块),然后弹出,用户做了某些操作(比如填表单等),然后在提交按钮中通过Ajax提交数据,根据返回结果做进一步处理(比如关闭对话框等)。

在发出Ajax请求时,一般需要一个正在处理的加载启示,示例如下:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
body{
color: #002200;
font: 76% georgia;
margin: 0px;
padding:0px;
}
</style>
<link href="jquery-ui.css" rel="stylesheet">
<script src="external/jquery/jquery.js"></script>
<script src="jquery-ui.js"></script>
<script src="jquery.blockUI.js"></script>
<script>
var img ='<img src="data:image/gif;base64,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" />';

function loading(){
    $.blockUI({ 
    	message: img+"正在处理,请耐心等待...",
        css: {
            padding:        0,
            margin:         0,
            width:          '100%',
            height:	    '38px',
            top:	    'none',
            bottom:	    '0',
            left:           '0',
            textAlign:      'center',
            color:          'none',
            border:         'none',
            backgroundColor:'#fff',
            cursor:         'wait'
        },
        overlayCSS:  {
            backgroundColor: '#000',
            opacity:         0.4,
            cursor:          'wait'
        },
    });
    $('.blockOverlay').attr('title','Click to unblock').click($.unblockUI);
}
function closeLoading(){
	$.unblockUI({ fadeOut: 200 });
}

jQuery(function(){
	$( "#dialog").dialog({
		autoOpen: false,
		width: 800,
		buttons: [
			{
				text: "提交",
				click: function() {
					$( this ).dialog( "close" );
				}
			},
			{
				text: "取消",
				click: function() {
					$( this ).dialog( "close" );
				}
			}
		]
	});	
	// Link to open the dialog
	$( "#dialog-link" ).click(function( event ) {
		$.ajax({ 
			url:'../t.php',
			async:true,
			type:"POST",
			data:{"id":1},
			dataType:"json",
			beforeSend:loading,
			complete:closeLoading,
			success:function(data){
				$( "#dialog" ).dialog( "open" );
			},
			error:function(){
				closeLoading();
				alert("Error occurred");
			}
		});
		
		
		event.preventDefault();
	});
	
	$( "#progressbar" ).progressbar({
		value: true
	}); 	
});
</script>
</head>
<body>
<p>
<a href="#" id="dialog-link" class="ui-state-default ui-corner-all">
<span class="ui-icon ui-icon-newwin"></span>Open Dialog
</a>
</p>
<!-- ui-dialog -->
<div id="dialog" title="Dialog Title">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>	
</div>
</body>
</html>

这里引入了jQuery BlockUI(http://malsup.com/jquery/block/#demos),它提供了一个可定制的AJAX提示加载框。
loading


关于进度条和服务端交互的例子:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery UI Progressbar - Custom Label</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.11.2/themes/smoothness/jquery-ui.css">
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//code.jquery.com/ui/1.11.2/jquery-ui.js"></script>
<style>
#progressbar{
	
}
.ui-progressbar {
	position: relative;
}
.progress-label {
	position: absolute;
	left: 50%;
	top: 4px;
	font-weight: bold;
	text-shadow: 1px 1px 0 #fff;
}
</style>
<script>
$(function() {
	var progressbar = $("#progressbar"),
	progressLabel = $(".progress-label");
	progressbar.progressbar({
		value: false,
		change: function() {
			progressLabel.text( progressbar.progressbar( "value" ) + "%" );
		},
		complete: function() {
			progressLabel.text( "Complete!" );
		}
	});
	
	$("#sync").click(function(){
		// 注册一个进度条唯一编号
		$.get("../s.php","type=new",function(data){
			var sssn = data.sid;
			
			var progress = function(){
				var val = progressbar.progressbar( "value" ) || 0;
				
				if ( val < 99 ) {
					setTimeout(progress, 1000);
				}
				
				$.get("../s.php","type=getvalue&sid="+sssn,function(d){
					var v = d.value;
					if(v > val){
						progressbar.progressbar( "value", v);
					}
				},'json');
			}			
			
			setTimeout(progress, 1000);
			progressbar.show();
			$.get("../t.php","sid="+sssn,function(d){
				progressbar.hide();
				$("#result").html(d.text);
			},'json');
			
						
		},'json');
	});
});
</script>
</head>
<body>
<button id="sync">同步</button>
<div id="progressbar" style="display:none;"><div class="progress-label">Loading...</div></div>
<div id="result">
</div>
</body>
</html>

用到的服务器段脚本:

///////////////////////////
//t.php
<?php
$server="localhost";
$user="root";
$pwd="";
$db="test";
$port=3306;
$sock='/var/lib/mysql/mysql.sock';
$charset='utf8';

function mysqlidb($server,$user,$pwd,$db,$charset,$port,$sock){
	$link = false;

	$connectionRetry = 10;

	while (!isset($link) || ($link == FALSE && $connectionRetry !=0) ){
		$link = mysqli_connect($server,$user,$pwd,$db,$port,$sock);
		$connectionRetry--;
	}

	if($link) {
		if (@mysqli_select_db($link, $db)) {
			if ((trim($charset) != '') && version_compare(@mysqli_get_server_info(), '4.1.0', '>=')) {
				@mysqli_query($link, "SET NAMES '" . trim($charset) . "'");
				if (function_exists('mysqli_set_charset')) {
					@mysqli_set_charset($link, trim($charset));
				} else {
					@mysqli_query($link, "SET CHARACTER_SET_CLIENT = '" . trim($charset) . "'");
					@mysqli_query($link, "SET CHARACTER_SET_RESULTS = '" . trim($charset) . "'");
				}
			}
		}
	}
	return $link;
}

$db = mysqlidb($server,$user,$pwd,$db,$charset,$port,$sock);

if(!empty($_GET['sid'])){
	$key = trim($_GET['sid']);
	
	$total = 120;
	
	$process = 0;
	for($i=0;$i<$total;$i++){
		usleep(200000);
		$process = (int)((100/120)*$i);
	
		mysqli_query($db,"update process_bar set key_value=".$process." where key_name='".$key."' limit 1");
	}
}



echo json_encode(array("text"=>"一共同步了1000条数据。"));
exit;


/////////////////////////
//s.php
<?php
$server="localhost";
$user="root";
$pwd="";
$db="test";
$port=3306;
$sock='/var/lib/mysql/mysql.sock';
$charset='utf8';

function mysqlidb($server,$user,$pwd,$db,$charset,$port,$sock){
	$link = false;
	
	$connectionRetry = 10;
	
	while (!isset($link) || ($link == FALSE && $connectionRetry !=0) ){
		$link = mysqli_connect($server,$user,$pwd,$db,$port,$sock);
		$connectionRetry--;
	}
	
	if($link) {
		if (@mysqli_select_db($link, $db)) {
			if ((trim($charset) != '') && version_compare(@mysqli_get_server_info(), '4.1.0', '>=')) {
				@mysqli_query($link, "SET NAMES '" . trim($charset) . "'");
				if (function_exists('mysqli_set_charset')) {
					@mysqli_set_charset($link, trim($charset));
				} else {
					@mysqli_query($link, "SET CHARACTER_SET_CLIENT = '" . trim($charset) . "'");
					@mysqli_query($link, "SET CHARACTER_SET_RESULTS = '" . trim($charset) . "'");
				}
			}
		}
	}
	return $link;
}

$db = mysqlidb($server,$user,$pwd,$db,$charset,$port,$sock);


if(!empty($_GET['type']) && ($_GET['type'] == "new")){
	$key = md5(time().rand(10000,99999));
	mysqli_query($db,"insert into process_bar (key_name,key_value) values('".$key."',0)");
	
	echo json_encode(array("sid"=>$key));
	exit;
}else if(!empty($_GET['type']) && ($_GET['type'] == "getvalue")){
	$sid = trim($_GET['sid']);
	
	$d = mysqli_query($db,"select * from process_bar where key_name='".$sid."' limit 1");
	$row = mysqli_fetch_assoc($d);
		
	echo json_encode(array("value"=>(int)$row['key_value']));
	exit;
}

///////////////////////////////////
// 数据表
CREATE TABLE IF NOT EXISTS `process_bar` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `key_name` varchar(128) NOT NULL,
  `key_value` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

这个进度条和一般的正在加载提示框最主要的区别在于对一个已知任务,进度条可以提示处理完成了多少,而一般提示框不知道这个,可能永远都不会结束。实际客户端的进度条实现应该是比较简单的,传递一个值给它然后修改显示。这个工具要和服务端搭配才有实际意义。

当要去请求一个脚本时,这个脚本要把自己处理完成了多少通过某种途径表示出来,客户端通过定时扫描获取这个值来设置进度条。以上例子中,通过请求一个进度条ID编号(插入数据库),处理任务的脚本把完成的值写入对应的标号中,客户端定时获取这个标号对应的最新值设置进度条。

待续….

jQuery插件 – Datatable

官方地址:http://www.datatables.net/

这是一个客户端表格工具,以jQuery的插件方式提供。面对不是非常大的集合进行操作时,把所有数据一次性返回到客户端,然后进行操作也未尝不是一个比较好的方案,但是如果数据大到感觉到被卡主了就没有多大意义了。我试图从服务器一次性返回大概1万条数据,然后在客户端进行操作,实际情况是浏览器首先被卡死一会,然后才能操作,所以我最终放弃了使用而改为传统的翻页操作。尽管如此,在操作有限数据集合时,Datatable还是一个不错的解决方案。

以下是做压力测试时使用的例子:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title></title>
	<link rel="stylesheet" type="text/css" href="css/jquery.dataTables.css">
	<script type="text/javascript" language="javascript" src="js/jquery.js"></script>
	<script type="text/javascript" language="javascript" src="js/jquery.dataTables.js"></script>
    <script>
$(document).ready(function() {
	var str = '';
	$.get("d.php",function(d){
		for(var i=0;i<d.length;i++){
			str += "<tr>";
			for(var j=0;j<d[i].length;j++){
				str += "<td>"+d[i][j]+"</td>";
			}
			str += "</tr>";
		}
		
		$('#example tbody').html(str);
		
	    $('#example').dataTable();		
	},'json');

} );
    </script>
</head>
<body>
<table id="example" class="display" cellspacing="0" width="100%">
    <thead>
        <tr>
            <th>Name</th>
            <th>Position</th>
            <th>Office</th>
            <th>Age</th>
            <th>Start date</th>
            <th>Salary</th>
        </tr>
    </thead>
 
    <tfoot>
        <tr>
            <th>Name</th>
            <th>Position</th>
            <th>Office</th>
            <th>Age</th>
            <th>Start date</th>
            <th>Salary</th>
        </tr>
    </tfoot>
 
    <tbody>
       
    </tbody>
</table>
</body>
</html>

服务端脚本:

<?php
function getPassword(){
	$str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	$pwdlen = rand(9,20);
	$passwd = '';
	for($i=0;$i<$pwdlen;$i++){
		$chr = rand(0,61);
		$passwd .= $str[$chr];
	}
	return $passwd;	
}
$data = array();
for($i=0;$i<8000;$i++){
	$data[] = array(getPassword(),getPassword(),getPassword(),getPassword(),getPassword(),getPassword());
}

echo json_encode($data);

以上测试虽然在本地运行,但是还是要稍等一会才有数据返回。如果是在互联网环境并且数据集合继续加大的情况,估计卡顿的情况就会比较明显。
jQuery Datatable

永久链接:http://blog.ifeeline.com/1504.html

Linux下PPTPD VPN安装使用

Linux下常用的VPN服务有两个,一个是pptp,另一个则是openvpn。pptp更简单一些,但是只有独立服务器和XEN VPS可以搭建;openvpn则没有任何限制。pptp搭建出来的VPN,不需要特殊的VPN客户端,直接在Windows下创建VPN拨号连接即可;而openvpn需要安装一个客户端来进行拨号。

一共有5个软件包。这个pptp vpn解决方案,地址:http://poptop.sourceforge.net/yum/stable/packages/,这里能下载到你想要的所有东西。

1 首先安装一个叫dkms的软件包:

wget http://poptop.sourceforge.net/yum/stable/packages/dkms-2.0.17.5-1.noarch.rpm
然后:[有一个警告提示,忽略]
rpm -ivh dkms-2.0.17.5-1.noarch.rpm

2 然后安装所谓的MPPE

wget http://poptop.sourceforge.net/yum/stable/packages/kernel_ppp_mppe-1.0.2-3dkms.noarch.rpm
rpm -ivh kernel_ppp_mppe-1.0.2-3dkms.noarch.rpm

使用rpm -qa kernel_ppp_mppe查看 显示kernel_ppp_mppe-1.0.2-3dkms 确保已经正确安装了。

3 然后是安装PPP, ppp在yum源中,可以使用:
yum install ppp 安装,如果已经安装则会被忽略。
如果yum无法安装PPP,可以使用RPM方式安装:

wget http://poptop.sourceforge.net/yum/stable/packages/ppp-2.4.4-9.0.rhel5.i386.rpm
rpm -Uvh ppp-2.4.4-9.0.rhel5.i386.rpm

4 安装iptables
一般iptables都是默认有安装的,如果iptables没有安装可以使用 yum install iptables安装,否则需要下载rpm包安装。

5 PPTPD安装:

wget http://poptop.sourceforge.net/yum/stable/packages/pptpd-1.3.4-2.rhel5.i386.rpm
rpm -ivh pptpd-1.3.4-2.rhel5.i386.rpm

[CentOS6 64位版本:poptop.sourceforge.net/yum/stable/packages/pptpd-1.3.4-2.el6.x86_64.rpm]
以上两个RPM包的安装,必须要注意的是操作系统的位数,以上是32为的软件包安装,如果是64位的,必须使用64位的软件包。[可以参看软件包列表,查看操作系统位数可以使用getconf LONG_BIT]

这样,软件包安装就完毕了。接下来是配置:
配置pptp。要编辑/etc/pptpd.conf文件:
vi /etc/pptpd.conf
找到“locapip”和“remoteip”这两个配置项,将注释符去掉,更改为你期望的IP段值。localip表示服务器的IP,remoteip表示分配给客户端的IP地址,可以设置为区间。以下是pptp默认的配置:

localip 192.168.0.1
remoteip 192.168.0.234-238,192.168.0.245

localip就是只服务器的IP地址,对于一台VPS,IP地址一定不是192.168.0.1,所以这里改为你机器的IP地址。remoteip是vpn接入后分配给客户机的ip地址。

接下来再编辑/etc/ppp/options.pptpd文件,为VPN添加Google DNS:

vi /etc/ppp/options.pptpd
#在末尾添加下面两行:
ms-dns 8.8.8.8
ms-dns 8.8.4.4

设置pptp VPN账号密码。需要编辑/etc/ppp/chap-secrets这个文件:
vi /etc/ppp/chap-secrets
在这个文件里面,按照“用户名 pptpd 密码 *”的形式编写,一行一个账号和密码。

修改内核设置,使其支持转发。编辑/etc/sysctl.conf文件:
vi /etc/sysctl.conf
将“net.ipv4.ip_forward”改为1,变成下面的形式:net.ipv4.ip_forward=1
保存退出,并执行下面的命令来生效它:sysctl -p

添加iptables NAT规则
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o eth0 -j MASQUERADE
这里的192.168.0.0/24是只一个网络,这个网络就是客户端IP地址所在的网络,把这个网络的数据转发给eth0接口,然后输入下面的指令让iptables保存我们刚才的转发规则,以便重启系统后不需要再次添加:

/etc/init.d/iptables save

重启iptables:

/etc/init.d/iptables restart

重启pptp服务。输入下面的指令重启pptp:

/etc/init.d/pptpd restart

设置开机自动运行服务:
最后一步是将pptp和iptables设置为开机自动运行,这样就不需要每次重启服务器后手动启动服务了

chkconfig pptpd on
chkconfig iptables on

插曲:
在CentOS6.x上安装时,通过yum安装了ppp,然后rpm安装了pptpd-1.3.4-2.rhel5.i386.rpm,最后发现pptpd起不来,最后安装了对应的pptpd-1.3.4-2.el6.i686.rpm解决(注意el6,yum安装ppp时安装了ppp-2.4.5-5.el6.i686)

如果服务器还启用了过滤规则,可能还需要添加如下规则,否则会导致连接不上:

-A INPUT -p gre -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 47 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 1723 -j ACCEPT

SQL实例之四

最近要完成一个自动计算销售价格并更新到销售价格表的需求。具体描述如下:

产品中记录成本价 和 重量,现在要监控这两个变量,一旦这两个变量发生改变,就要按照公式重新计算价格,然后把新的价格写入产品价格表。计算价格的过程需要多个变量,其中产品对应不同类目,会有不同的收费费率,所有需要把所有SKU和类目对应起来,这里需要手动完成这个对应过程(记录到新表)。

首先要解决的是:如何监控成本价 和 重量的变化?
解决方案是使用MySQL的触发器,这个还算简单,因为我只需要监控变化,价格计算过程还是需要调用程序完成的,MySQL的触发器是无法发出调用外部程序的指令的,所以我决定采用最简单的方式:只要数据有变化就写入到一张监控表中(product_trigger),通过计划任务定时扫描这个表执行整个过程。

产品删除时,并不需要去删除对应的价格,所以对于删除产品时,我并不做监控。但是在产品新增和更新时添加了触发器:

DROP TRIGGER IF EXISTS `product_insert`;
CREATE DEFINER=`root`@`localhost` TRIGGER `product_insert` AFTER INSERT ON `product` FOR EACH ROW 
BEGIN 
insert into product_trigger(pid,sku,oprice,oweight,nprice,nweight,type,issync) 
values(NEW.id,0,0,NEW.price,NEW.weight,1,0);
END 

DROP TRIGGER IF EXISTS `product_update`;
CREATE DEFINER=`root`@`localhost` TRIGGER `product_insert` AFTER INSERT ON `product` FOR EACH ROW 
BEGIN 
insert into product_trigger(pid,sku,oprice,oweight,nprice,nweight,type,issync) 
values(NEW.id,OLD.price,OLD.weight,NEW.price,NEW.weight,2,0);
END 

产品添加时,type为1,为1表示是新插入的,那么就直接使用nprice和nweight来计算价格(不需要检测价格和重量是否变化)。当是产品更新时,type为2,为2表示是更新,要检查价格和重量是否变化了,变化了才去更新价格。如果对应的产品价格更新,对应的issync就是1,如果失败了就是2,默认去扫描issync为0的记录。

这个触发表SQL:

CREATE TABLE IF NOT EXISTS `product_trigger` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pid` int(11) NOT NULL DEFAULT '0',
  `oprice` decimal(10,2) NOT NULL DEFAULT '0.00',
  `oweight` int(10) NOT NULL DEFAULT '0',
  `nprice` decimal(10,2) NOT NULL DEFAULT '0.00',
  `nweight` int(10) NOT NULL DEFAULT '0',
  `type` int(3) NOT NULL DEFAULT '0',
  `issync` int(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

然后首先是一段主程序框架:

$allUpdate = $db->fetchAll("SELECT * FROM product_trigger
WHERE issync<1 ORDER BY id");
foreach($allUpdate as $itm){
    if(($itm['type']=1) ||  (($itm['type']==2) && ($itm['oprice'] != $itm['nprice'] || $itm['oweight'] != $itm['nweight']))){
        //需要计算价格
        ......
    }else{
        //不需要操作的记录 可能是更新其它字段导致的写入
        $db->update('product_trigger',array('issync'=>1),"id=".$itm['id']);
    }
    //主要获取产品ID
}

查询中的ORDER BY id是为了解决当有多次更新时,可以确保它是安装顺序进行的。当是更新操作时,只有价格和重量变化了才操作。

计算价格这段程序,有一个变量需要确定,就是产品对应的类名,使用如下SQL查询:

SELECT p.id,p.sku,p.is_var,pv.sku,pr.sku FROM (product p LEFT JOIN product_var pv ON p.id = pv.pid) JOIN product_rate pr ON (pr.sku = if(p.is_var,pv.sku,p.sku)) 
WHERE p.id = PID

这里的PID就是主程序循环获取的产品ID,表product_rate结构:

--
-- 表的结构 `product_rate`
--

CREATE TABLE IF NOT EXISTS `product_rate` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sku` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
  `platform` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
  `site` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
  `rate` decimal(10,4) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=2 ;

--
-- 转存表中的数据 `product_rate`
--

INSERT INTO `product_rate` (`id`, `sku`, `platform`, `site`, `rate`) VALUES
(1, '00302', 'eBay', 'US', '1.0000');

注意这个表记录的是最小SKU对应的平台站点费率。在可以确定费率之后主程序继续完善如下:

$allUpdate = $db->fetchAll("SELECT * FROM product_trigger
WHERE issync<1 ORDER BY id");
foreach($allUpdate as $itm){
    $del = true;
    if(($itm['type']=1) ||  (($itm['type']==2) && ($itm['oprice'] != $itm['nprice'] || $itm['oweight'] != $itm['nweight']))){
        //需要计算价格
        $reslt = $db->fetchAll("SELECT p.id,if(p.is_var,pv.sku,p.sku) as sku,pr.platform, pr.site, pr.rate FROM (product p LEFT JOIN product_var pv ON p.id = pv.pid) WHERE p.id = ".$itm['pid']);
        if(!empty($reslt)){
             // 循环每个SKU
             foreach($reslt as $rat){
                 // 最小SKU
                 $sku = $rat['sku']; 

                 // 这里初始化所有参数*****

                 $rateTable = $db->fetchAll("SELECT * FROM product_rate WHERE platform='eBay' sku = '".$sku); 
                 if(!$rateTable){
                     ////////////////////////////
                     // 对应费率
                     $rate = $rat['rate'];
                     // 平台
                     $platform = $rat['platform'];
                     // 站点
                     $site = $rat['site'];
                     /////////////////////////////
                     // 开始计算 写入价格表逻辑
                 }else{
                     // 对应费率
                     $rate = 0.05;
                     // 平台
                     $platform = 'SMT';
                     // 站点
                     $site = 'All';
                     // 开始计算 写入价格表逻辑
                 }
                 
             }
        }
    }

    if($del)
        //更新
        $db->update('product_trigger',array('issync'=>1),"id=".$itm['id']);
    }
}

原本想着这段程序会稍微复杂,要搞多次JOIN,实际上根本没有JOIN的机会。

Javascript在子窗口中操作父窗口DOM

父窗口:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type='text/javascript' src='jquery-1.10.1.js'></script>
<script>
jQuery("document").ready(function(){
	
	$("#open").click(function(){
		win=window.open("n.html","win","width=800,height=500,top=200,left=200,toolbar=no");	
	});
	
});
 
</script>
</head>
<body>
<button id="open">Open</button>
<div id="container">Hello World</div>
</body>
</html>

点击Open按钮将弹出一个窗口,链接到n.htmml,内容如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type='text/javascript' src='jquery-1.10.1.js'></script>

</head>
<body>
<script>
jQuery("document").ready(function(){
	$("#trigger").click(function(){
                alert(opener.$("#container").text()); 

		alert($("#container",$(opener.document)).text());	
		
		alert($(opener.document).find("#container").text());
	});
});

</script>
<button id="trigger">触发</button>
</body>
</html>

点击触发按钮,将会取出父窗口中id为container容器的内容显示。

在Javascript中,window代表窗口,它是一个容器,任何其它变量与函数都是从它开始,任何没有使用var前缀而使用的变量,默认是window的变量。window的opener指向打开它的父窗口,opener可能为空,因为它不一定是从一个存在的窗口中被打开的。

JQuery中的美元符号实际也是window对象的一个属性而已,所有opener.$这种语法就可以操作父窗口内容,这是最为便利的方式。

window.open快速参考:

window=object.open([URL ][, name ][, pramaters]]]])

URL:       新窗口的URL地址
name:      新窗口的名称,可以为空
pramaters: 属性控制字符串,在此控制窗口的各种属性,属性之间以逗号隔开

fullscreen={ yes/no/1/0 }是否全屏,默认no
channelmode={ yes/no/1/0 } 是否显示频道栏,默认no
toolbar={ yes/no/1/0 } 是否显示工具条,默认no
location={ yes/no/1/0 } 是否显示地址栏,默认no
directories = { yes/no/1/0 } 是否显示转向按钮,默认no
status={ yes/no/1/0 } 是否显示窗口状态条,默认no
menubar={ yes/no/1/0 } 是否显示菜单,默认no
scrollbars={ yes/no/1/0 } 是否显示滚动条,默认yes
resizable={ yes/no/1/0 } 是否窗口可调整大小,默认no
width=number窗口宽度(像素单位)
height=number 窗口高度(像素单位)
top=number窗口离屏幕顶部距离(像素单位)
left=number窗口离屏幕左边距离(像素单位)

这个东西仍然有应用价值的地方在于它可以跳出一个子窗口,然后在子窗口中选择一些东西然后会写到父窗口中,对于子窗口,假如数据有多页,那么使用纯JS方案,显然比较麻烦,在这里就可以翻页,然后把合适的数据回写到父窗口。

SQL实例系列 之三

ER图
产品 与 供应商实现多对多关系,但是每个产品有一个默认供应商,这个字段记录在产品-供应商表(正确应该写入产品表);采购员 和 供应商也实现多对多关系,供应商有一个默认的采购员,写入了采购员-供应商(应该写入供应商表才对)。

现在问题是,要根据一个SKU,获取默认的采购员代码(就是要进入采购员表)。流程如下,先根据SKU定位到产品ID,根据这个产品ID到产品-供应商表中获取到供应商代码,用这个代码到采购员-供应商表中获取采购员ID,用这个ID到采购员表中获取采购员代码。

这个过程少说要搞5个查询…..

试着分析一下,产品有默认供应商,供应商有默认采购员,那么不就意味着产品有默认采购员吗?这样的话,就应该把默认供应商和默认采购员写入产品表中,当要获取供应商和采购员时都非常容易(也高效)。但是目前不是这样搞的。最终使用如下SQL实现这个需求:

SELECT u.采购员 FROME 产品表 p LEFT JOIN 产品多属性表 pv ON p.pid=pv.pid JOIN 产品-供应商表 pp ON pp.pid=p.pid AND pp.is_default=1 JOIN
(采购员-供应商 ppu JOIN采购员 u ON u.uid=ppu.uid AND ppu.is_default=1)
ON pp.supplier_code = ppu. supplier_code
WHERE if(p.is_var,pv.sku,p.sku)=SKU

很费劲,有么有?

数据模型设计的重要性在这里充分体现出来。

PHP模板引擎 – Smarty

最近参与维护开发一个ERP项目,模板引擎使用Smarty,由于从来没有使用过任何模板引擎的经历,所以维护这个老古董实在是费劲。先看一段代码:

{foreach from=$items key=i item=v name=n}
{/foreach}

相比纯PHP语法:

<?php
foreach($items as $i=>$v){

}

Smarty的搞法不见得比PHP代码更加的好理解。

文档里面大概有一个观点是为了让逻辑和设计更好分离,所以发明了Smarty。事实已经充分证明,使用Smarty和直接使用PHP相比,没有任何优势,真不见得Smarty定义的那套东西会比纯PHP语法更好理解。有人说Smarty有缓存,会运行更快,这样的认识多少有点奇葩..

就我个人而言,对使用任何的模板引擎缺乏兴趣。但是既然是要维护老项目,那么也只能硬着头皮了,但是不代表着要妥协,所以,在维护开发时,我会在使用模板引擎的模板中嵌入纯PHP代码,以下是在Smarty中嵌入纯PHP代码的方法:

{@php@}
// 获取视图引用
$view = Zend_Registry::get("view");

// 直接从视图中获取数据
$data = $view->rows;

foreach($data as $d){

}
{@/php@}

以上的{@和@}是Smarty的自定义界定符。把代码嵌入这个奇葩的标签对中就可以了。

最后还是说一点模板引擎的好话吧,唯一我认可的就是可以有效防止在HTML中嵌入过多的PHP代码,可以通过Smarty嵌入部分的处理逻辑,但是它是有限的集合,如果使用纯PHP就不一样了,基本可以随心所欲,虽然灵活,但是会引起VIEW模糊混乱,这个问题只能强制规范开发者来实现,但是使用模板引擎就可以从技术上限制这个问题,逻辑处理完才到VIEW,到达VIEW后只能进行有限的逻辑处理,确实可以达到一定程度的规范。

我对模板引擎引入的好处也就仅这个看法而已。实际操作中在HTML中嵌入Smarty代码还是由程序员完成的,至少是配合完成的,页面要调整时业务逻辑往往也需要最一点点调整,所以想达到脱钩是基本不可能的,故而其它的观点就无法认可了。

注:在SMARTY3.x中已经禁止了嵌入PHP代码的入口,好吧,它做到了我说的技术手段强制规范的问题,带给开发者的是规范,同时让开发者失去了强灵活性和学习成本,这个是模板引擎被吐槽的本质原因。