MySQL悲观锁总结和实践

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
 
使用场景举例:以MySQL InnoDB为例
商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。
 

//1.查询出商品信息
select status from ceshi_lock where id=1;
//2.根据商品信息生成订单
insert into ceshi_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update ceshi_lock set status=2;
 

前面已经提到,只有当goods status为1时才能对该商品下单,上面第一步操作中,查询出来的商品status为1。但是当我们执行第三步Update操作的时候,有可能出现其他人先一步对商品下单把goods status修改为2了,但是我们并不知道数据已经被修改了,这样就可能造成同一个商品被下单2次,使得数据不一致。所以说这种方式是不安全的。
 
2使用悲观锁来实现:
在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当我们在查询出goods信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为goods被锁定了,就不会出现有第三者来对其进行修改了。
 

 
我们可以使用命令设置MySQL为非autocommit模式:
set autocommit=0;
 
设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:
//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息
select status from ceshi_lock where id=1 ;
//2.根据商品信息生成订单
insert into ceshi_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update ceshi_lock set status=2;
//4.提交事务
commit;/commit work;
 

 
上面的第一步我们执行了一次查询操作:select status from ceshi_lock where id=1 for update;
与普通查询不一样的是,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。此时在ceshi_lock表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。
创建表ceshi_lock,包括id,status,name三个字段,id为主键,数据库中记录如下;

1.png


为了测试数据库锁,使用两个console来模拟不同的事务操作,分别用console1、console2来表示。


console1:查询出结果,并且把该条数据锁定了

6.png

console2:查询被阻塞

7.png


二:明确指定主键,若查无此数据,无lock

5.png


console1:查询出结果,并且把该条数据锁定了

2.png

console2:查询被阻塞

3.png


以上就是关于数据库主键对MySQL锁级别的影响实例,需要注意的是,除了主键外,使用索引也会影响数据库的锁定级别

有不对的地方欢迎拍砖,下一次会带来数据库乐观锁的总结和实践


客官,点击下方打赏一个呗~

【SSE】实现服务端数据推送方式

老的http协议是请求-响应式的,对于某些实时性要求比较高的需求(例如微博消息推送),实现起来是比较麻烦

html5标准中,新增了一个SSE(server-sent event,服务端推送事件)允许服务器端向客户端推送新数据(简称数据推送)的HTML5技术

当数据源有新数据时,服务器端能立刻发送给一个或多个客户端,而不用等客户端来请求,这些新数据可能是突发新闻、最新股票、上线朋友的聊天信息、新的天气预报、策略游戏中的下一步等。

SSE适用于更新频繁、低延迟并且数据都是从服务端到客户端。它和WebSocket的区别:

1)便利,不需要添加任何新组件,用任何习惯的后端语言和框架就能继续使用,不用为新建虚拟机弄一个新的IP或新的端口号而劳神。

2)服务器端的简洁。因为SSE能在现有的HTTP/HTTPS协议上运作,所以它能够直接运行于现有的代理服务器和认证技术。

WebSocket相较SSE最大的优势在于它是双向交流的,这意味着服务器发送数据就像从服务器接受数据一样简单,而SSE一般通过一个独立的Ajax请求从客户端向服务端传送数据,因此相对于WebSocket使用Ajax会增加开销。因此,如果需要以每秒一次或者更快的频率向服务端传输数据,就应该用WebSocket。

sse是直接建立在当前http连接上的,本质上是保持一个http长连接,但是和comet不同的是:comet是每次服务端返回数据后,连接关闭然后客户端马上再次发起连接。而sse是保持长连接常驻。

