alipay 无线 & PHP

详细下载demo,看文档:

http://download.alipay.com/public/api/base/WS_SECURE_PAY_SDK.zip

移动快捷支付应用集成接入包支付接口接入与使用规则.pdf

主要说明:

php 端,技术客服说,新版本sdk,使用统一公钥,即:

1.android端用商家生成的公钥和密钥,android的配置 密钥需要  pkcs8

2.php端,使用原来的demo里的公钥和密钥

郁闷呐。东西搞好后心血来潮去修改反而挂了.

测试小技巧.

服务器端测试数据post以var_export保存成文本文件,然后进行验证测试.

主要解决函数的相关启用是否开启.

转发请注明出处http://blog.martoo.cn
如有漏缺,请联系我 QQ 243008827

ANDROID & PHP 通讯加密解密

参考:

http://www.androidsnippets.com/encrypt-decrypt-between-android-and-php

注意:密钥和填充函数的类型和值.

附加动态密钥解决方案:

每次会话生成一个token外反回一个关联的密钥,客户端和服务器端的通讯基于这个密钥.

原理简单,可以降低危害。即使代码反编译成功,也无法对其它帐号造成影响.

 

附加json 处理

附加包的参考:

http://blog.csdn.net/zenson_g/article/details/8491436

注:

JSONObject!=JSONArray

 

demo-php:

$mcrypt = new MCrypt ();
// ncrypt
$data = array (
		'asd' => 'as!@#!@你dfas' 
);
$data = json_encode ( $data );
echo $data;
echo '<br>';
echo $encrypted = $mcrypt->encrypt ($data);

demo-java:

package my3des;

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