而客户端对数据的通信是通过js的EventSource来进行的,EventSource提供了三个事件:
1、open:当成功建立连接时产生
2、message:当接收到消息时产生
3、error:当出现错误时产生
直接使用即可。
<html>
    <head>
        <meta charset="UTF-8">
        <title>basic SSE test</title>
    </head>
    <body>
        <pre id = "x">initializting...</pre>
        <!--之所以使用pre标签而不是p或者div是为了确保数据能以它被接受时的格式呈现,而不会修改或格式化-->
    </body>
    <script>
        var es = new EventSource("./basic_sse.php");
        es.addEventListener("message",function(e){
            //e.data
            document.getElementById("x").innerHTML += "\n"+e.data;
        },false);//使用false表示在冒泡阶段处理事件,而不是捕获阶段。
    </script>
</html>
<!doctype html>
<?php
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    $time = date('Y-m-d H:i:s');

    echo 'retry: 1000'.PHP_EOL;
    echo 'data: The server time is: '.$time.PHP_EOL.PHP_EOL;
?>


空白:表示该行是注释,会在处理时被忽略。
data:表示该行包含的是数据。以 data 开头的行可以出现多次。所有这些行都是该事件的数据。
event:表示该行用来声明事件的类型。浏览器在收到数据时,会产生对应类型的事件。
id:表示该行用来声明事件的标识符。
retry,表示该行用来声明浏览器在连接断开之后进行再次连接之前的等待时间。

jdfw.gif


***************当你发现自己的才华撑不起野心时,就请安静下来学习吧***************

PHP与客户端交互使用OpenSSL RSA加解密数据

众所周知,任何数据在互联网上传输,是极其不安全的,所以一些私密数据就需要通过加密后再传输
应用场景:PHP与客户端交互
一:下载OpenSSL
wget  http://www.openssl.org/source/openssl-1.1.0e.tar.gz


二:解压OpenSSL到当前目录,得到openssl-openssl-1.1.0e文件夹
tar -xzf openssl-1.1.0e.tar.gz


三:进入解压的目录
cd openssl-1.1.0e


四:设定Openssl 安装( --prefix )参数为要安装的目录,也就是安装后的档案会出现在该目录下
./config --prefix=/usr/local/openssl


五:执行命令./config -t


六:执行make,编译Openssl
make install


七:进入Openssl目录
cd usr/local/openssl/bin/

1.png

文件已经存在,安装完成



生成原始 RSA私钥文件:openssl genrsa -out rsa_private_key.pem 1024
加密长度是1024位。加密长度是指理论上最大允许“被加密的信息”长度的限制,也就是明文的长度限制。随着这个参数的增大(比方说2048),允许的明文长度也会增加,但同时也会造成计算复杂度的极速增长。

将原始 RSA私钥转换为 pkcs8格式:openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out private_key.pem


根据私钥生成RSA公钥:openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

进入目录查看生成的公钥、私钥文件,cd usr/local/bin/

从结果中可以看出,文件已经生成成功!

QQ截图20170816102503.png

==========华丽的分割线==========
九:PHP使用RSA私钥加密、公钥解密
1)将上面生成的公钥文件和私钥文件复制到PHP项目中
cp -r ./rsa_public_key.pem /www/wwwroot/rsa/
cp -r ./rsa_private_key.pem /www/wwwroot/rsa/


2)下图可以看出,已经成功的把公钥、私钥文件复制进来

5.png


3)执行php代码
/**
 * rsa加解密
 */
public function rsa(){
	
	$private_key = file_get_contents("http://".$_SERVER['SERVER_NAME']."/rsa/rsa_private_key.pem");		//私钥
	$public_key = file_get_contents("http://".$_SERVER['SERVER_NAME']."/rsa/rsa_public_key.pem");		//公钥
	
	$pi_key =  openssl_pkey_get_private($private_key);						//可用返回资源id
	$pu_key = openssl_pkey_get_public($public_key);
	
//	加密数据
	$data = [
	    'id' => '1234567890',
	    'name' => '小明',
	    'mobile' => '123456',
	];
	$data = json_encode($data,JSON_UNESCAPED_UNICODE);						//转json格式


	$encrypted = '';		//加密后的结果
	$decrypted = '';		//解密后的结果

	openssl_public_encrypt($data, $encrypted, $pu_key);						//公钥加密
//	参数:1.数据源,2.加密后的结果,3.资源id
	$encrypted = base64_encode($encrypted);									//转base64传输
	

	openssl_private_decrypt(base64_decode($encrypted), $decrypted, $pi_key);//私钥解密
//	参数:1.数据源(先解base64),2.解密后的结果,3.资源id
	
//	打印结果
	p('数据源:'.$data);
	p('加密后的数据:'.$encrypted);
	p('解密后的数据:'.$decrypted);
}

6.png

7.png


使用OpenSSL生成RSA文件,PHP使用RSA加解密至此完成~~


客官,点击下方打赏一个呗~


MySQL之按年月分区

都说程序猿是靠爬坑积累起来的,对于这话深感认同。。。

最近公司新运行的项目,数据量已经过百万级了,为公司带来业绩、利润的同时,程序一些操作的耗时也越来越大,好吧,我承认这是前期着急上线,对于数据量的考量没有做到位,现在开始补救。。(开启填坑模式o(╯□╰)o)

在索引、字段类型已经建立好的情况下,将大表拆分
要对表的时间字段(类型:datetime)基于月进行分区。于是遍历MySQL官方文档分区章节,总结如下:

 
主要是以下几种:

1. RANGE分区
 
2. LIST分区
 
3. HASH分区

4. KEY分区

测试的维度主要从两个方面进行
 

 
针对特定的查询,是否能进行分区剪裁(即只查询相关的分区,而不是所有分区)
 
一、分区剪裁 
针对特定的查询,是否能进行分区剪裁(即只查询相关的分区,而不是所有分区)
 
二、查询时间
 
鉴于该批测试数据是静止的(即没有并发进行的insert,update和delete操作),数据量也不太大,从这个维度来考量貌似意义也不是很大。
 
因此,重点测试第一个维度。
 
基于RANGE的分区方案
 
在这里,选用了TO_DAYS函数


1、创建表并且设置按年月分区

CREATE TABLE bdm_range_datetime(
    id INT,
    hiredate DATETIME,
    time INT
)
PARTITION BY RANGE (TO_DAYS(hiredate) ) (
    PARTITION p1 VALUES LESS THAN ( TO_DAYS('20170101') ),
    PARTITION p2 VALUES LESS THAN ( TO_DAYS('20170201') ),
    PARTITION p3 VALUES LESS THAN ( TO_DAYS('20170301') ),
    PARTITION p4 VALUES LESS THAN ( TO_DAYS('20170401') ),
    PARTITION p5 VALUES LESS THAN ( TO_DAYS('20170501') ),
    PARTITION p6 VALUES LESS THAN ( TO_DAYS('20170601') ),
    PARTITION p7 VALUES LESS THAN ( TO_DAYS('20170701') ),
    PARTITION p8 VALUES LESS THAN ( TO_DAYS('20170801') ),
    PARTITION p9 VALUES LESS THAN ( TO_DAYS('20170901') ),
    PARTITION p10 VALUES LESS THAN ( TO_DAYS('20171001') )
);

1.png

2、批量插入10W条数据

public function ceshi(){
	for($i=0;$i<99999;$i++){
		$time = rand(1488297600, 1501430399);			//3月1日-7月30日
		$data['hiredate'] = date("Y-m-d H:i:s",$time);
		$data['time'] = time();
		$data['id'] = $i;
		M('range_datetime') -> add($data);
	}
}

2.png

3、查看各分区数据条数

SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'bdm_range_datetime';

4.png

4、查看特定查询语句的执行计划

explain partitions select * from bdm_range_datetime where hiredate > '20170501001000' and hiredate<='20170630235959';

3.png

从执行结果可以看出,查询2017年5月1日 - 2017年6月30日的数据,查询39623行,确实是走的所属分区,但是为什么会多走了p1这个分区呢??百思不得其解,查询了很多资料,还是没能找到答案,有知道原因的小伙伴可以在下方给我留言噢