public class tempx {
	public static void main(String[] arges) {
		MCrypt mcrypt = new MCrypt();
		String encrypted;
		try {
			String decrypted = new String(mcrypt.decrypt("3a219b7b3990fc86b8d96f8d14c7a17c5800794dcd1b5f42113f7e0473a8a22e"));
			System.out.print(decrypted);
			JSONObject jsonArray = JSONObject.fromObject(decrypted);  
			System.out.println( jsonArray );  
			System.out.print(jsonArray.get("asd"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

转发请注明出处http://blog.martoo.cn
如有漏缺,请联系我 QQ 243008827

探究 Zend Guard 加密&解密

参考:http://zhainan.org/post-1667.html

解密:黑刀  http://www.tmd.me/2008/read.php/50.htm

补充:

zend guard 6.0的费用不低,在接连5.0~5.5被轻松反编译的现在。估计6.0也是早晚的事。觉得还是干扰名比较好。

但是基于维护不方便性,比如原来的项目已经部署上线的情况下。不可能进行重新生成。和覆盖。这块需要继续探究解决方案。

做为单一小脚本和核心功能模块处理的情况下仍推荐用变量名干扰。这样用5.0即可,且支持的5.2的环境比较多。

转发请注明出处http://blog.martoo.cn
如有漏缺,请联系我 QQ 243008827

 

【转】PHP工程师面临的成长瓶颈

来自:http://www.jianglb.com/2010/11/22/php-dev.html

作为Web开发中应用最广泛的语言之一,PHP有着大量的粉丝,那么你是一名优秀的程序员吗?在进行自我修炼的同时,你是否想过面对各种各样的问题,我该如何突破自身的瓶颈,以便更好的发展呢?
PHP工程师面临成长瓶颈

先明确这里所指的PHP工程师,是指主要以PHP进行Web系统的开发,没有使用其的语言工作过。工作经验大概在3~4年,普通的Web系统(百万级访问,千成级数据以内或业务逻辑不是特别复杂)开发起基本得心应手,没有什么问题。但他们会这样的物点:
◆除了PHP不使用其它的语言,可能会点shell 脚本。
◆对PHP的掌握不精(很多PHP手册都没有看完,库除外)。
◆知识面比较窄(面对需求,除开使用PHP和mysql ,不知道其它的解决办法)。
◆PHP代码以过程为主,认为面向对象的实现太绕,看不懂。

这些PHPer在遇到需要高性能,处理高并发,大量数据的项目或业务逻辑比较复杂(系统需要解决多领域业务的问题)时,缺少思路。不能分析问题的本质,技术判断力比较差,对于问题较快能找出临时的解决办法,但常常在不断临时性的解决办法中,系统和自己一步步走向崩溃。那怎么提高自己呢?怎么样可以挑战难度更高的系统?

更高的挑战在那里?
结合我自己的经验,我列出一些具体挑战,让大家先有个感性的认识。
高性能系统 的挑战在那里?
◆如何选择Web服务器?要不要使用fast-cgi 模式;
◆要不要使用反向代理服务?选择全内存缓存还是硬盘缓存?
◆是否需要负载均衡?是基于应用层,还是网络层? 如何保证高可靠性?
◆你的PHP代码性能如何,使用优化工具后怎么样? 性能瓶颈在那里? 是否需要写成C的扩展?
◆用户访问有什么特点,是读多还是写多?是否需要读写分离?
◆数据如何存储?写入速度和读出速度如何? 数据增涨访问速读如何变化?
◆如何使用缓存? 怎么样考虑失效?数据的一致性怎么保证?

高复杂性系统 的挑战在那里?
◆能否识别业务所对应的领域?是一个还是多个?
◆能否合理对业务进行抽象,在业务规则变化能以很小的代价实现?
◆数据的一致性、安全性可否保证?
◆是否撑握了面向对象的分析和设计的方法

这里所列出的问题,你都能肯定的回答,说明在技术上你基本已经可能成为架构师了。如果你还不能回答,你需要在以下几个方向加强。
怎么样提高,突破瓶颈
如果你还不能回答,你需要在以下几个方向加强:
◆分析你所使用的技术其原理和背后运行的机制,这样可以提高你的技术判断力,提高你技术方案选择的正确性;
◆学习大学期间重要的知识, 操作系统原理,数据结构和算法。知道你以前学习都是为了考试,但现在你需要为自己学习,让自己知其所以然;
◆重新开始学习C语言,虽然你在大学已经学过。这不仅是因为你可能需要写PHP扩展,而且还因为,在做C的应用中,有一个时刻关心性能、内存控制、变量生命周期、数据结构和算法的环境;
◆学习面向对象的分析与设计,它是解决复杂问题的有效的方法。学习抽象,它是解决复杂问题的唯一之道。

Cordova 2.9 插件 BaiduPush 之集成

看这个,最起码需要熟悉eclipse的操作。phonegap初级开发经验(包括插件的开发),

熟悉andriod的相关配置和简单的运行原理。穷人用不起ios,等适当的时候再去琢磨xcode的实现。不过baidupush是统一的方式。这个好说

开发语言需要java,js,xml

百度开发官方链接

http://developer.baidu.com/

注册和基本的操作

http://developer.baidu.com/wiki/index.php?title=docs/cplat/push/guide

客户端demo下载

http://developer.baidu.com/wiki/index.php?title=docs/cplat/push/sdk/clientsdk

需要注意的是:

AndroidManifest.xml

权限,对应的接受类和处理类的声明

appk的配置

 <meta-data android:name="api_key" android:value="GpcU7jAxPdItPlfYwFnZSiDV" />

主类的运行模式为

android:launchMode=”singleTask”

因为接收和绑定的过程中。会通过调用来传递相关参数和激活应用。如果不进行设置,会造成死循环.特殊情况请自行处理

电脑编译出apk进行安装后,提示”Bind Success”后,在控制台那边简单发送个消息客户端会进行提示。

接下来是pg的集成.

创建 pg 测试项目 (自己解决…)

将push demo代码 相关文件进行复制。

整合配置文件到pg里。

主类的启动换成pg.

不过baidupush是不推荐将绑定的操作在启动过程实现的。不过我放到了

super.loadUrl("file:///android_asset/www/index.html");

因为只是完成绑定操作,可以通过多线程进行操作。

接下来是主类的代码

package org.apache.cordova.test.actions;

import java.util.ArrayList;
import java.util.List;

import org.apache.cordova.DroidGap;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Notification;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.widget.TextView;
import android.widget.Toast;

import com.baidu.android.common.logging.Log;
import com.baidu.android.pushservice.CustomPushNotificationBuilder;
import com.baidu.android.pushservice.PushConstants;
import com.baidu.android.pushservice.PushManager;
import com.phonegap.plugin.baidupush.Utils;

public class test extends DroidGap {
	public static int initialCnt = 0;
	private boolean isLogin = false;

	@Override
	public void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);
		super.init();
		android.webkit.WebSettings settings = super.appView.getSettings();

		// String appCachePath = this.getCacheDir().getAbsolutePath();
		// settings.setAppCachePath(appCachePath);
		// settings.setAllowFileAccess(true);
		super.appView.addJavascriptInterface(new IGDMOBILE(this, appView),
				"IGD_WH");
		settings.setAppCacheEnabled(false);

		super.loadUrl("file:///android_asset/www/index.html");
		// baidu push start
		Resources resource = this.getResources();
		String pkgName = this.getPackageName();

		PushManager.startWork(getApplicationContext(),
				PushConstants.LOGIN_TYPE_API_KEY,
				Utils.getMetaValue(test.this, "api_key"));

		CustomPushNotificationBuilder cBuilder = new CustomPushNotificationBuilder(
				resource.getIdentifier("notification_custom_builder", "layout",
						pkgName), resource.getIdentifier("notification_icon",
						"id", pkgName), resource.getIdentifier(
						"notification_title", "id", pkgName),
				resource.getIdentifier("notification_text", "id", pkgName));
		cBuilder.setNotificationFlags(Notification.FLAG_AUTO_CANCEL);
		cBuilder.setNotificationDefaults(Notification.DEFAULT_SOUND
				| Notification.DEFAULT_VIBRATE);
		cBuilder.setStatusbarIcon(this.getApplicationInfo().icon);
		cBuilder.setLayoutDrawable(resource.getIdentifier(
				"simple_notification_icon", "drawable", pkgName));
		PushManager.setNotificationBuilder(this, 1, cBuilder);
		// baidu push end

		// Set by <content src="index.html" /> in config.xml
		// super.loadUrl("http://192.168.1.114:8080/test/phonegap/");//Config.getStartUrl()
		// super.setIntegerProperty("splashscreen", R.drawable.splash);

		// super.loadUrl("file:///android_asset/www/index.html", 3000);
	}

	@Override
	public void onStart() {
		super.onStart();
		Log.i("gddebug", "gddebug test.onStart:");
		PushManager.activityStarted(this);
	}

	@Override
	public void onResume() {
		super.onResume();

		showChannelIds();
	}

	@Override
	protected void onNewIntent(Intent intent) {
		// 如果要统计Push引起的用户使用应用情况,请实现本方法,且加上这一个语�?
		super.onNewIntent(intent);
		setIntent(intent);
		handleIntent(intent);
	}

	@Override
	public void onStop() {
		super.onStop();
		PushManager.activityStoped(this);
	}

	/**
	 * 处理Intent
	 * 
	 * @param intent
	 *            intent
	 */
	private void handleIntent(Intent intent) {
		String action = intent.getAction();

		if (Utils.ACTION_RESPONSE.equals(action)) {

			String method = intent.getStringExtra(Utils.RESPONSE_METHOD);

			if (PushConstants.METHOD_BIND.equals(method)) {
				String toastStr = "";
				int errorCode = intent.getIntExtra(Utils.RESPONSE_ERRCODE, 0);
				if (errorCode == 0) {
					String content = intent
							.getStringExtra(Utils.RESPONSE_CONTENT);
					String appid = "";
					String channelid = "";
					String userid = "";

					try {
						JSONObject jsonContent = new JSONObject(content);
						JSONObject params = jsonContent
								.getJSONObject("response_params");
						appid = params.getString("appid");
						channelid = params.getString("channel_id");
						userid = params.getString("user_id");
					} catch (JSONException e) {
						Log.e(Utils.TAG, "Parse bind json infos error: " + e);
					}

					SharedPreferences sp = PreferenceManager
							.getDefaultSharedPreferences(this);
					Editor editor = sp.edit();
					editor.putString("appid", appid);
					editor.putString("channel_id", channelid);
					editor.putString("user_id", userid);
					editor.commit();

					showChannelIds();

					toastStr = "Bind Success";
				} else {
					toastStr = "Bind Fail, Error Code: " + errorCode;
					if (errorCode == 30607) {
						Log.d("Bind Fail", "update channel token-----!");
					}
				}

				Toast.makeText(this, toastStr, Toast.LENGTH_LONG).show();
			}
		} else if (Utils.ACTION_LOGIN.equals(action)) {
			String accessToken = intent
					.getStringExtra(Utils.EXTRA_ACCESS_TOKEN);
			PushManager.startWork(getApplicationContext(),
					PushConstants.LOGIN_TYPE_ACCESS_TOKEN, accessToken);
			isLogin = true;
//			initButton.setText("更换百度账号初始化Channel");
		} else if (Utils.ACTION_MESSAGE.equals(action)) {
			String message = intent.getStringExtra(Utils.EXTRA_MESSAGE);
			String summary = "Receive message from server:nt";
			Log.e(Utils.TAG, summary + message);
			JSONObject contentJson = null;
			String contentStr = message;
			try {
				contentJson = new JSONObject(message);
				contentStr = contentJson.toString(4);
			} catch (JSONException e) {
				Log.d(Utils.TAG, "Parse message json exception.");
			}
			summary += contentStr;
			AlertDialog.Builder builder = new AlertDialog.Builder(this);
			builder.setMessage(summary);
			builder.setCancelable(true);
			Dialog dialog = builder.create();
			dialog.setCanceledOnTouchOutside(true);
			dialog.show();
		} else {
			Log.i(Utils.TAG, "Activity normally start!");
		}
	}

	private void showChannelIds() {
		String appId = null;
		String channelId = null;
		String clientId = null;

		SharedPreferences sp = PreferenceManager
				.getDefaultSharedPreferences(this);
		appId = sp.getString("appid", "");
		channelId = sp.getString("channel_id", "");
		clientId = sp.getString("user_id", "");

		Resources resource = this.getResources();
		String pkgName = this.getPackageName();
//		infoText = (TextView) findViewById(resource.getIdentifier("text", "id", pkgName));
//
//		String content = "tApp ID: " + appId + "ntChannel ID: " + channelId
//				+ "ntUser ID: " + clientId + "nt";
//		if (infoText != null) {
//			infoText.setText(content);
//			infoText.invalidate();
//		}
	}

	private List<String> getTagsList(String originalText) {

		List<String> tags = new ArrayList<String>();
		int indexOfComma = originalText.indexOf(',');
		String tag;
		while (indexOfComma != -1) {
			tag = originalText.substring(0, indexOfComma);
			tags.add(tag);

			originalText = originalText.substring(indexOfComma + 1);
			indexOfComma = originalText.indexOf(',');
		}

		tags.add(originalText);
		return tags;
	}
}

 

需要注意的是

PushMessageReceiver.java

清楚原控件的相关代码。

启动的时候完成绑定操作,然后再测试推送,ok,成功过。

关于绑定操作,这时候需要自己写插件啦。

代码如下

package com.phonegap.plugin.baidupush;

import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.apache.cordova.test.R;
import org.apache.cordova.test.actions.test;
import org.json.JSONArray;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;

import com.baidu.android.pushservice.PushConstants;
import com.baidu.android.pushservice.PushManager;

public class BaiduPush extends Plugin {
	public static final String PLUGIN_NAME = "BaiduPush";
	PendingIntent contentIntent;

	@Override
	public PluginResult execute(String action, JSONArray args, String callbackId) {
		Log.d(PLUGIN_NAME, "Plugin execute called with action: " + action);
		boolean result = false;
		String appId = null;
		String channelId = null;
		String clientId = null;
		SharedPreferences sp = PreferenceManager
				.getDefaultSharedPreferences(this.cordova.getActivity().getApplicationContext());
		appId = sp.getString("appid", "");
		channelId = sp.getString("channel_id", "");
		clientId = sp.getString("user_id", "");
		//通过js将这几个值传出去与用户做绑定,漂亮代码我就不写了。可以参考别人的操作把代码写漂亮些。
		Log.i("gddebug","gddebug com.phonegap.plugin.baidupush.BaiduPush execute:appId="+appId);
		Log.i("gddebug","gddebug com.phonegap.plugin.baidupush.BaiduPush execute:channelId="+channelId);
		Log.i("gddebug","gddebug com.phonegap.plugin.baidupush.BaiduPush execute:clientId="+clientId);

		return new PluginResult(result ? PluginResult.Status.OK
				: PluginResult.Status.ERROR);
	}
}

服务器端的东西比较容易解决。这边以php为例.

http://developer.baidu.com/wiki/index.php?title=docs/cplat/push/sdk/serversdk

下载好后,自己看下吧。Oauth2.0的一般模式呵呵。

主要需要注意的。暂时有

推送消息的具体内容。

基础参数看

http://developer.baidu.com/wiki/index.php?title=docs/cplat/push/api/list#push_msg

关于messages的内容分析。。这是我最想骂人的地方。。

上代码:

// 通知类型的内容必须按指定内容发送,示例如下:
	$message = json_encode ( array (

			'title' => 'test_push2',
			//对应的分组。这样应用里就可以分组进行推送了。yd.通过服务器端进行绑定就可以了。哈
			'tag'=>'caihaibin',

			'description' => 'openurl2',
			//必须 为0,basic_style才有效
			'notification_builder_id' => 0,
			//这个需要强烈注意,文档根本没有说明
			//具体的合并参数是  4 铃声 2震动 1可清除 分别累加
			'notification_basic_style' => 7,
			//这个启动当前应用
			'open_type' => 2,

			'custom_content' => array (
					'type_code' => '1',
					'type_content' => '7448969' 
			) )

好了,基本的东西就到这了。

偶然发现在faq里有简单的说明:唉。。

http://developer.baidu.com/wiki/index.php?title=docs/cplat/push/faq

搜索 notification_basic_style 相关

注:

1.有时会有延时。这个可以无视。毕竟处理方面在百度。反正误差估计就1分钟,还算合理。

2.测试分组和两app时的提示是否有冲突。暂时没有发现

转发请注明出处http://blog.martoo.cn
如有漏缺,请联系我 QQ 243008827

IE6 PNG 解决方案 2

IE6 最让人头痛的除了兼容,其实还有png.还有因为png兼容脚本引发的各种兼容的蛋疼问题。

举个例子。

DD_belatedPNG_0.0.8a.js

的渲染各有代价,但是偏偏冲突最大是兼容和渲染解决得最好的。

DD_belatedPNG.fix('*');

矛盾吧哈哈。会生成一堆shape的标签,这样那样的问题。

各种脚本测试过程中,主要的透明问题有以下几个:
1.切换透明问题

2.图片渲染数据糟糕的问题

3.特殊标签不能很好渲染等。

好了,接下来讲解图片渲染的完美解决方案。
叫哥吧哈哈.

<script type="text/javascript" src="js/iepngfix/iepngfix_tilebg.js"></script>
<style type="text/css">
img,div,input,a,li,p {
	behavior: url("js/iepngfix/iepngfix.htc")
}
</style>
<script type=text/javascript src="js/DD_belatedPNG_0.0.8a.js"></script>
<script type="text/javascript">
document.execCommand("BackgroundImageCache", false, true);
$(function(){
	DD_belatedPNG.fix('div#zd_right_jqurey');
	if(typeof(fixie6f)=='function'){
		//当是ie6的时候,检测这方法,进行执行,对应的页面做处理
		fixie6f();
	}
})
</script>

利用各脚本的处理特点。dd_gelatedpng渲染效果好,但是冲突严重。

兼容好的,效果不好。那就结合一起使用。其中iepngfix的兼容处理得相当完善,但是部分图渲染得不是很好。

那就用dd_gelatedpng 单独渲染这玩意.

不过最后还是说一条.ie6你还是去死吧死吧死吧….

转发请注明出处http://blog.martoo.cn
如有漏缺,请联系我 QQ 243008827

Jquery ajax 并发请求,返回集中处理

/**
		* @author:caihaibin
		* @desc:用于解决并发请求后的集中处理,如果回调后的有特殊操作,可以在回传参数里做特殊标记,
		* @example:{type:'test',result:''} //<==遍历里有test做特殊处理
		* @demo:
		* majax([ 'test.php', 'test.php?r=23', 'test.php?r=2333',
		*			'test.php?r=233333', 'test.php?test=true' ], function(
		*					responses) {
		*				for ( var index in responses) {
		*					if (responses[index].state == 200) {
		*						console.log(responses[index].result);
		*					}
		*				}
		*			});
		*/
		function majax(requests, recall) {
			var responses = [];
			if (!$.isArray(requests)) {
				recall(responses);
			}
			var responseindex = 0;
			var requestlength = requests.length;
			if (!requestlength) {
				recall(responses);
			}
			for ( var index in requests) {
				$.ajax({
					url : requests[index],
					success : function(result, textStatus) {
						responses[responseindex] = {
							state : 200,
							result : result
						};
						responseindex++;
						if (responseindex == requestlength) {
							recall(responses);
						}
					},
					error : function(obj, textStatus) {
						responses[responseindex] = {
							state : textStatus
						};
						responseindex++;
						if (responseindex == requestlength) {
							recall(responses);
						}
					}
				});
			}
		}

转发请注明出处http://blog.martoo.cn
如有漏缺,请联系我 QQ 243008827

HTML5 跨域提交和上传实现 及 nginx $_SERVER[‘HTTP_X_FILENAME’]解决

以下所涉及的基本都需要有服务器端的权限,没有请绕道~ ,当然也可以当学习哈~

GET

这个普通的jsonp就可以了,其实就是远程调个本地的函数。。谷歌去吧。

POST

发现个有意思的,就是file条件下可以无视跨域,这种做内置浏览器的时候可以好好利用下。

AJAX 这个是本篇最纠结的

普通提交依旧通过jsonp解决

关于表单提交和文件的上传

需要服务器端头的设置

相关原理参考: http://drops.wooyun.org/tips/188

服务器端的设置代码为

if (isset ( $_SERVER ['HTTP_ORIGIN'] )) {
	header ( "Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}" );
	header ( 'Access-Control-Allow-Credentials: true' );
	header ( 'Access-Control-Max-Age: 86400' ); // cache for 1 day
}
// Access-Control headers are received during OPTIONS requests
if ($_SERVER ['REQUEST_METHOD'] == 'OPTIONS') {

	if (isset ( $_SERVER ['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] ))
		header ( "Access-Control-Allow-Methods: GET, POST, OPTIONS" );

	if (isset ( $_SERVER ['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] ))
		header ( "Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}" );

	// exit(0);
}

 

XMLHttpRequest Level 2

是否支持跨域的脚本验证

var xhr = new XMLHttpRequest();
	if (typeof xhr.withCredentials == undefined) {
		document.write("fuck");
		//This browser does not support xhr2 yet.
	} else {
		document.write("true");
		//Go head!
	}

简单数据提交

	xhr.open("POST", "http://xxxxxxx", true);
	xhr.onload = function(data) {
		document.write("ok");
		document.write(data);
		//加载完咯...
	}
	//支持跨域发送cookies 改成true 就发不了... 和顺序没有关系
	//xhr.withCredentials = true;
	xhr.send();

 

上传相关文件从这获取,别人写好的,使用的是html5的特性。不过要积分。javaeye那边无需积分

http://blog.csdn.net/never_say_goodbye/article/details/8598521

上传的文件的提取不同于表单的提交,也不能简单通过查看chrome的表单请求头做判断

上传的文件名

$fn = (isset($_SERVER['HTTP_X_FILENAME']) ? $_SERVER['HTTP_X_FILENAME'] : false);

注:这样获取的文件名在apache下可用。在nginx 里会无法获取。可直接通过判断php://input进行文件传入判断,完善点可以再通过获取文件头判断文件类型

上传的文件数据,单个。。

file_get_contents('php://input')

转发请注明出处http://blog.martoo.cn
如有漏缺,请联系我 QQ 243008827