==========华丽的分割线==========


TO_DAYS函数出现的这个p1分区没有解决的情况下,换了一种思路

基于RANGE COLUMNS的分区方案

RANGE COLUMNS可以直接基于列,而无需像上述RANGE那种,分区的对象只能为整数。


1、创建表并且设置按年月分区

CREATE TABLE bdm_range_datetime(
    id INT,
    hiredate DATETIME,
    time INT
)
PARTITION BY RANGE COLUMNS(hiredate) (
    PARTITION p1 VALUES LESS THAN ( '20170101' ),
    PARTITION p2 VALUES LESS THAN ( '20170201' ),
    PARTITION p3 VALUES LESS THAN ( '20170301' ),
    PARTITION p4 VALUES LESS THAN ( '20170401' ),
    PARTITION p5 VALUES LESS THAN ( '20170501' ),
    PARTITION p6 VALUES LESS THAN ( '20170601' ),
    PARTITION p7 VALUES LESS THAN ( '20170701' ),
    PARTITION p8 VALUES LESS THAN ( '20170801' ),
    PARTITION p9 VALUES LESS THAN ( '20170901' ),
    PARTITION p10 VALUES LESS THAN ('20171001' )
);

5.png

2、批量插入10W条数据

public function ceshi(){
	for($i=0;$i<99999;$i++){
		$time = rand(1488297600, 1501430399);			//3月1日-7月30日
		$data['hiredate'] = date("Y-m-d H:i:s",$time);
		$data['time'] = time();
		$data['id'] = $i;
		M('range_datetime') -> add($data);
	}
}

2.png

3、查看各分区数据条数

SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'bdm_range_datetime';

6.png

4、查看特定查询语句的执行计划

explain partitions select * from bdm_range_datetime where hiredate > '20170501001000' and hiredate<='20170630235959';

7.png


总结: 
1. 经过对比,个人倾向于第二种方案,即基于RANGE COLUMNS的分区实现。
 
2. 在5.7版本之前,对于DATA和DATETIME类型的列,如果要实现分区裁剪,只能使用YEAR() 和TO_DAYS()函数,在5.7版本中,又新增了TO_SECONDS()函数。
 
3. 其实LIST也能实现基于天的分区方案,但在这个需求上,相比于RANGE,还是显得很鸡肋。
 
4. TIMESTAMP类型的列,只能基于UNIX_TIMESTAMP函数进行分区,切记!


==========华丽的分割线==========

普通表转分区表

1、查看原表结构

desc bdm_order;

1.png

这是一张普通表,oid为主键


2、新增字段(datetime类型,用于分区)

2.png


3、修改原表结构(关联主键、索引)

3.png

4.png

设置关联主键,关联索引


4、将原普通表更改为分区表

alter table bdm_order partition by RANGE COLUMNS(hiredate) (
    PARTITION p201612 VALUES LESS THAN ( '20170101' ),
    PARTITION p201701 VALUES LESS THAN ( '20170201' ),
    PARTITION p201702 VALUES LESS THAN ( '20170301' ),
    PARTITION p201703 VALUES LESS THAN ( '20170401' ),
    PARTITION p201704 VALUES LESS THAN ( '20170501' ),
    PARTITION p201705 VALUES LESS THAN ( '20170601' ),
    PARTITION p201706 VALUES LESS THAN ( '20170701' ),
    PARTITION p201707 VALUES LESS THAN ( '20170801' ),
    PARTITION p201708 VALUES LESS THAN ( '20170901' ),
    PARTITION p201709 VALUES LESS THAN ( '20171001' ),
    PARTITION p201710 VALUES LESS THAN ( '20171101' ),
    PARTITION p201711 VALUES LESS THAN ( '20171201' ),
    PARTITION p201712 VALUES LESS THAN ( '20180101' ),
    PARTITION p201801 VALUES LESS THAN ( '20180201' ),
);

5.png


5、查看分区数据情况

SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'bdm_order';

6.png

从结果中看出,分区裁剪已经成功,所有数据都存在于2017年之前的分区中


6、将原time类型时间戳转换为可视化时间写入新增的字段中

UPDATE bdm_order SET hiredate=FROM_UNIXTIME(orderTime, '%Y-%m-%d %H:%i:%S') WHERE sid!=0;

7.png

8.png

从结果中看出,时间戳已经转换为可视化时间写到新字段中了


7、再次查看分区数据情况和查看特定查询语句的执行计划

SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'bdm_order';

9.png

explain partitions select * from bdm_order where hiredate > '20170501001000' and hiredate<='20170630235959';

10.png

至此填坑完毕!



客官,点击下方打赏一个呗~

Thinkphp关闭系统日志

题记:一大早正在吃早餐,工作群里有人发了张截图(如下图),说网站打不开了,马上丢下碗筷去处理。。。


1.jpg


原来是服务器磁盘空间不够了,可一想不对啊。。这只放框架文件,40个G怎么就不够了!?

2.png


好吧,连接Xshell,排查是哪些文件占用的系统磁盘空间!困...

命令:du -sh /*


3.png

4.png


一路排查下来,10个多G!!!原来是系统日志文件搞的鬼,果断删除!!!

删除之后,网站立刻恢复正常,整个世界都清净了!赶紧滚去继续吃早餐。。


事情到了这里还没完,既然这个日志文件是自动生成的,果断给他关闭掉

在index.php入口文件注释掉下面这句话

//define('APP_DEBUG',TRUE);

 5.png


然后去配置项那里增加一行参数

/*隐藏index.php主入口*/
'URL_CASE_INSENSITIVE'  =>	FALSE,

6.png


如果不设置这个配置项,本地运行没问题,但是到服务器会出现报错哟。。。


点击下方打赏一个呗~

【微信扫一扫】爬坑之旅,微信扫一扫调起摄像头

微信扫一扫功能在我们日常生活中很常见,那么微信JS-SDK是如何实现扫一扫功能的呢,最近在做一个线下扫码取货的功能,并附有代码实现。


查看微信官方JS-SDK文档,调起微信扫一扫的api


一:微信JS-SDK里面的接口都需要依赖js的config配置项,所以我们先获取配置项需要的参数

/**
 * 获取微信jsSDK配置项数据
 */
public function getWxConfig(){
	$mid = $_SESSION['userData']['mid'];
	
//	商户微信配置
	$wechat = M('wxtoken') -> where(['mid'=>$mid]) -> find();
	
//	签名生成时间戳
	$time = time();					//必须为数值类型,否则某些版本的的手机系统会报js的配置项有误!!
	
	$rand = rand(1000, 999999);		//签名随机串
	$rand = strval($rand);			//数值转字符串类型(如果不注意数据类型,只有安卓7.0能通过,其他版本的手机全部报js的配置项有误!!)
	
//	获取access_token
	$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$wechat['appid']."&secret=".$wechat['appsecret'];
	$get_access_token = $this -> https_request($url);
	$get_access_token = json_decode($get_access_token,TRUE);
	$access_token = $get_access_token['access_token'];
	
//	获取jsapi_ticket
	$url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=$access_token&type=jsapi";
	$get_jsapi_ticket = $this -> https_request($url);
	$get_jsapi_ticket = json_decode($get_jsapi_ticket,TRUE);
	$jsapi_ticket = $get_jsapi_ticket['ticket'];
	
//	config签名
	$jsConfigSign = "jsapi_ticket=".$jsapi_ticket.
				 "&noncestr=".$rand.							//签名的随机串类型必须是字符串
				 "&timestamp=".$time.							//签名的时间戳类型必须是数值
				 "&url=http://www.xxxxxxxxx.com/v/wxhx.html";	//该路径必须和拉起摄像头的页面路径完全一致(否则某些版本的手机js配置项报错)
	$jsConfigSign = sha1($jsConfigSign);
	
	$data['appid'] = $wechat['appid'];		//appid
	$data['timestamp'] = $time;				//生成签名的时间戳
	$data['nonceStr'] = $rand;				//生成签名的随机串
	$data['signature'] = $jsConfigSign;		//签名
	$data['jsapi'] = 'scanQRCode';			//jsApi
	
	echo json_encode($data,JSON_UNESCAPED_UNICODE);die;
}

QQ截图20170705100511.png

一开始只有华为荣耀8的手机才能成功拉起微信扫一扫,其他手机全部报错,都快把微信开发者文档泛滥了,也没找到原因,后来尝试了下把参数的类型都定义死了,居然所有手机都可以拉起扫一扫了。。。在此爬坑一下午,默默的鄙视下微信开发者文档的小编,文档无处不是坑。。


二:在页面js配置config配置项

<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js" type="text/javascript"></script>	//微信js-sdk依赖文件
		<script type="text/javascript">
$.ajax({
	type:"post",
	url:"http://www.xxxxxxx.com/v_api/getWxConfig",
	dataType:'json',
	success:function(datas){
		
		wx.config({
		    debug: false, 		// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
		    appId: datas.appid, 	// 必填,公众号的唯一标识
		    timestamp: datas.timestamp, // 必填,生成签名的时间戳
		    nonceStr: datas.nonceStr, 	// 必填,生成签名的随机串
		    signature: datas.signature,	// 必填,签名,见附录1
		    jsApiList: [datas.jsapi] 	// 必填,需要使用的JS接口列表,所有JS接口列表见附录2
		});
		
		wx.ready(function(){
			wx.scanQRCode({
			    needResult: 1, 		    // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
			    scanType: ["qrCode","barCode"], // 可以指定扫二维码还是一维码,默认二者都有
			    success: function (res) {
				    var result = res.resultStr; // 当needResult 为 1 时,扫码返回的结果
				    if (result!='') {
//				    	取到扫一扫返回结果后,在此编写后续的逻辑代码
				    }
				}
			});
		})
		
	}
});

QQ截图20170705101635.png

微信图片_20170705102117_meitu_1.jpg

微信扫一扫至此完结!耗时一天,再次默默的鄙视下微信开发者文档的小编。。。


点击下方打赏一个呗~

PHP调用barcodegen插件生成条形码

最近在为公司做一个扫码支付,需要用到条形码的相关功能,下面就先介绍如何生成条形码:


1、如何生成条形码?

复制下面的百度网盘链接,下载barcodegen插件

链接: https://pan.baidu.com/s/1o8nyfNG 

密码: r9c7


2、将下载出来的压缩包解压,并且放到ThinkPHP\Library\Vendor\barcodegen

2.1文件结构:

QQ截图20170625141505.png


2.2具体解析

(1)class文件夹是已封装好生成条形码的类,只需要调用即可。

(2)index.PHP是一个可选择条件生成条形码的功能,是主程序的入口,而html文件夹是提供的被引用的代码,code39.php指的是指向默认的编码格式。


3、在框架内调用插件

/**
	 * 生成付款条形码
	 */
	public function vip_get_barcode(){

		$file_dir = 'Uploads/Bar/'.date("Y-m-d",time());	//文件路径
        if(!file_exists($file_dir)) {					//判断文件是否存在
            mkdir($file_dir);						//不存在则生成
        }
		$imgUrl = $file_dir . '/' . time() . '.png';		//图片路径
        vendor('barcodegen.class.BCGcode128');				//载入依赖包
        vendor('barcodegen.class.BCGDrawing');
        vendor('barcodegen.class.BCGColor');
        $color_white = new \BCGColor(255, 255, 255);			//定义颜色
        $drawing = new \BCGDrawing('', $color_white);			//赋值颜色
        $code = new \BCGcode128();
        $font = new \BCGFontFile('Public/font/Arial.ttf', -1000); 	//字体大小
	$code->setFont($font);						//文字大小
	$code->setThickness(30);					//条码厚度
        $code->parse('123465789012345678');				//条形码内容
        $drawing->setBarcode($code);
        $rs = $drawing->setFilename($imgUrl);				//存放路径
        $drawing->draw();						//渲染图片
        $drawing->finish($drawing::IMG_FORMAT_PNG);			//生成图片
		
		$out_arr['code'] = '000000';
		$out_arr['url'] = $imgUrl;
		echo json_encode($out_arr,JSON_UNESCAPED_UNICODE);die;
	}

QQ截图20170625141820.png


4、将Ajax接到的路径输出到<img>标签的src里

QQ截图20170625142023.png

QQ截图20170625142342.png

爬坑两天,生成条形码业务逻辑完结。。。


点击下方打赏一个呗~


【微信支付】大道至简 抛弃SDK,纯原生开发之微信统一下单

前言:微信公众号的回调绑定了a服务器,我需要在b服务器做微信登录及支付,然后开启了无限爬坑模式…


由于代码和注释有点多,就直接上截图了


1.png

2.png

3.png

至此,微信统一下单告一段落,接下来是拉起微信H5输入支付密码的页面了。。。当时在这爬了整整1天的坑


4.png


支付完成之后,微信回8连发调用上面定义的回调页面,具体可点击下面的链接查看

【微信支付】大道至简 抛弃SDK,纯原生开发之微信通知


点击下方打赏一个呗~

PHP fputcsv()高效导出大数据量Excel(CSV)

前端时间公司部门提出后台要增加数据导出,以方便他们统计业务数据,很多人使用的PHPExcel插件,这个插件能很方便快捷的达到导入导出的功能。。

有需要PHPExcel导出的可以点击下面的链接

ThinkPHP3.2.3接入PHPExcel1.8.0控件导出Excel报表文件处理方法


现在问题来了。。虽然Excel表格最高支持107W行,但是只要数据达到2W-3W的量,PHPExcel就显得有点无力了,经常卡死或者内存溢出,这种大数据量的导出如果换成PHP自带函数 fputcsv() 来实现的话,效率能有很大的提升

set_time_limit(0);
ini_set("memory_limit", "1024M");

//		导出到本地
header ( "Content-type:application/vnd.ms-excel" );
header ( "Content-Disposition:filename=".$sname."商品销售报表.csv" );
header ('Cache-Control: max-age=0');

$fp = fopen('php://output', 'a');					//打开PHP文件句柄,php://output 表示直接输出到浏览器

$head = ['商品名称','商品条码','商品分类','现有库存','销售数量','商品总价','实收金额'];		//定义标题

foreach ($head as $i => $v) {
    $head[$i] = iconv('utf-8', 'GB18030', $v);		//将中文标题转换编码,否则乱码
}

fputcsv($fp, $head);								//将标题名称通过fputcsv写到文件句柄

foreach ($data as $k => $v) {						//重组数组
	$rows[$k]['gname'] = $v['gname'];
	$rows[$k]['barcode'] = $v['barcode'];
	$rows[$k]['cname'] = $v['cname'];
	$rows[$k]['kc'] = '1';
	$rows[$k]['sl'] = $v['sl'];
	$rows[$k]['hj'] = round($v['hj'],2);
	$rows[$k]['ss'] = round($v['hj'],2);
}

$limit = 30000;
$num = 0;											//计数器
foreach ( $rows as $v ) {  							//循环数据
	$num++;

	if($num == $limit){
		ob_flush();			//释放内存
		flush();
	}
    $rows = array();
    foreach ( $v as $kk => $vv){
        $rs[$kk] = iconv('utf-8', 'GB18030', $vv);	//转译编码
    }
    fputcsv($fp, $rs);
}

321321.png

最后需要注意的是,无论是使用PHP内置函数还是使用PHPExcel做导出功能,都需要使用href跳转get传参的方式,如果是用Ajax异步传参的话会出现问题!!!


点击下方图标打赏一个呗~