Giter VIP home page Giter VIP logo

ionepub.github.io's Introduction

ionepub.github.io's People

Contributors

ionepub avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

ionepub.github.io's Issues

PHP设置session失效时间

1. PHP设置session失效时间

  • 打开php.ini
  • 找到session.gc_maxlifetime = 1440(默认1440秒,即24分钟)
  • 将值改成需要的值即可

2. PHP设置session在浏览器关闭后仍可用

  • 打开php.ini
  • 找到session.cookie_lifetime = 0(默认为0,表示关闭浏览器就失效)
  • 将值改为大于0的值即可

session在浏览器中始终是以cookie的形式存放的,当某个SESSIONID的cookie不失效时,此session就不失效

不论是以文件形式还是数据库形式存储session,以上设置都是有效的

如果session一直不起作用,检查一下session的保存目录session.save_path是否可写

日行一记

Ubuntu16.04安装php的curl组件

安装

如果不确定是7.0还是7.1版本,先查看版本信息

sudo apt-cache search php7

安装:

sudo apt-get install php7.0-curl

修改配置

安装完成之后,编辑/etc/php/7.0/apache2/php.ini文件:

sudo gedit /etc/php/7.0/apache2/php.ini

查找 extension=php_curl.dll,将这行代码前的;号删掉,保存文件

重启apache服务

sudo service apache2 reload

我的HTML入门书(一):前言

html

这本书服务的对象是程序小白,本着宁可错杀一千,也不放过一个的原则,我想尽可能详细的讲解各个知识点。

本人水平有限,如果有错的地方,还请指正,谢谢。

算算时间,距离刚开始学习网页编程已经有六年了。六年时间,说长不长,说短不短。

从最基础的HTML、CSS、Javascript到ASP、PHP,一步步走来,也是不容易的。

只能说,如果你对编程这个东西没有兴趣,最好是放弃。

互联网的发展速度太快了,连带着编程语言也一样。如果没有兴趣做支撑,是很难跟上它们的脚步的。

比如Nodejs,它不能算作一门新的编程语言,因为它是基于javascript的,只能算作是一个javascript的运行环境,但是从2009年开始,因为它的出现,使得javascript这门原本只能在客户端执行、用于增强页面动态效果的语言变成了一门可以用在服务器端运行网站、在Arduino上运行机器人程序的语言。也因为Nodejs的出现,让javascript的高级语法逐渐普遍起来。

说回HTML网页编程,对于一个新手,要怎么开始学习编程?

我认为有以下几点。

一、拿起一本入门书

对于什么都不会的新手,我其实不太建议直接看视频教程学习。

首先,视频教程是一些老师或者资历较深的工程师说给你听,同时做给你看的教程,它最大的缺点就是慢,需要耗费的时间很长。要知道,视频教程这个东西,往往动不动就是几十分钟,甚至一两个小时,嗯,一节课,虽然近几年的视频教程时间都有所减少,但是相对而言,要么内容减少了,要么课程数增加了。实际上,人说话、写字、敲代码的时间比阅读文字的时间要长几倍到几十倍,所以在最初阶段,看书是最合适的方式。

你可以通过看书,了解整本书的目录和大概内容,能知道自己在看完这本书之后,出什么成果,学到什么东西,这些东西对自己是否真的有用。

不论是谁,都喜欢看到成果。我之所以选择网页编程,也有一部分原因在此,因为我对程序的小小改动,都能在刷新浏览器之后得到一个结果,可能我只是在某个地方加了几个字,整个页面会产生翻天覆地的变化,而这个变化正是我想要的,我离目标又近了一步。

入门书从哪里来?

对于HTML而言,其实完全可以从网络上获取。当然如果你更喜欢纸质书,也可以去图书馆翻一翻。

最出名的恐怕莫过于W3School了吧:
http://www.w3school.com.cn/html/html_intro.asp
http://www.runoob.com/html/html-intro.html

二、看视频教程

你确定是想要走上编程这条不归路?

每一门编程语言,之所以能成为语言,都因为其在某方面或某些方面有深刻的意义,因此,不论是哪一门语言,其包含的内容都可以认为是浩瀚无穷的。

我只想说,编程这个活,很累。

前几年就有一句流行的话:要嫁就嫁程序员,钱多话少死得早。

拿我自己来举例,这几年看过的视频教程估计累计已经几百小时甚至上千小时了,跑过几百次图书馆(虽然每次都看不完,但是对自己知识宽度的拓展还是比较有帮助的(⊙.⊙))

如果你确定你对编程有着浓厚的兴趣,那么请看一些视频教程吧。

我在六年前开始学习网页编程的时候,就是从视频教程开始的。上面也说到了,视频教程最大的优点就是:详细。

网页编程我比较推荐的就李炎恢老师的视频教程,我当时就是看的李老师的视频入的门,很多人吐槽这位老师的普通话和英文发音,但是全国上下,有几个程序员、工程师英文发音杠杠的?我认为能听懂,自己的英文不会被老师带跑就行。在其他方面(主要方面),李老师的教程还是非常详细,非常值得看的。而且,他的视频绝大多数是免费的,其他部分即使现在收费,以后也会是免费的。

如果你想学习Android、java开发,那么建议你看李兴华老师的视频教程和书,学习完会有不少收获。

在我学习李炎恢老师的视频的时候,**的慕课网站不像现在这么发达,那个时候都是用迅雷下载压缩包,解压出里面的视频再看。(即使这样也没有阻挡我的步伐!

比较推荐的视频教程网站(可以在线看):

如果你的英文不错,那可以试试这个网站:

在学习视频教程的同时,一定要记笔记,用手。所谓“好记性不如烂笔头”说的就是这个。(ง •̀_•́)ง

这里附上一张今天在Lofter上看到的笔记,围观一下别人是怎么把笔记做的高大上的。

note.jpg

三、边学边练,多多动手

在学习的过程中,一定要多练习,最好的办法就是跟着视频教程一步一步敲代码。跟不上怎么办?暂停视频就好了(ノ=Д=)ノ┻━┻

在练习的过程中,不仅对所学知识加深了印象,而且知识大部分是相通的,通常你能在学习完一节或几节教程之后,活学活用,解锁出新姿势。

最好的练习莫过于模仿知名网站了,虽然难度较大,但是成果也是不错的。不过如果你基础不够好,还是建议先把基础打牢,不用急于一时。( ̄^ ̄)

在我的HTML入门书里,我会用一个简单的博客实例,一步步地讲解HTML的基础知识。

四、多阅读外文资料

前两天,听一个朋友说了一个故事,这里分享给大家。

故事的主人公是一个测试工程师,暂且称他为A。

他的英文很棒,可以很流畅地阅读英文资料,跟中文水平有的一拼。

有一次,朋友在工作上碰到了难题,一直找不出bug在哪儿,找同事帮忙,结果也没找出问题。A这个时候刚从会议室出来,从朋友身边经过,看到出问题了,瞄了一眼,问道:“是不是出bug了?试看一下XX功能YY模块有没有ZZ问题?你可以尝试用CC方法替换BB方法。”结果朋友检查了一下,果然是这个问题。一个很难找到的bug就这样被他轻轻松松地被找到、被解决。

你可能会问,A怎么这么厉害?而且这跟英文好像没有关系?

事实上,朋友跟A后面聊天的过程中了解到,原来这个问题他在一年前碰到过,当时他也是跟其他国家的工程师探讨了一阵子才找到解决方案。

朋友跟我说,A经常在公司的微信群里发一些不错的文章,虽然大部分是中文的,但是发送时间一般在早上6点-7点之间。勤奋、刻苦是成功人士普遍具备的优秀品质。 (ง •̀_•́)ง

确实,事实是这样的,国外的技术普遍比国内的技术领先1-2年,当然有些是例外的。

我自己就是一个例子。说来有点好笑,我是在文科性质的外语学校的非计算机专业学习的编程和计算机课程……当时学校老师教授的还是ASP这门课程,我也是傻傻的学,毕业之后才知道,ASP这门语言在10年前就被淘汰了 (๑•́ ₃•̀๑)

感谢你能看完这篇文章。

今天是教师节,给你的老师们请安了吗?


日行一记

PHP、HTML、JavaScript中判断IE浏览器

1. PHP判断是否IE浏览器

<?php
    $userAgent = $_SERVER['HTTP_USER_AGENT'];
    if(strstr($userAgent, 'MSIE 6')){
    	// IE6
    }elseif(strstr($userAgent, 'MSIE 7')){
    	// IE7
    }elseif(strstr($userAgent, 'MSIE 8')){
    	// IE8
    }elseif(strstr($userAgent, 'MSIE 9')){
    	// IE9
    }else{
    	// 非IE6/7/8/9
    }
?>

2. HTML判断是否IE浏览器

<!--[if lt IE 10]>  
IE版本小于10(6,7,8,9)
<![endif]-->

3. JavaScript判断是否IE浏览器

<script>
    // 方法1
    var isIE=!!window.ActiveXObject; 
    var isIE6=isIE&&!window.XMLHttpRequest; 
    var isIE8=isIE&&!!document.documentMode; 
    var isIE7=isIE&&!isIE6&&!isIE8; 
    if (isIE){ 
    	if (isIE6){ 
    		alert("ie6"); 
    	}else if (isIE8){ 
    		alert("ie8"); 
    	}else if (isIE7){ 
    		alert("ie7"); 
    	} 
    }

    // 方法2
    if(navigator.appName == "Microsoft Internet Explorer" && navigator.appVersion.match(/6./i)=="6."){ 
    	alert("IE 6"); 
    } 
    else if(navigator.appName == "Microsoft Internet Explorer" && navigator.appVersion.match(/7./i)=="7."){ 
    	alert("IE 7"); 
    } 
    else if(navigator.appName == "Microsoft Internet Explorer" && navigator.appVersion.match(/8./i)=="8."){ 
    	alert("IE 8"); 
    } 
    else if(navigator.appName == "Microsoft Internet Explorer" && navigator.appVersion.match(/9./i)=="9."){ 
    	alert("IE 9"); 
    } 
    
    // 方法3
    if(navigator.userAgent.indexOf("Opera") != -1) { 
    	alert('Opera'); 
    } 
    else if(navigator.userAgent.indexOf("MSIE") != -1) { 
    	alert('Internet Explorer'); 
    } 
    else if(navigator.userAgent.indexOf("Firefox") != -1) { 
    	alert('Firefox'); 
    } 
    else if(navigator.userAgent.indexOf("Netscape") != -1) { 
    	alert('Netscape'); 
    } 
    else if(navigator.userAgent.indexOf("Safari") != -1) { 
    	alert('Safari'); 
    } 
    else{ 
    	alert('无法识别的浏览器。'); 
    }
</script>

4. CSS区分各版本IE

         ———————IE6——   IE7——   IE8——   FF2——   FF3—    Opera9.5 
>property——     Y——     Y——     Y——     N——     N——     N 
.property——     Y——     Y——     Y——     N——     N——     N 
*property——     Y——     Y——     N——     N——     N——     N 
_property——     Y——     N——     N——     N——     N——     N 
body{
    _color: red; /* ie6 */
    *color: blue; /* ie7 */
    >color: green; /* ie8 */
}

日行一记

JavaScript 监听键盘事件

监听ctrl+s事件

document.onkeydown = function(event){
    var e = event || window.event || arguments.callee.caller.arguments[0]; // 兼容目前所有浏览器
    if(e && e.ctrlKey  == true && e.keyCode == 83){
        // ctrl+s 保存
        console.log('ctrl+s');
        return false; // 拦截浏览器快捷键
    }
}

keycode键码列表

keyCode 8 = BackSpace BackSpace
keyCode 9 = Tab Tab
keyCode 12 = Clear
keyCode 13 = Enter
keyCode 16 = Shift_L
keyCode 17 = Control_L
keyCode 18 = Alt_L
keyCode 19 = Pause
keyCode 20 = Caps_Lock
keyCode 27 = Escape Escape
keyCode 32 = space
keyCode 33 = Prior
keyCode 34 = Next
keyCode 35 = End
keyCode 36 = Home
keyCode 37 = Left
keyCode 38 = Up
keyCode 39 = Right
keyCode 40 = Down
keyCode 41 = Select
keyCode 42 = Print
keyCode 43 = Execute
keyCode 45 = Insert
keyCode 46 = Delete
keyCode 47 = Help
keyCode 48 = 0 equal braceright
keyCode 49 = 1 exclam onesuperior
keyCode 50 = 2 quotedbl twosuperior
keyCode 51 = 3 section threesuperior
keyCode 52 = 4 dollar
keyCode 53 = 5 percent
keyCode 54 = 6 ampersand
keyCode 55 = 7 slash braceleft
keyCode 56 = 8 parenleft bracketleft
keyCode 57 = 9 parenright bracketright
keyCode 65 = a A
keyCode 66 = b B
keyCode 67 = c C
keyCode 68 = d D
keyCode 69 = e E EuroSign
keyCode 70 = f F
keyCode 71 = g G
keyCode 72 = h H
keyCode 73 = i I
keyCode 74 = j J
keyCode 75 = k K
keyCode 76 = l L
keyCode 77 = m M mu
keyCode 78 = n N
keyCode 79 = o O
keyCode 80 = p P
keyCode 81 = q Q at
keyCode 82 = r R
keyCode 83 = s S
keyCode 84 = t T
keyCode 85 = u U
keyCode 86 = v V
keyCode 87 = w W
keyCode 88 = x X
keyCode 89 = y Y
keyCode 90 = z Z
keyCode 96 = KP_0 KP_0
keyCode 97 = KP_1 KP_1
keyCode 98 = KP_2 KP_2
keyCode 99 = KP_3 KP_3
keyCode 100 = KP_4 KP_4
keyCode 101 = KP_5 KP_5
keyCode 102 = KP_6 KP_6
keyCode 103 = KP_7 KP_7
keyCode 104 = KP_8 KP_8
keyCode 105 = KP_9 KP_9
keyCode 106 = KP_Multiply KP_Multiply
keyCode 107 = KP_Add KP_Add
keyCode 108 = KP_Separator KP_Separator
keyCode 109 = KP_Subtract KP_Subtract
keyCode 110 = KP_Decimal KP_Decimal
keyCode 111 = KP_Divide KP_Divide
keyCode 112 = F1
keyCode 113 = F2
keyCode 114 = F3
keyCode 115 = F4
keyCode 116 = F5
keyCode 117 = F6
keyCode 118 = F7
keyCode 119 = F8
keyCode 120 = F9
keyCode 121 = F10
keyCode 122 = F11
keyCode 123 = F12
keyCode 124 = F13
keyCode 125 = F14
keyCode 126 = F15
keyCode 127 = F16
keyCode 128 = F17
keyCode 129 = F18
keyCode 130 = F19
keyCode 131 = F20
keyCode 132 = F21
keyCode 133 = F22
keyCode 134 = F23
keyCode 135 = F24
keyCode 136 = Num_Lock
keyCode 137 = Scroll_Lock
keyCode 187 = acute grave
keyCode 188 = comma semicolon
keyCode 189 = minus underscore
keyCode 190 = period colon
keyCode 192 = numbersign apostrophe
keyCode 210 = plusminus hyphen macron
keyCode 211 =
keyCode 212 = copyright registered
keyCode 213 = guillemotleft guillemotright
keyCode 214 = masculine ordfeminine
keyCode 215 = ae AE
keyCode 216 = cent yen
keyCode 217 = questiondown exclamdown
keyCode 218 = onequarter onehalf threequarters
keyCode 220 = less greater bar
keyCode 221 = plus asterisk asciitilde
keyCode 227 = multiply division
keyCode 228 = acircumflex Acircumflex
keyCode 229 = ecircumflex Ecircumflex
keyCode 230 = icircumflex Icircumflex
keyCode 231 = ocircumflex Ocircumflex
keyCode 232 = ucircumflex Ucircumflex
keyCode 233 = ntilde Ntilde
keyCode 234 = yacute Yacute
keyCode 235 = oslash Ooblique
keyCode 236 = aring Aring
keyCode 237 = ccedilla Ccedilla
keyCode 238 = thorn THORN
keyCode 239 = eth ETH
keyCode 240 = diaeresis cedilla currency
keyCode 241 = agrave Agrave atilde Atilde
keyCode 242 = egrave Egrave
keyCode 243 = igrave Igrave
keyCode 244 = ograve Ograve otilde Otilde
keyCode 245 = ugrave Ugrave
keyCode 246 = adiaeresis Adiaeresis
keyCode 247 = ediaeresis Ediaeresis
keyCode 248 = idiaeresis Idiaeresis
keyCode 249 = odiaeresis Odiaeresis
keyCode 250 = udiaeresis Udiaeresis
keyCode 251 = ssharp question backslash
keyCode 252 = asciicircum degree
keyCode 253 = 3 sterling
keyCode 254 = Mode_switch

日行一记

免费无广告视频播放实现

no ads

在网站上(特别是企业类型的网站),有时候会需要放置一些视频,如企业简介等内容,一般而言有两种方法实现:

  1. 将视频文件(.avi,.flv等)上传到自己的网站服务器上,直接用url访问
  2. 将视频文件上传到第三方网站(如优酷、土豆及七牛云存储等),通过第三方网站提供的方式访问

这两种方式各有利弊:

1

如果将视频文件放在自己的服务器上,好处就是可以自己控制,视频的清晰度自己把控;不好之处在于:

  • 首先,视频文件通常比较大,1G以上很正常,会占用较多的服务器空间
  • 第二,访问视频文件是需要流量的,流量是根据文件大小的,所以每次一个视频文件的完整播放,将耗费服务器1G+的流量
  • 第三,如果页面加载视频的代码没有做延迟或者优化,那么你的页面加载时间可能会超过5分钟(需要等待视频文件加载一部分)

2

如果把视频文件放在第三方网站上,视频清晰度就不能自己控制,但是好处是不需要耗费自己网站服务器的流量,也不需要担心因为视频导致页面加载缓慢的问题。

而将视频放在第三方网站上,有一个比较头疼的问题就是,第三方播放器往往会在播放视频时添加一些广告。

这里介绍一个免费无广告视频播放的方法:土豆网。

  1. 注册土豆账号或使用第三方账号登录,用于上传自己的视频到土豆网
  2. 打开需要播放的视频链接,找出其中的视频id编号
  3. 选择iframe或embed方式将视频插入到自定义的位置即可
视频Url地址:http://www.tudou.com/programs/view/AFd5w6ZzYbo/

注意:这个是视频的Url地址 ,不是打开视频之后的分享地址。

观察这个Url地址,可以发现AFd5w6ZzYbo就是视频的id编号,将其代入以下代码即可。

跨平台播放(iframe)

跨平台播放模式下,能兼容手机端播放

<iframe
src="http://www.tudou.com/programs/view/html5embed.action?code=AFd5w6ZzYbo&autoPlay=false&playType=H5"
width="500px"
height="300px"
frameborder="0"
scrolling="no"
></iframe>
参数 是否必选 说明 示例
code 视频的id编号 AFd5w6ZzYbo
autoPlay 是否自动播放(针对PC),默认不自动播放 固定值,true:自动,false:手动
playType 强制HTML5播放器播放,如果不填则默认为IOS及安卓设备为HTML5播放器播放,其他设备均为FLASH播放器播放 固定值,H5:强制HTML5播放器播放

FLASH播放(需要安装flash插件)

flash播放模式下,需要安装flash插件,一般PC浏览器默认已安装,但是手机浏览器默认未安装。

<embed
src="http://www.tudou.com/v/AFd5w6ZzYbo&autoPlay=false/v.swf"
type="application/x-shockwave-flash"
allowscriptaccess="always"
allowfullscreen="true"
wmode="opaque"
width="480"
height="400"
id="tudouPlayer">
</embed>
参数 是否必选 说明 示例
code 视频的id编号 AFd5w6ZzYbo
autoPlay true:播放器加载后会自动播放、false(默认):需要点击才会自动播放 固定值,true:自动,false:手动

说明:有些视频无法通过此方式播放,一般为土豆网推荐的视频或者有版权的视频。

日行一记

Win10系统更新之后运行自定义的本地站点,显示EOF的解决方法

当前环境

  • 系统:Windows10
  • PHP:PHPStudy集成环境
  • 站点:vhost自定义域名(lc.shop)

问题

Windows自动更新之后,访问自定义的域名任意页面,只显示 EOF,没有任何其他有效数据。

解决方法

修改自定义域名,修改hosts配置,重新启动apache和mysql。

由这个解决方法可以想到,应该是系统更新导致hosts和本地域名指向的问题。

参考

http://www.cnblogs.com/zengguowang/p/6727494.html

日行一记

Debian Server下报错Call to undefined function getallheaders()

问题描述

shell脚本内容:

#!/bin/bash
token="abc123456"
sh -c "cd /var/www/dist/; /usr/bin/php cron.php /Api/Cron/index/token/$token;"

CronController.class.php内容:

<?php
namespace Api\Controller;
use Think\Controller;

/**
 * 定时任务控制器
 * @author lan
 */
class CronController extends Controller{
	/**
	 * 检查权限
	 * token === "abc123456" && CRON_AVAILABLE === true
	 * @return bool
	 */
	private function authCheck(){
		return (isset($_GET['token']) && $_GET['token'] === "abc123456" && CRON_AVAILABLE === true) ? true : false;
	}

	/**
	 * 主方法
	 */
	public function index(){
		if(!$this->authCheck()){
			exit("Access Deny");
		}

		set_time_limit(0);
		
		// do crontab 
		$headers = getallheaders();
		
		
	}
	
}

在执行shell脚本时,报错:

root@debian:/var/www/shell# /bin/sh ./test.sh

PHP Fatal error:  Call to undefined function Api\Controller\getallheaders() in /var/www/dist/Application/Api/Controller/CronController.class.php on line 51
Call to undefined function Api\Controller\getallheaders()
FILE: /var/www/dist/Application/Api/Controller/CronController.class.php(51)

报错原因

  • 1.使用FastCGI模式,但PHP版本不是5.4
  • 2.服务器不是Apache

https://www.drupal.org/node/2200567)

此处可能是第三个原因(不是用Url直接访问,而是用php命令调用)

解决方法

Common/functions.php中添加以下代码(当函数不存在时,自定义一个)

<?php
    if(!function_exists('getallheaders')){
		function getallheaders(){
			foreach($_SERVER as $h=>$v){
				if(ereg('HTTP_(.+)',$h,$hp))
				$headers[$hp[1]]=$v;
			}
			return $headers;
		}
	}
?>

日行一记

我的HTML入门书(二):网站概述

这本书服务的对象是程序小白,本着宁可错杀一千,也不放过一个的原则,我想尽可能详细的讲解各个知识点。

本人水平有限,如果有错的地方,还请指正,谢谢。

上一篇: 我的HTML入门书(一):前言


本章节内容有:

  1. 什么是互联网?
  2. 什么是网站?

一、什么是互联网?

网的概念

先说说什么是网。

想象一下生活中的网,比如蜘蛛网、渔网,它们是什么样子的?

蜘蛛网

渔网

网都是由一个个节点连接而成,互联网也是如此。

互联网也是由无数个节点连接起来的,这些节点就是网站。

域名和IP

实际上最开始的时候,人们要从一台电脑访问其他电脑,是通过IP地址访问的,类似这样: http://192.168.0.60,但是这种地址总是难以记忆,往往会出现错误,于是就有了域名。

域名其实就是为了方便记忆而诞生的。我们常说的域名解析这个过程其实就是将某一个域名跟指定的IP地址进行绑定,这种绑定关系是单向的,某一个域名/子域名只能与一个IP地址绑定,但是一个IP地址可以同时绑定多个域名/子域名。

这样就可以通过域名访问了。域名往往是有意义的,方便记忆的。

TIP:可以通过访问:http://s.tool.chinaz.com/same 查询属于同IP的域名网站有哪些。

什么是网站?

服务器和网站

服务器这个概念也是很常见的。其实服务器就是一台电脑,只是这个电脑和你的个人电脑有些区别。其中比较重要的区别就是:服务器拥有一个或多个固定IP地址。正是这些固定的IP地址,才能让互联网中其他电脑或设备可以访问它,准确地说是访问放在它上面的网站。

明白了这一点之后,也就不难猜到,所谓的网站就是放在服务器这台电脑上的某一个文件夹,而这个文件夹里的各个文件,就是这个网站的一个个网页。

比如,现在有一个A网站,它的网址是www.a.com, 在这个网站上有一个网页,地址是www.a.com/blog/1.html, 那么我们不难猜到,这个A网站,它的文件夹里包含了一个叫blog的文件夹,而这个blog文件夹里,有一个叫1.html的文件。

网页

从上面可以知道,所谓的网页就是放在服务器上的一个个文件,跟在Windows上写文本文档、word文档是一个道理。

那如何判断一个文件是不是网页?

很简单,就是看文件的后缀。凡是.html/.htm/.xhtml这些后缀的文件,在浏览器中都会当做网页文件。

可以做一个小试验:将下面的代码内容添加到一个文本中,尝试将这个文本文件的后缀改为.html和.txt,然后将文件拖到浏览器中,对比其中的区别。

<h1>This is a title.</h1>

如果是.html后缀,那么将显示一个加粗的大标题:This is a title.而如果是.txt后缀,将原样输出,不会有任何变化。

编辑器

现在应该清楚一件事,一个网页就是一个文件,这个文件像文本文档一样编辑。你可能注意到了,在上面的示例代码有一些是高亮状态的,它有助于你区分、辨别特殊的代码,这一点很有用。

像Windows的文本编辑器是没有高亮功能的,在读文本文档时会相对比较吃力。目前网络上有非常多优秀的代码编辑器,专门用来敲代码的,安利程序员。

比较有名的有:

  • notepad++
  • editplus
  • Adobe Dreamweaver
  • sublime text
  • UltraEdit

上面这些编辑器可以任选一个作为自己常用的编辑器。本人比较推荐的是notepad++和editplus。

日行一记

JS实现form表单文件上传组件预览效果

说明

利用HTML5的FileReader接口,实时读取待上传图片内容(base64)

示例

HTML:

<input type="file" name="file" onchange="showPreview(this)" />
<img id="portrait" src="" width="70" height="75">

JS:

function showPreview(source) {
	var file = source.files[0];
	if(window.FileReader) {
		var fr = new FileReader();
		fr.onloadend = function(e) {
			document.getElementById("portrait").src = e.target.result;
		};
		fr.readAsDataURL(file);
	}
}

浏览器兼容性

使用了HTML5 API,IE9及以下版本不支持

浏览器兼容性

#IE9方案(未测试)

API文档

https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader

参考

http://blog.csdn.net/zk437092645/article/details/8745647

日行一记

ThinkPHP 数据库表大写命名的问题

MySQL数据库表表名默认为小写

在MySQL(Windows)中,数据表表名默认是小写的,即使在导入的sql文件中写的数据表表名是大写的,也会自动转换为小写。

解决方法:

修改my.ini配置:

# my.ini文件
# 添加下面这行代码,如果已经有了此配置,将值改为0
lower_case_table_names=0

修改完成之后,重启MySQL服务

net stop mysql
net start mysql

ThinkPHP中M方法不能直接实例化有大写字母的数据表

默认情况下,ThinkPHP中可以使用M方法实例化表模型,但是如果数据表名中有大写字母,则会被转换成下划线加小写。

$m = M('config_action_actionType');
# 上面的数据实际上调取的数据表名为:config_action_action_type

具体原因

参考:http://www.9958.pw/post/thinkphp_table_name

解决方法(一)

  1. 使用table方法替代
# 用下面的代码实际调取的表名为:config_action_actionType,不会被转换
$m = M()->table('config_action_actionType');
  1. 在模型定义里面加上trueTableName覆盖$this->trueTableName
protected $trueTableName = 'myTableName';

使用table方法引发的问题

获取主键getPk()方法失效

$Mod = M()->table('tableName');
$pk = $Mod->getPk(); // 永远返回 'id'

获取单条数据find()方法失效

$id = 1;
$Mod = M()->table('tableName');
$res = $Mod->find($id); // 可能返回的不是对应$id的数据

解决方法(二)

修改ThinkPHP框架源代码

# ThinkPHP/Common/functions.php line:482

/**
 * 字符串命名风格转换
 * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
 * @param string $name 字符串
 * @param integer $type 转换类型
 * @return string
 */
function parse_name($name, $type=0) {
    if ($type) {
        return ucfirst(preg_replace_callback('/_([a-zA-Z])/', function($match){return strtoupper($match[1]);}, $name));
    } else {
        /**
         * 此处会将数据表名中大写字母转换成_小写字母形式
         * @date 2016-10-20
         * @author lan
         */
        # return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
        return trim(  $name, "_"  );
    }
}
# ThinkPHP/Library/Think/Model.class.php line:1478

/**
     * 得到完整的数据表名
     * @access public
     * @return string
     */
    public function getTableName() {
        if(empty($this->trueTableName)) {
            $tableName  = !empty($this->tablePrefix) ? $this->tablePrefix : '';
            if(!empty($this->tableName)) {
                $tableName .= $this->tableName;
            }else{
                $tableName .= parse_name($this->name);
            }
            /**
             * 此处会将数据表名中大写字母转换成小写字母形式
             * @date 2016-10-20
             * @author lan
             */
            # $this->trueTableName    =   strtolower($tableName);
            $this->trueTableName = $tableName;
        }
        return (!empty($this->dbName)?$this->dbName.'.':'').$this->trueTableName;
    }

日行一记

APP接口通信安全策略及OAuth 2.0

通信安全

使用临时access token代替明文密码

就APP接口通信而言,如果客户端保存着用户的密码(明文),且每次请求接口数据都附带明文密码,那对于用户的信息来说是非常不安全的。

通过对目前一些安全策略和各大家推出的开放接口分析来看,大部分都是以临时token令牌的形式获取授权,这种方式相对使用用户名+密码的形式无疑安全了许多。

大概的过程为:

用户名+密码(APPID+APP Secret) --> 获取临时access token令牌 --> 使用token请求接口数据 --> token失效 --> 刷新或重新获取token

在此过程中,只有在第一次获取access token时,才需要输入用户名和密码,而且可以通过refresh token刷新token,客户端就不需要存储用户的明文密码了。

access token的有效期

临时token的有效期一般为2h(7200秒)。

不论是网页操作还是APP操作,一般来说,用户每次的最大停留时间是在7200秒以内的。

refresh token的有效期

refresh token只有一个作用,当token即将过期或已过期时,可以使用refresh token刷新access token。

一般来说,refresh token的有效期较长,从30天到360天不等。

用户中心系统

在调研过程中发现,如果能建立一个独立的用户中心系统,不论是对APP接口通信、OAuth 2.0还是对多系统 单点登录 都有一定的好处。

这个用户中心只做有关用户的事,例如:用户的登录、注册、忘记密码;用户基本信息的存储;access token授权;登录验证、token验证等,不涉及具体的业务逻辑。

其他的系统在需要登录认证时,统一向用户中心发送和验证token,获取授权。

对于一个可持续发展中的系统来说,用户中心系统的搭建是有必要的。

独立的用户中心好处还是不少的:

  • 用户系统独立出来,当其他系统出现安全问题时,可以保证用户资料的安全性
  • 单点登录,实现一处登录,处处登录,避免由跨域引起的安全问题和登录问题
  • 类似接口的概念,让用户相关逻辑跟业务逻辑解耦,更便捷地实现多个业务系统的对接

不过也有一些不便之处

  • 开发难度加大
  • 增加维护成本
  • 跨域问题(如何辨别已授权的业务系统?)

跨域问题

建立独立的用户中心系统,就意味着它将和其他业务系统在域名上可能有跨域问题(一般是不同子域名之间跨域),有几种解决方法。

假设:session使用数据库表存储,使用ThinkPHP框架

#1 修改cookie作用域

在操作cookie时,将cookie的域设置为 .demo.com 而不是当前域。

<?php
	public function read(){
		C('COOKIE_DOMAIN', ".lc.dist2");  // 设置cookie域
		$coo = mycookie("test");
		dump($coo);
	}

	public function write(){
		C('COOKIE_DOMAIN', ".lc.dist2");  // 设置cookie域
		mycookie("test", "hahaha");
	}

#2 修改session的cookie域

如果在ThinkPHP框架下,则找到 /ThinkPHP/Common/functions.php 文件中的session管理函数

在函数开头添加代码:

// 如果配置了session主域名,则设置
if(C('SESSION_MAIN_DOMAIN')) ini_set('session.cookie_domain', C('SESSION_MAIN_DOMAIN'));

修改后的函数部分代码:

function session($name='',$value='') {
    $prefix   =  C('SESSION_PREFIX');

    // 如果配置了session主域名,则设置
    if(C('SESSION_MAIN_DOMAIN')) ini_set('session.cookie_domain', C('SESSION_MAIN_DOMAIN'));

    if(is_array($name)) { // session初始化 在session_start 之前调用
    // 后面的代码省略了...

并在/Application/Common/Conf/config.php配置文件中,添加:

'SESSION_MAIN_DOMAIN'	=>		'mydomain.com'

修改完之后,就可以正常使用了:

<?php
	public function read(){
		$coo = session('test');
		dump($coo);
	}

	public function write(){
		session('test', '123456');
	}

如果不是在ThinkPHP框架下,则尝试在session_start()之前,加入 ini_set('session.cookie_domain', 'domain.com'); ,例如:

<?php
	public function read(){
		ini_set('session.cookie_domain', 'lc.dist2');
		session_start();
		dump($_SESSION['test']);
	}

	public function write(){
		ini_set('session.cookie_domain', 'lc.dist2');
		session_start();
		$_SESSION['test'] = 'aaaaaa';
		dump($_SESSION['test']);
	}

如果不能使用ini_set函数,则尝试在php.ini中设置。

#3 添加header头信息

在APP接口中,通常无法使用cookie和session,此时可以通过在header头部信息中添加一些认证信息,来完成接口鉴权。

curl -X POST \
    -H "X-Bmob-Application-Id: Your Application ID" \
    -H "X-Bmob-REST-API-Key: Your REST API Key" \
    -H "Content-Type: application/json" \
    -d '{"score":1337,"playerName":"Sean Plott","cheatMode":false}' \
    https://api.bmob.cn/1/classes/GameScore

单点登录参考资料

跨域参考资料

使用HTTPS

为了防止被抓包,比较建议使用HTTPS,加强安全性。即使可能被破解,破解的难度也加大了很多。

如果不能全站使用HTTPS,建议至少给“用户中心系统”加HTTPS证书。

另外,在iOS开发中,要求必须使用HTTPS(iOS10+)。

HTTPS证书参考资料

  • TODO:怎么安装HTTPS证书?

OAuth 2.0

关于OAuth 2.0的描述在 理解OAuth 2.0oauth2开放认证协议原理及案例分析 这两篇文章中讲得很清楚。

OAuth 2.0是一个协议,它相对OAuth1版本更安全。

OAuth 2.0是基于浏览器的一个网页授权过程,多用于第三方向资源服务器(如微信、微博等)请求资源。

OAuth2.0中比较典型的、功能最完善、流程最严谨的授权模式是authorization code(授权码模式),微信公众平台的获取网页授权就是用的这种模式,QQ、微博等网页登录同样使用的也是授权码模式。

authorization code 授权流程:

OAuth2授权码模式授权流程

APP接口通信安全策略

对于普通网站来说,当使用浏览器时,可以使用session和cookie等方式来保持用户的登录状态,但是APP不行。APP并不基于浏览器,所以无法存储session和cookie。

尝试一:APPID + APP Secret(使用用户uid和登录密码代替APPID、Secret)

这种方式参考的是微信公众平台开发中的 获取Access token

通过向用户中心系统发送已分配的APPID和APP Secret,用户中心验证后将临时access token返回给APP。

对于APPID和APP Secret的分配,有几种方式:

  • 为每个用户分配APPID和Secret
  • 为每台设备分配APPID和Secret
  • 使用用户uid和登录密码代替APPID、Secret
  • 仅为每个业务系统分配APPID和Secret

为每台设备分配APPID这种方式相对于另外两种来说,更加繁琐,也会带来更多的问题,比如:同一设备不同用户登录时APPID相同;同一用户不同设备登录时可能被强制退出(access token被覆盖)等。

对于自用的,不需要公开开放的系统来说,比较好的处理方式是 使用用户uid和密码代替APPID

流程:

APP接口通信流程

流程图中的userNo即加密过的uid,对用户唯一。

access token的有效期较短,所以安全性也可以得到保障。

在这种方式下,当被抓包时,仍然有可能被拦截获取到token数据。

尝试二:userNo + Timestamp + nonce_str + sign

这种方式参考的是微信支付、银生支付等网关支付接口方式,不使用token

使用Timestamp(时间戳)可以在一定程度上保证屏蔽请求数据相同的请求。

使用nonce_str(随机字符串)和sign(签名)更保证了接口安全性,即使发送的数据被拦截,也较难破解。

使用上面流程中取得的userNo,在每次请求接口时,同时传递timestamp等参数,在服务器端对这些参数进行过滤,可以设置timestamp如果超时(如超过5分钟)则失效等。

参数示例(javascript):

var userNo = "c4ca4238a0b923820dcc509a6f75849b"; // 从登陆接口返回的数据
var time = new Date().getTime();
var nonce_str = "5K8264ILTKCH16CQ2502SI8ZNMTM67VS";
var sign = time + md5(nonce_str + userNo); // 签名生成算法可以自定义

var url = BaseUrl + ApiUrl 
		+ "?userNo="+userNo
		+ "&nonce_str="+nonce_str
		+ "&sign="+sign;

具体的签名算法比较灵活,主要就是不让窃取的人容易猜到。

比如:

#1 不明传时间戳,但是在sign中可以截取出时间戳

var time = new Date().getTime();
var sign = time + md5(nonce_str + userNo);
// or
var sign = time + md5(nonce_str + time + userNo);

var url = BaseUrl + ApiUrl 
		+ "?userNo="+userNo
		+ "&nonce_str="+nonce_str
		+ "&sign="+sign;

后端可以截取sign的前13位数字,检查时间是否跟当前时间相近或相同;而后用相同的算法对比sign的值是否正确。

如果认为这样的时间戳格式很容易被猜出来,可以对时间戳或者sign做一些简单的封装,比如:

var time = new Date().getTime();
time = time.substr(6,7); // 截取时间戳的后七位
// 也对sign中md5部分进行截取,此时sign的总长度为32
var sign = time + md5(nonce_str + userNo).substr(0, 25);

甚至可以在sign中指定字符截取长度

var time = new Date().getTime();
// 生成1-9之间的随机数
var length = parseInt(Math.random() * 8) + 1;
time = time.substr(Math.abs(length));
var sign = time + md5(nonce_str + userNo) + length;

#2 在登录接口返回Secret key,使用key加密

var key = "123456";
var time = new Date().getTime();
var nonce_str = "5K8264ILTKCH16CQ2502SI8ZNMTM67VS";
var sign = md5(key + nonce_str + time + userNo);

var url = BaseUrl + ApiUrl 
		+ "?userNo="+userNo
		+ "&time="+time
		+ "&nonce_str="+nonce_str
		+ "&sign="+sign;

如果使用Secret key,要保证key在客户端不会被泄露

尝试三:APPID + APP Secret(仅为每个业务系统分配APPID和Secret)

类似第三方向微信、QQ等申请授权一样,将用户中心系统作为资源服务器,将每个业务系统作为单独的第三方系统,为每个业务系统分配APPID和APP Secret。

业务系统在服务器端存储APPID,并通过服务器向用户中心系统发起认证请求,获得access token和refresh token,再由服务器向各个接口做请求。

实质上,跟第一种尝试的流程大致是相同的,区别在于:

  • 由业务服务器端存储APPID和Secret,减少重新登录可能的同时,Secret的安全性得到保障
  • 业务服务器和用户中心系统之间的通信基本上都是通过服务器,不经由客户端
  • 对网页授权可能更适合一些

总结

可以尝试将几种方式混合起来,配合使用,更建议的是,对不同的接口做划分,安全性要求高的接口使用混合方式或者第二种方式,安全性要求不高的接口可以采用其他单个方式。

对于sign(签名)的生成算法,应尽量避免外泄。各个token的生成算法也是如此。

日行一记

Ubuntu16.04 外放有声音但是耳机没有声音的解决方法

步骤

先尝试 sudo alsactl restore,看有没有声音,如果没有,打开pavucontrol修改配置,还不行,打开alsamixer查看声音是不是没了。
最后,打开系统设置->音量设置,选择模拟耳机,调整左右声道。

参考

http://blog.csdn.net/wangjinbao5566/article/details/52689889
http://jingyan.baidu.com/article/6525d4b15e6174ac7d2e94f3.html
http://www.linuxidc.com/Linux/2014-07/104825.htm
http://www.cnblogs.com/qi09/archive/2012/04/09/2438975.html##
http://www.cnblogs.com/kingstrong/p/5960466.html##
http://blog.csdn.net/jamesjiangchn/article/details/6648401##

用Casper.js做自动化测试(起步)

简介

casperjs是一个基于phantomjs和nodejs的无界面浏览器,在phantomjs基础上增加了一些框架方法和测试方法等。

安装

sudo npm install -g casperjs

其他安装方法:

http://docs.casperjs.org/en/latest/installation.html

官方文档

http://docs.casperjs.org/en/latest

基本用法

basic.js

var casper = require('casper').create();

casper.start('http://d.com:12870/', function() {
    this.echo(this.getTitle());
});

casper.thenOpen('https://www.baidu.com/', function() {
    this.echo(this.getTitle());
});

casper.run();
>casperjs basic.js

phantomjs基本用法

http://www.tuicool.com/articles/nieEVv

options配置

var casper = require('casper').create({
    verbose: true,
    viewportSize: {    
        width: 1920,    
        height: 1080    
    },
    onAlert: function (self, msg) {//alert的回调函数    
        console.log("onAlert===========================msg:" + msg);    
    },
    onError: function (self, msg, trace) {
	console.log("onError===========================msg:" + msg);
    },
    
});

// next

设置头信息

casper.start之后,便可以使用phantomjs的page对象:

casper.start('http://lc.shop/Admin/admin/login');
casper.page.customHeaders = {
    "Casper-Test": "hello"
};

完整的测试代码1

var casper = require('casper').create({
    verbose: true,
    viewportSize: {    
        width: 1920,    
        height: 1080    
    },
    onAlert: function (self, msg) {//alert的回调函数    
        console.log("onAlert===========================msg:" + msg);    
    },
    onError: function (self, msg, trace) {
	console.log("onError===========================msg:" + msg);
    },
    
});

casper.start('http://lc.shop/Admin/admin/login');

casper.page.customHeaders = {
    "Casper-Test-for-Ytshop": "hello"
};

/*casper.thenEvaluate(function(){
    var testInput = document.createElement('input');
    testInput.setAttribute('type', 'hidden');
    testInput.setAttribute('name', 'casperTest');
    document.querySelector('div.login-box-body').appendChild(testInput);
});*/

casper.then(function(){

    this.fill('div.login-box-body', { username: 'admin', password: '123456', vertify: '1235' }, false);
    this.clickLabel('立即登录 ', 'button');
    this.wait(500, function(){
        casper.capture('2.2.png', {
	    top: 0,left: 0,width: 1920,height: 1080
        });
    });
    
});

casper.run();

利用phantomjs提交post表单

var page = require('webpage').create();

var postBody = 'username=admin&password=admin888&vertify=1234&casperRequest=YtShop';

page.open("http://lc.shop/Admin/Admin/login", 'POST', postBody, function(status){
    //console.log('Post Login Status: ' + status);
    //console.log(page.content);

    if(status == 'success'){
        try {
            var response = page.content.replace("<html><head></head><body>", "");
            response = response.replace("</body></html>", "");

            var responseData = JSON.parse(response);
            console.log(responseData.msg);

        } catch (e) {
            console.log('Post login page parse response failed.');
        } finally {
            phantom.exit();
        }
    }else{
        console.log('Post Login Status: ' + status);
        phantom.exit();
    }

});

实例:测试时,先从服务器端获取token,再利用token做登录操作

此实例用普通模式运行

token可用于数据统计和绕过验证码等功能。

adminLogin.js

/**
 * 后台登录脚本(获取token之后用token绕过图形验证码)
 * casperjs adminLogin.js
 * @author lan
 */
var baseUrl = 'http://lc.shop',
	debugMode = true;

var page = require('webpage').create();

var casper = require('casper').create({
    viewportSize: {
        width: 1920,
        height: 1080
    },
    onAlert: function (self, msg) {//alert的回调函数
        console.log("onAlert===========================msg:" + msg);
    },
    onError: function (self, msg, trace) {
		console.log("onError===========================msg:" + msg);
    }
});

function getLoginToken(callback){
	var postBody = 'casperRequest=YtShop';

	page.open(baseUrl + "/Home/Api/getCasperToken", 'POST', postBody, function(status){
		debugMode && console.log('getCasperToken Status: ' + status);
		debugMode && console.log('getCasperToken response data: '+page.content);

		if(status == 'success'){
			try {
				var response = page.content.replace("<html><head></head><body>", "");
				response = response.replace("</body></html>", "");

				var responseData = JSON.parse(response);
				debugMode && console.log('requestToken: '+responseData.token);

				if(typeof callback == 'function'){
					callback(responseData.token);
				}else{
					console.log('callback is not a function.');
					phantom.exit();
				}

			} catch (e) {
				console.log('Post login page parse response failed.');
				phantom.exit();
			} finally {

			}
		}else{
			console.log('Post Login Status: ' + status);
			phantom.exit();
		}

	});
}

function adminLogin(requestToken){
	debugMode && console.log('adminLogin start');

	if(!requestToken){
		console.log('token required on admin login');
		phantom.exit();
	}

	casper.start(baseUrl+'/Admin/admin/login');
	casper.page.customHeaders = {
	    "Casper-Token-for-Ytshop": requestToken
	};

	casper.then(function(){

	    this.fill('div.login-box-body', { username: 'admin', password: 'admin888', vertify: '1234' }, false);
	    this.clickLabel('立即登录 ', 'button');
	    this.wait(500, function(){
			// 只要帐号密码是对的,一般都能登录成功
			debugMode && console.log('adminLogin success');
	        casper.capture('2.2.png', {
		    	top: 0,left: 0,width: 1920,height: 1080
	        });

	    });

	});

	casper.then(function(){
		debugMode && console.log('next step here');
	})

	casper.run();

}

getLoginToken( adminLogin );

/Home/ApiController.class.php

<?php
    /**
     * for casper.js test
     * 用于casper测试时获取token,绕过图形验证码
     * @author lan
     */
     public function getCasperToken(){
         if( I('post.casperRequest')=='YtShop' ){

             // 如果session还有效,则直接用,不刷新
             if(session('?casperToken')){
                 $token = session('casperToken');
                 exit( json_encode( array('token'=>$token) ) );
             }

             $token = md5(time().mt_rand(666666,999999));
             session('casperToken', $token);
             exit( json_encode( array('token'=>$token) ) );
         }else{
             exit();
         }
     }

用测试框架运行上面的代码

测试模式下不能有 var casper=require('casper').create()代码,否则会报错:

Fatal: you can't override the preconfigured casper instance in a test environment.

adminLoginTest.js

/**
 * 后台登录脚本(获取token之后用token绕过图形验证码)
 * 此脚本用于测试 casperjs test adminLoginTest.js
 * @author lan
 */
var baseUrl = 'http://lc.shop',
	debugMode = true;

var page = require('webpage').create();

// 在test模式下不能有这个
/*var casper = require('casper').create({
    viewportSize: {
        width: 1920,
        height: 1080
    },
    onAlert: function (self, msg) {//alert的回调函数
        console.log("onAlert===========================msg:" + msg);
    },
    onError: function (self, msg, trace) {
		console.log("onError===========================msg:" + msg);
    }
});*/

function getLoginToken(callback, test){
	var postBody = 'casperRequest=YtShop';

	page.open(baseUrl + "/Home/Api/getCasperToken", 'POST', postBody, function(status){
		debugMode && console.log('getCasperToken Status: ' + status);
		debugMode && console.log('getCasperToken response data: '+page.content);

		if(status == 'success'){
			try {
				var response = page.content.replace("<html><head></head><body>", "");
				response = response.replace("</body></html>", "");

				var responseData = JSON.parse(response);
				debugMode && console.log('requestToken: '+responseData.token);

				if(typeof callback == 'function'){
					callback(responseData.token, test);
				}else{
					console.log('callback is not a function.');
					phantom.exit();
				}

			} catch (e) {
				console.log('Post login page parse response failed.');
				phantom.exit();
			} finally {

			}
		}else{
			console.log('Post Login Status: ' + status);
			phantom.exit();
		}

	});
}

function adminLogin(requestToken, test){
	debugMode && console.log('adminLogin start');

	if(!requestToken){
		console.log('token required on admin login');
		phantom.exit();
	}

	casper.start(baseUrl+'/Admin/admin/login');
	casper.page.customHeaders = {
	    "Casper-Token-for-Ytshop": requestToken
	};

	casper.then(function(){

	    this.fill('div.login-box-body', { username: 'admin', password: 'admin888', vertify: '1234' }, false);
	    this.clickLabel('立即登录 ', 'button');
	    this.wait(1000, function(){
			// 只要帐号密码是对的,一般都能登录成功
			debugMode && console.log('adminLogin success');
	        casper.capture('2.2.png', {
		    	top: 0,left: 0,width: 1920,height: 1080
	        });

	    });

	});

	casper.then(function(){
		// coding here
		debugMode && console.log('next step here');
		test.assertExists('span.logo-lg', "logo found");
	})

	casper.run(function() {
        test.done();
    });

}

function testStart(){
	casper.test.begin('Test test', 1, function suite(test){
		debugMode && console.log('test start');
		getLoginToken(adminLogin, test)
	});
}

testStart();

小tip

由于casperjs是基于phantomjs的,所以即使js文件里的代码全都是phantomjs的,也依然可以用casperjs xx.js正确执行。

参考

日行一记

ThinkPHP中前置操作和后置操作的使用

ThinkPHP版本:3.2.3

  1. 只能用在controller中
  2. 只对Url访问有效,类中直接调用方法或用A方法调用时无效
  3. 除非直接调用前置或后置操作,否则不能在前置、后置方法中传递参数
class demo{
    //前置操作,在index方法执行之前执行,index可以为其他任意名称
    public function _before_index(){

    }
    public function index(){

    }
    //后置操作,在index方法之后执行
    //如果index方法中有exit或者die等,则不执行后置操作
    public function _after_index(){

    }
}

日行一记

Linux 命令行中如何翻页?

64-150h2120pjh

有时候在使用命令行查看信息时,会发现一个命令输出的结果多于一屏,导致无法查看更多的内容,这个时候可以使用以下命令实现命令行中的上下翻页功能。

Shift + PageUp
Shift + PageDown

日行一记

使用sprintf函数实现补零

使用方法

sprintf("%0Nd", $number); //N为预期的数字长度,如10表示预期要生成10位的数字

<?php
    $num = 103;
    $length = 10; //共10位,不足时补零
    $num2 = sprintf('%0'. $length .'d', $num);
    // 结果:$num2 = 0000000103
?>

日行一记

字符串中多个参数替换(preg_replace_callback)

场景

数据表 notify 中有两个字段:tpl body,分别保存消息通知的模板和模板填充内容

tpl demo: 尊敬的{{0}},感谢您注册{{1}}平台,您的验证码是{{2}}。

body demo: ["15012341234","易众投","123456"] (键名分别对应0,1,2)

呈现给用户的:

尊敬的15012341234,感谢您注册易众投平台,您的验证码是123456。

解决方案

一、sprintf

思路:先将{{x}}统一替换为%s,再进行替换

<?php
    $tpl = "尊敬的{{0}},感谢您注册{{1}}平台,您的验证码是{{2}}";
    $tpl2 = preg_replace('/{{\d}}/', '%s', $tpl);

    $body = '["15012341234","易众投","123456"]';
    $body_arr = json_decode($body, true);

    $result = sprintf($tpl2, $body_arr[0], $body_arr[1], $body_arr[2]);
    var_dump($result);
?>

二、preg_replace_callback(执行一个正则表达式搜索并且使用一个回调进行替换)

思路:借助匿名函数进行替换

从PHP5.3开始支持匿名函数

reference:http://www.php.net/manual/zh/functions.anonymous.php

<?php
    $tpl = "尊敬的{{0}},感谢您注册{{1}}平台,您的验证码是{{2}}";

    $body = '["15012341234","易众投","123456"]';
    $body_arr = json_decode($body, true);

    $result = preg_replace_callback('/{{(\d)}}/', 
                                function($macthes) use($body_arr){ 
                                    $tpl_item_index = intval($macthes[1]);
                                    return $body_arr[$tpl_item_index]; 
                                }, 
                                $tpl
                            );
    var_dump($result);
?>

日行一记

PHP获取文件的类型

/**
 * 获取文件类型
 * @param string $filename 文件名称
 * @return string 文件类型
 */
function getFileType($filename) {
   return substr($filename, strrpos($filename, '.') + 1);
}
 
/**
 * 获取文件类型2
 * @param string $filename 文件名称
 * @return string 文件类型
 */
function getFileType2($filename) {
   return strtolower(pathinfo($filename)['extension']);
}
 
/**
 * 获取文件类型3
 * @param string $filename 文件名称
 * @return string 文件类型
 */
function getFileType3($filename) {
  return $exten[count($exten = explode('.', $filename)) - 1];
}
 
/**
 * 获取文件类型4
 * @param string $filename 文件名称
 * @return string 文件类型
 */
function getFileType4($filename) {
   $exten = explode('.', $filename);
   return end($exten);
}

日行一记

Ubuntu中SVN客户端的使用

安装

sudo apt-get install subversion

checkout 检出

(先进入创建的目录中)

cd /var/www/source/trunk
svn co url

update 更新

(先进入目录)

cd /var/www/source/trunk
svn up

commit 提交

(先进入目录)

cd /var/www/source/trunk
svn ci -m '修改信息'

add 添加

(先进入目录,add之后需要commit)

cd /var/www/source/trunk
svn add filename
## or
svn add path

log查看日志

(先进入目录)

cd /var/www/source/trunk
svn log
## or
svn log filename

日行一记

《岛》

岛

岛●始

天机之地,踏入者死!

“大家跟紧了!后面的快跟上!”船长在队首喊着。后面跟着十五个疲惫不堪的船员和乘客。

“这里已经是半山腰了,山林紧密,请大家注意安全!左右的船员特别留意有没有野兽出没!”船长再次高喊。其他的人都有些厌烦了,不停地唠叨,却也不敢放松警惕。

“前面那个船员怕是不行了,我过去看看。”一个女人对旁边的男人说道。

“好,你小心点,亲爱的,”男人很温柔,“牧子想上厕所,我们等会赶上你们。”男人摸摸旁边的小女孩的头,微笑。

“嗯。”女人看了看前面,跑了过去。

“我留下陪你们吧。刚才好像听到一声狼嚎。”一个大个子听到男人的话,停了下来。

“这个......那就多谢了。”

几分钟后,当三人爬上一个山头时,没有看到一个人。大个子立刻觉察到不对劲,他马上跑过前面的一个小山包。惨象显现。

十三具尸体,展现人生百态。

十三个人全部落入几米深的陷阱里,等在下面的,是一排排尖尖的木桩!于是,十三只猎物被俘虏了,腥红的血溅起很高。

“妈妈,妈妈,妈妈!”一个女孩哭喊。

“啊——”两个男人大叫。

船长的手伸出。他想求救。

一声狼嚎,在树林深处响起。

末日的审判,来临。


岛●劫

这是属于每个人的劫难。

未知领域,未知海岸。

“瑶瑶,醒醒!”阿木摇摇白瑶的肩膀,着急地喊着。他已经这样做快五分钟了,白瑶还是不醒。突然,他看到白瑶开裂的嘴唇动了一下,他以为她渴了,赶忙把她扶起来,想喂她口水,可谁知白瑶一弯起腰来就吐水。

“该死,我怎么连这个都忘了。”阿木一边责骂自己失误,一边拍拍白瑶的背。

白瑶吐出腹中的水后,又咳了几次,才睁开眼。“这是哪儿?”

“我也不知道,可能是船失事了,把我们漂到这儿,其他我找不到更好的理由。难道他们趁我们睡着把我们扔进海里吗?哈哈哈!”

“好了,阿木。有什么好笑的。我昏迷多久了?”白瑶借着阿木的手臂想撑起身子。

“小心点。从我醒来到找到你,再到你醒来,应该有半个多小时了吧。别担心,我检查过了,你的玉体上没有陌生人的痕迹,也没有任何禽兽攻击的迹象。”阿木悠悠地说道。

“你!你竟然趁我昏迷,你......卑鄙的人!英明的上帝啊,请您发发慈悲,狠狠地教训这可恶的色狼吧!”白瑶站起来,很“虔诚”地对着天喊。

“仁慈的上帝啊,请您做主,为您忠诚的将来的著名医生铁木先生申冤吧。”阿木也相当严肃地双手半握,向上帝祷告。

“上帝,你要是听信他的谣言,你就死定了。”白瑶咬咬牙狠狠地看着阿木。

“你敢对上帝如此不敬!上帝啊,请宽恕这个无知的女人,让我来惩罚她吧。”

“你别碰我!啊——”

“抓到你了!”

突然一声狼嚎响起,两人惊惧地回头,只见身后空旷的海滩突然变成了茂密的树林。

“快,快快,它在那儿!”突然旁边又传出一个很粗犷的男音,紧接着是一阵阵脚步声。两人又相拥着惊恐地往旁边看,只见两个男人向他们跑来。前面的一个肌肉发达,左手抓着一块大石头,右手里拿着一支矛,背上还有一张弓和几只箭,似乎都是自制的。后面的男人瘦瘦的,戴着一副眼镜,他用左手环抱着一个长发的小女孩,右手里也抓着一支矛。

野人?!不对!

只见肌肉男突然停下,将手上的矛使劲扔向树林。矛平稳而快速地穿过灌木丛,“铮”地一声钉在了一棵大树上。

“该死的,又让它跑了!”

那三人也发现了阿木两人,便向他们跑来。待到三人跑到阿木旁边时,狼嚎声再起,郁郁葱葱的树林迅速将他们覆盖。阿木再回头看,哪里还有什么海,一大片全是森林!其他三人似乎见惯了,没什么反应,只剩阿木和白瑶愣愣地往后退。他们不知道来人可不可信,目前的所有都打乱了他们的思维,他们无法接受。

“一定是幻觉,通通都是幻觉!”白瑶歇斯底里地叫了起来。

突然,狼嚎声又起,一道强光穿过树缝,射向五人。各人都禁不住闭上眼。再睁开时,五人却惊奇地发现他们漂泊在海上,白天变成了黑夜!

海一望无际,微弱的月光照耀着海面,波光粼粼,五人同坐在一块大木板上漂浮着。阿木和白瑶始终相拥着。他们不敢睁开眼。这些地方,这些人。想到这些他们就联想到电影里恐怖景象,越想越怕,越想越怕,他们又叫起来。

“别叫了!我们的船在那儿,我们划过去。”肌肉男受不了了。他想要个安静的世界。

阿木和白瑶听到肌肉男大叫,根本不理会他说了什么。眼睁一下又赶紧闭上,嘴也闭了起来。身体不由自主地颤抖。其他三人无可奈何,只好作罢。

白瑶忽然想起什么,慢慢地睁开眼。一艘船出现在眼前。船上没有灯,但月光似乎特意加强了对船身的照射,“海神号”三个大字醒目的印在船身中间。

“阿木,快看,有一艘船!海、神、号,阿木,阿木,那是我们坐的那艘船!快看!我们有救了!喂,我们在这儿!”白瑶很激动地站起来叫喊。

阿木很高兴,可一转身看到正看向他们的两个男人,脸顿时僵硬了,手不停地拉着白瑶的衣角。见白瑶不理会,他站起来一把拉过她,给她使了个眼色,然后又对着两个男人苦笑一阵,才坐下。他的心激动地“扑通”声超出海浪的拍打声。白瑶自然识趣地坐下,不再说话。

一阵沉默。

“看来我们坐的是同一艘船”,肌肉男打破沉默,“你们是幸运的,很好。”他悲伤起来。

女孩的父亲更是悲伤地流出泪来。阿木开始相信这一切,相信这几个同船的乘客。

“到底发生了什么事?”阿木不禁问道。

“没事,什么事都没有。既然它想让我们离开,我们就离开。我们回家去,把这里的一切都忘得干干净净,我们一定要活下去……”肌肉男说着说着就哭起来。他不停地抚摸着小女孩的头。

三人陷入悲伤的往事,一时无法摆脱。阿木和白瑶尴尬地坐在原地,不知该干什么,说什么。

过了几分钟,两个男人的情绪稍稍稳定。擦干眼泪后,肌肉男说道:“对不起,让你们见笑了。但你们没事我真的很高兴。我们划过去吧,那船上不会有人的。对了,我叫韩瑨,这是杨兴杨先生,他的女儿,牧子。”

“我叫铁木,是医学院的实习生,她是我的女朋友,白瑶。”阿木应道。

杨兴把女孩放下来,柔声道:“牧子,要打个招呼吗?”女孩摇摇头。阿木看不到她的脸,只见她长长的黑发随着头的摆动流动着美丽的光泽。

杨兴一点也没有生气的样子,只是对阿木笑笑,表示抱歉。他摸了摸牧子的头,道:“没事的,宝贝,一切都会过去。”女孩在他的搀扶下坐了下来。

“我们用手划吧。我喊一二三开始。”韩瑨说道。于是五人分居四角,开始行动。

“一二,一二,一二……”粗犷的声音在海上飘荡着。


过了大约半个小时,五人离船只有几米远了。肌肉男试着叫了声:“喂,有人吗?!”过了几分钟都不见回应,他继续道,“我们上去吧。”他找到锚索,先爬了上去,见没什么危险就招了招手,示意其他人上船。

上船后,阿木才知道,该死的月亮只把月华贡献给船身,船身几乎伸手不见五指。不过他凭记忆找到了灯开关。要知道,刚上船时,他为了向白瑶显示他超强的感知力,半夜爬起来,把眼睛蒙起来,然后装瞎子绕船跑了好几圈,才找到白瑶关闭的灯的开关。他对这已经是轻车熟路了。“吧嗒”一声,瞬时光临大地。感谢上帝,灯竟然还有用!回到人类的时代,众人的感觉不言而喻。原来人真的离不开社会,自古以来就是这样。

集群的鸟不会有悲伤。

牧子一上船就往父亲怀里钻。她似乎很内向。这让阿木和白瑶有些看不下去。杨兴抱起她,安慰地拍拍她的头,走向船长室。他粗粗地检查了一下,全部设备都完好无损,只是油表示数为零。

“韩老大,这儿的机器都没坏,不过可能没油了。”他向外喊。

“知道了,我去底舱看看还有没有备用油。”韩瑨应道。他转身去了底舱。

这时,牧子出奇地主动要求降落地面。她站在甲板上看海,虽然看到的几乎是一片黑。杨兴摇摇头,转身去查看船的其他各设备是否能正常运行。阿木和白瑶因为帮不上什么忙,也站在甲板上。白瑶摆弄着她的头发,阿木则用指节敲着护栏。

不一会儿,韩瑨便提了两桶汽油出来。他又找来一百水果刀撬开桶盖。正要往油箱里倒油,却发现油箱口里面塞了个啤酒瓶盖子。

“哼,又是个酒鬼!”

因为油箱口大概有5厘米长,又小的可怜,要拿出盖子有点困难,韩瑨就把手尽力地往里伸。同时,阿木感觉有点口渴,就问白瑶要不要喝饮料。得到肯定回答后,他慢慢地往船舱走去。

就在韩瑨的手就要拔出啤酒瓶盖子的时候,异变开始了!油箱口突然间高速旋转起来,伸进去的手立刻被绞了进去,瞬时血肉横飞。鲜红的血竟一滴都没溅出,全部流入了油箱中!5厘米长的油箱口浑然就是一个绞肉机!剧痛传来,韩瑨忍不住大叫起来,使劲地想把手拔出来,却没想到他如此的大力都没法阻止转动或减缓手被绞的速度,更不用说把手拔出来了。

“啊——”

就在韩老大的大半只手臂都不复存在的时候,他做了一个决定。他一把抓起水果刀用尽全身气力向自己的右臂砍去!血溅一身,朦胧了眼。

阿木端着两杯橙汁刚从船舱出来,忽然听到韩瑨的叫声,吓了一跳,扔下杯子就跑了过去。杯子碎了,橙汁像油似的流到船沿,又往下流动。

作为一个医学院的优等生,他拥有和医生一样的冷静和技术。他利索地撕下衣服上的一块布,将韩瑨的伤口包上十来层,虽然还是止不住血,但很明显的,流血量减少了很多。

“你待在这儿别动,我去找药箱!”阿木的动作变得迅捷了不少。确实,对医学他有极大的兴趣。当他提着药箱一个箭步跨出船舱,他听到了一个致命的声音——白瑶的尖叫声。这时候他哪里还管什么医德,扔下药箱,径直跑到甲板上。


几十秒之前,甲板上。

牧子望着海,喃喃自语道:“我不要离开妈妈,我不要离开妈妈……”当勇气胜过怯懦,冲动便出发了。于是女孩跨过栏杆,纵身跳下了海。当时白瑶背着她正要转过身来,突然看到她跳海,不禁尖叫一声,接着她毫不犹豫地跳下海救人。对于一个专业女子泳队出身的人来说,在水中救出一个人,不是难事。

“牧子!”杨兴先阿木几步到甲板上,助跑几步后径直跳下了海。紧接着阿木也跳了下去。韩瑨听到声响,忍着疼痛跑到甲板上,犹豫了一会儿,也跳了下去。为防备不测,他不忘将那把“血色桃枝”带上。

一切来得太快,谁都来不及拯救。

“啊——爸爸!”一丝火光闪过,乌云中射出一道闪电,顿时地面着起火来。

谁能料想,原来要跳海却跳到了陆地上,天也由黑瞬间变大白。几乎毫发无伤的小女孩还没开始庆幸,就又陷入了另一道陷阱!地面上什么都没有,火却突然变得很大,围成一个圈,恰好将刚碰到地面的牧子包围。不管她从哪个方向冲,都被强大的火势压回去。增加的只是烟熏眼的痛苦和心中的恐慌。

“爸爸!爸爸!”牧子只得声嘶力竭地喊着,累不停地流。

害怕了吗?不是你一直叫着不离开妈妈吗?

牧子想着,更大声地哭喊,似乎只有这样才能克制心中的恶魔。“啊——”尖叫冲破云霄。

白瑶刚跳下来时也吃了一惊,还想着能救个人,却未料火海之中,怎么也游不得。而且,更关键的,原来跳海时头是向下的,怎么碰到地面时头就向上了?!她也顾不了这么多了,一手掩着嘴,两脚踏进又踏出,但就是无法突围进去。火总在她最靠近的时候最猛烈,她只能干着急。

杨兴连吃惊都不管了,更不管那火,看到自己的孩子身处火海之中,想也没想就径直冲了进去。只几秒钟时间他就冲到女孩的身旁,疼爱地抱起哭泣的女孩,哽咽着安慰受伤的小兔:“宝贝,别怕,爸爸在这儿,爸爸在这儿,没事了。”此刻他的心中只有愧疚。他不停地自责。如果当初我没让妻子到前面去,如果带牧子的是她而不是我,如果刚才没让牧子一个人在甲板上,如果我一直陪着她,如果……

“爸爸,我要妈妈,我要妈妈!”

“好,乖宝贝,我们这就去找妈妈。”他脱下外套盖在女孩身上,护住她的身体,便往外跑。

这时,阿木早已适应周围环境。他冲到白瑶身旁,焦急地喊:“瑶瑶,你没事吧?有没有受伤?”

突然从大火中冲出的男人着实让阿木吓了一跳。定睛一看,认出他们后,阿木又关切地询问他们的情况。男人只是微微摇头,不回答。阿木看到他的腿有些烧焦了,想帮他看看,但看他的表情,又把冲动压了下去。

“畜生,又是你干的好事!”

众人回头,只见韩瑨不知从哪儿捡回了那块丢失的石头,左手使劲一挥,放下是火的左边。众人都看不到,但心里都明白这“畜生”指的是谁。果然,左边传来了那只狼的哀嚎声。这让阿木和白瑶觉得不自在。救吗?可是它好像和人恩怨不浅。不救?那也是生命啊。矛盾的两端冲突着,谁也赢不了谁。

可韩瑨不打算放过它。他走到狼跟前,确定没有危险后,将右脚抬起,使劲地朝喘息着的狼首踏去。顿时,脑浆、血液溅了一地。

狼死了。这该死的畜生终于死了。


韩瑨刚要转身,忽然想到好几天没吃东西了,烤狼肉应该不错。可当他就要拾起狼尸时,铺天盖地的大黄蜂突然出现!

成千上万的大黄蜂瞬间盖住了他的身体,他不得不站直身子,挥动左手赶走它们。谁知它们似乎发现了他的伤口,一个劲地往他右手的伤口上撞!剧烈的疼痛感重新传来,韩瑨不由地跳了起来。

它们要让这样的疼痛更深刻!这,就是它们的目的。

不一会儿,韩瑨觉得身上的疼痛减轻了,身上好像也,没有了翅膀轻拍的感觉。睁开眼一看,哪里还有什么大黄蜂。再低头一看,狼尸也不见了!

“该死的!”他使劲一跺脚,又捡起那块击中了狼肚子,还留有些许血滴和腥味的石头,狠狠地砸了出去。

众人在搜查火源无果后跑到韩瑨的跟前,看到他的神情也不好多问什么。杨兴好像想到了什么,突然放下牧子:“宝贝,你待在这儿别乱走,好吗?”

牧子乖巧地点点头,却又明显地对父亲的怀抱恋恋不舍,还轻声地叫了声“爸爸”。

“韩老大,先照顾一下牧子。”

“好。”韩瑨应声抱起了牧子。

杨兴回身向着熊熊的烈火,再次冲了进去!韩瑨对此不惊讶,反而笑了。原来两人都发现了火中的奥秘。整个火场是按太极摆的,外圈的火力最猛,但圈内的两个点才是精髓。因为有它们才有了外圈的火。而这两个火点又是温度最低,火力最弱的,只要稍微飞点力把火点之一熄灭,整个系统都会瘫痪!

杨兴站在离他最近的火点旁,蔑视地看着它,将外套盖了下去。火轻而易举地灭了。果不出所料,外围的熊熊烈火渐渐地也灭了。

天空再次清明。

众人笑,众神笑。

突然,一条更为猛烈的火舌直冲云天!那原本距离杨兴五米远的火点突然移到了他的脚下,顿时点燃了他的衣服。他不禁大叫起来。

“爸爸——”

“杨先生!”韩瑨顿时脸色苍白,几颗斗大的汗珠从他的额头渗出来。他忘记了致命的一点,太极是无时不在转动着的!也就是说,熄灭的火点是假的,现在更猛烈的,才是真的!可是,来不及了!

众人都想冲上去,然而火的速度比他们更快。火圈以迅雷不及掩耳之势和熊熊之魄挡住了他们。愤怒、焦虑充斥着每个人的心。

当火焰渐大,浓烟渐散,火的形状渐渐清晰。众人知道了真相:狼。

“你这该死的畜生!”韩瑨愈发愤怒,额上的青筋都显而易见。他放下哭红了眼的女孩,不顾一切的冲进了火场。他并没有感觉到灼热的疼痛。但是他接近杨兴的时候,火停了。彻底地停了。

狼嚣张地来,又嚣张地走。

只剩下一具焦黑的尸体,跪在地上,没有表情,他似乎是在乞求上帝的宽恕。

被宽恕的是什么?

“畜生!不把你碎尸万段我誓不为人——”只见韩瑨拔下腰间的水果刀,走到外套旁边,掀起外套,迅速、有力地捅向地面。地面竟然开了个口子!

“嗷呜——”又是一声狼嚎,只是多了一分悲凉。场景再次转换,突然之间众人陷身海域。

“爸爸!”牧子大叫。可不管她往哪个方向看都没有父亲的影。

他就这样地走了……


白瑶游向她,安慰地把她抱在怀里。牧子也只能这样,失声哭泣。

就在这时,四人发现周围的压力骤增,海水似乎开始凝固,他们都快不能呼吸了。在这惊人的压力下,他们竟然没有往下沉!但是,他们动不了了,这是事实。只见韩瑨身体飘起,却一动不动,只是眼睛睁得铜铃一般,皮肤似乎有些下陷。

一个声音说,报复,开始了。

数以万计的大黄蜂再次出现,飞向韩瑨,瞬间就把他的周身包围。血不断地从他的脚底流出,滴入海中,即刻溶解,四散。四面八方的海面上不知什么时候又出现了食人蚁,数量越来越多,黑压压的一大片。而且,它们不会下沉!它们的目标只有一个——韩瑨。于是,韩瑨的身体迅速由黄变黑。当只剩下一颗黄色的头时,他大叫一声,竟然喊出了声音!然后,像没有穿宇航服而在宇宙中游荡的宇航员一样,他自爆了。血肉四溅,淋漓了世界。

阿木和白瑶惊恐万分。第二次直面死亡。如此惨烈。

一个声音在远处笑。

怎么回事?这到底是怎么回事?!

还没等他们反应过来,场景再次变换。只听锣鼓声天,车轮滚滚,呐喊声一阵阵传来。黄沙散漫,北风狂啸,断旌半插黄土中。他们来到了古战场!

战场之上,非己者,死!

三人只能不断地躲避着,防守着,在遭受了几轮攻击之后,三人被分在三个不同的方向上,距离也越拉越远。

各自拼搏,各自祈祷吧。于是,三人向着不同的方向往外冲,身后依然嚣声如雷。

一支箭破空飞向阿木,而他则毫不知情地向前奔跑。逃生是他唯一的本能。

突然,他被脚下的一块大石头绊倒,身体毫不留情地撞向地面。全身的疼痛和进入眼睛的灰尘让他眼中流出了泪水。

我不该来的……

他爬起来,才发现周围很荒凉,一个人都没有。天灰蒙蒙的,周围是一个个小山包,都不高。这时,他似乎发现了一个奇怪的东西——一块半倒的石碑。他凑过去,定睛一看,却见碑上刻着三个字:万人塚。阿木大吃一惊,跌坐在地上。不过他的背好像靠到了什么东西,他呆呆地转过身,只见身后的一块断碑上刻着:铁木之墓。他身下的土地突然塌陷……

地狱之门打开,已死之人进来。


同一时刻,白瑶在另一方向确实是跑不动了,她不得不弯下腰来休息。她感觉到了异变。突然抬起头,她才发现自己已经在一片草地上。风拂草而去。夕阳无限好,只是近黄昏。

一只黑猫优雅地走在草地上,猫眼慵懒地睁着,尾巴弯曲着,不停跳动。白瑶不敢放松,见只是一只猫,松了口气,然后往旁边看去,观察一切可能发生的异象。当她的脸再次转向前方时,她的眼前一团黑。猫扑了上来。她不停地甩头,双手不停地乱抓,只希望能把这该死的猫拿开。可是,当黑猫摇着尾巴优雅地离开时,白瑶只能睁大眼,死命地掐着脖子。猫的毛塞入了她的嘴和鼻子。她虽然拿掉了鼻子和嘴里的猫毛,却无法取出卡在喉咙里的。

她,只能痛苦地,等死。

牧子在一片沼泽前停下脚步。

越过雷池一步,身将万劫不复。

我想活着,但我没有选择……

牧子含着泪跳了下去。

爸爸,妈妈,好高兴,又能见到你们了。

空中,杨兴和一个女人的影像浮现……

她闭上了眼。

但,这里不是地狱。

牧子睁开眼,却发现这是一个空旷的广场,广场**站立着一个瘦高的男子,正聚精会神地看着身前悬空的水晶球。他大声地笑,目光不离地对牧子说:“这是玥,我的神明,我的主。它赐予我无穷的力量和无上的光荣。哈哈!”

牧子定定心,跑过去一看。原来水晶球上显现的,正是白瑶的死状。她不由地大叫起来。男子阴笑,转身幽幽地对她说:“你所看到的一切,都是幻象……”

“你这个杀人狂!凶手!还我妈妈!还我爸爸!”牧子向他扑过去,他却像弱不禁风一样,与牧子一同撞向水晶球。空中,男子款款地说:“我的妻子,不也被你们杀了吗?”

水晶球碎了,两个人的头部都被玻璃刺中,流血不止……


岛●终

一个男子缓慢地走在冰面上,右手托着一个巴掌大的水晶球。他的身前不远处是一张冰床,床上躺着一具母狼的躯体……

“总有一天,我们会摆脱这该死的诅咒,对吗,亲爱的?”男子透过水晶球深情地凝视着冰床上的躯体。

CSS3实现switch按钮效果

效果:

image

源码:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>CSS3自定义Checkbox特效DEMO演示</title>
</head>
<style type="text/css">
.tgl{display:none}
.tgl,.tgl *,.tgl :after,.tgl :before,.tgl+.tgl-btn,.tgl:after,.tgl:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
.tgl ::-moz-selection,.tgl :after::-moz-selection,.tgl :before::-moz-selection,.tgl+.tgl-btn::-moz-selection,.tgl::-moz-selection,.tgl:after::-moz-selection,.tgl:before::-moz-selection{background:0 0}
.tgl ::selection,.tgl :after::selection,.tgl :before::selection,.tgl+.tgl-btn::selection,.tgl::selection,.tgl:after::selection,.tgl:before::selection{background:0 0}
.tgl+.tgl-btn{outline:0;display:block;width:4em;height:2em;position:relative;cursor:pointer}
.tgl+.tgl-btn:after,.tgl+.tgl-btn:before{position:relative;display:block;content:"";width:50%;height:100%}
.tgl+.tgl-btn:after{left:0}
.tgl+.tgl-btn:before{display:none}
.tgl:checked+.tgl-btn:after{left:50%}


.tgl-light+.tgl-btn{background:#f0f0f0;border-radius:2em;padding:2px;-webkit-transition:all .4s ease;transition:all .4s ease}
.tgl-light+.tgl-btn:after{border-radius:50%;background:#fff;-webkit-transition:all .2s ease;transition:all .2s ease}
.tgl-light:checked+.tgl-btn{background:#9FD6AE}


.tgl-ios+.tgl-btn{background:#fbfbfb;border-radius:2em;padding:2px;-webkit-transition:all .4s ease;transition:all .4s ease;border:1px solid #e8eae9}
.tgl-ios+.tgl-btn:after{border-radius:2em;background:#fbfbfb;-webkit-transition:left .3s cubic-bezier(.175,.885,.32,1.275),padding .3s ease,margin .3s ease;transition:left .3s cubic-bezier(.175,.885,.32,1.275),padding .3s ease,margin .3s ease;-webkit-box-shadow:0 0 0 1px rgba(0,0,0,.1),0 4px 0 rgba(0,0,0,.08);box-shadow:0 0 0 1px rgba(0,0,0,.1),0 4px 0 rgba(0,0,0,.08)}
.tgl-ios+.tgl-btn:active{-webkit-box-shadow:inset 0 0 0 2em #e8eae9;box-shadow:inset 0 0 0 2em #e8eae9}
.tgl-ios+.tgl-btn:active:after{padding-right:.8em}
.tgl-ios:checked+.tgl-btn{background:#86d993}
.tgl-ios:checked+.tgl-btn:active{-webkit-box-shadow:none;box-shadow:none}
.tgl-ios:checked+.tgl-btn:active:after{margin-left:-.8em}


.tgl-skewed+.tgl-btn{overflow:hidden;-webkit-transform:skew(-10deg);-ms-transform:skew(-10deg);transform:skew(-10deg);-webkit-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden;-webkit-transition:all .2s ease;transition:all .2s ease;font-family:sans-serif;background:#888}
.tgl-skewed+.tgl-btn:after,.tgl-skewed+.tgl-btn:before{-webkit-transform:skew(10deg);-ms-transform:skew(10deg);transform:skew(10deg);display:inline-block;-webkit-transition:all .2s ease;transition:all .2s ease;width:100%;text-align:center;position:absolute;line-height:2em;font-weight:700;color:#fff;text-shadow:0 1px 0 rgba(0,0,0,.4)}
.tgl-skewed+.tgl-btn:after{left:100%;content:attr(data-tg-on)}
.tgl-skewed+.tgl-btn:before{left:0;content:attr(data-tg-off)}
.tgl-skewed+.tgl-btn:active{background:#888}
.tgl-skewed+.tgl-btn:active:before{left:-10%}
.tgl-skewed:checked+.tgl-btn{background:#86d993}
.tgl-skewed:checked+.tgl-btn:before{left:-100%}
.tgl-skewed:checked+.tgl-btn:after{left:0}
.tgl-skewed:checked+.tgl-btn:active:after{left:10%}


.tgl-flat+.tgl-btn{padding:2px;-webkit-transition:all .2s ease;transition:all .2s ease;background:#fff;border:4px solid #f2f2f2;border-radius:2em}
.tgl-flat+.tgl-btn:after{-webkit-transition:all .2s ease;transition:all .2s ease;background:#f2f2f2;content:"";border-radius:1em}
.tgl-flat:checked+.tgl-btn{border:4px solid #7FC6A6}
.tgl-flat:checked+.tgl-btn:after{left:50%;background:#7FC6A6}


.tgl-flip+.tgl-btn{padding:2px;-webkit-transition:all .2s ease;transition:all .2s ease;font-family:sans-serif;-webkit-perspective:100px;-ms-perspective:100px;perspective:100px}
.tgl-flip+.tgl-btn:after,.tgl-flip+.tgl-btn:before{display:inline-block;-webkit-transition:all .4s ease;transition:all .4s ease;width:100%;text-align:center;position:absolute;line-height:2em;font-weight:700;color:#fff;position:absolute;top:0;left:0;-webkit-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden;border-radius:4px}
.tgl-flip+.tgl-btn:after{content:attr(data-tg-on);background:#02C66F;-webkit-transform:rotateY(-180deg);-ms-transform:rotateY(-180deg);transform:rotateY(-180deg)}
.tgl-flip+.tgl-btn:before{background:#FF3A19;content:attr(data-tg-off)}
.tgl-flip+.tgl-btn:active:before{-webkit-transform:rotateY(-20deg);-ms-transform:rotateY(-20deg);transform:rotateY(-20deg)}
.tgl-flip:checked+.tgl-btn:before{-webkit-transform:rotateY(180deg);-ms-transform:rotateY(180deg);transform:rotateY(180deg)}
.tgl-flip:checked+.tgl-btn:after{-webkit-transform:rotateY(0);-ms-transform:rotateY(0);transform:rotateY(0);left:0;background:#7FC6A6}
.tgl-flip:checked+.tgl-btn:active:after{-webkit-transform:rotateY(20deg);-ms-transform:rotateY(20deg);transform:rotateY(20deg)}
</style>

<body>

  <span class='tg-list-item'>
    <input class='tgl tgl-light' id='cb1' type='checkbox'>
    <label class='tgl-btn' for='cb1'></label>
  </span>

  <span class='tg-list-item'>
    <input class='tgl tgl-ios' id='cb2' type='checkbox'>
    <label class='tgl-btn' for='cb2'></label>
  </span>

  <span class='tg-list-item'>
    <input class='tgl tgl-skewed' id='cb3' type='checkbox'>
    <label class='tgl-btn' data-tg-off='OFF' data-tg-on='ON' for='cb3'></label>
  </span>

  <span class='tg-list-item'>
    <input class='tgl tgl-flat' id='cb4' type='checkbox'>
    <label class='tgl-btn' for='cb4'></label>
  </span>
  
  <span class='tg-list-item'>
    <input class='tgl tgl-flip' id='cb5' type='checkbox'>
    <label class='tgl-btn' data-tg-off='Nope' data-tg-on='Yeah!' for='cb5'></label>
  </span>

</body>
</html>

日行一记

Ubuntu16.04 系统状态栏显示实时网速、CPU和内存

2017-06-09 13-10-42

使用的是一个python写的系统插件,名为:indicator-sysmonitor

1. 首先,安装依赖:

sudo apt-get install python python-psutil python-appindicator

2. 然后下载indicator-sysmonitor

wget -c https://launchpad.net/indicator-sysmonitor/trunk/4.0/+download/indicator-sysmonitor_0.4.3_all.deb

3. 安装:

sudo dpkg -i indicator-sysmonitor_0.4.3_all.deb

4. 修改图标

由于软件默认显示图标是 sysmonitor.svg,而在 Ubuntu 14.04 64 位系统中没有这个图标,这导致图标显示错误。于是要把软件默认使用的图标改成一个存在的图标:

sudo gedit /usr/bin/indicator-sysmonitor

找到724行的sysmonitor,改为:gnome-system-monitor,保存即可。

系统图标存放在:/usr/share/icons/Humanity/apps/ XX 目录下(XX 为:128 16 192 22 24 32 48 64 中任一个,对应同一图标的不同尺寸,同一图标并不是每个尺寸都有)。

5. 运行

indicator-sysmonitor

6. 设置开机自启动

点击系统状态栏上的图标,在出现的菜单里选择首选项,勾选Run on startup,即可。

7. 自定义显示内容

默认显示的内容是CPU和内存使用情况,如果想加上实时网速,在首选项弹窗里,选择Advanced,然后在Sensors列表里选择net,点击添加按钮,将网络添加到输出中

实时网速:{net}

点击Test按钮,测试一下,发现报错:

File "/usr/bin/indicator-sysmonitor", line 291, in _fetch_net
for _, iostat in ps.network_io_counters(pernic=True).items():
AttributeError: 'module' object has no attribute 'network_io_counters'

可能的原因:安装时拉取的包版本较低,程序有问题。

根据提示知道:psutil模块没有network_io_counters方法。

在查找文档(https://github.com/giampaolo/psutil)之后发现确实没有这个方法,而是net_io_counters这个方法。

知道问题之后,就可以解决:

sudo gedit /usr/bin/indicator-sysmonitor

找到291行,把 ps.network_io_counters 改为 ps.net_io_counters ,保存之后重新运行 indicator-sysmonitor ,再设置网速就没有问题了。

参考

日行一记

SVN构建分支命令

构建分支

svn cp -m "构建6.5.5.57版本" http://d.com:88/ytshop/source/trunk http://d.com:88/ytshop/source/branches/6.5.5.57

查看log

svn log         #什么都不加会显示所有版本commit的日志信息:版本、作者、日期、comment。
svn log -r 4:20 #只看版本4到版本20的日志信息,顺序显示。
svn log -r 20:5 #显示版本20到4之间的日志信息,逆序显示。
svn log test.c  #查看文件test.c的日志修改信息。
svn log -r 8 -v #显示版本8的详细修改日志,包括修改的所有文件列表信息。
svn log -r 8 -v -q   #显示版本8的详细提交日志,不包括comment。
svn log -v -r 88:866 #显示从版本88到版本866之间,当前代码目录下所有变更的详细信息 。
svn log -v dir  #查看目录的日志修改信息,需要加v。
svn log http://foo.com/svn/trunk/code/  #显示代码目录的日志信息。

Debian下域名和端口配置

默认配置

所有站点的域名、端口配置信息都在 /etc/apache2/sites-available 文件夹下

进入Apache配置文件夹

root@debian:~# cd /etc/apache2/sites-available && ls
default  default-ssl

其中的default文件即默认的站点配置文件。

<VirtualHost *:80>
        ServerAdmin webmaster@localhost

        DocumentRoot /var/www
        <Directory />
                Options FollowSymLinks
                AllowOverride All
        </Directory>
        <Directory /var/www/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride All
                Order allow,deny
                allow from all
        </Directory>

        ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
        <Directory "/usr/lib/cgi-bin">
                AllowOverride All
                Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
                Order allow,deny
                Allow from all
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log

        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn

        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

可以看到,默认的站点文件夹在 /var/www 文件夹中,可以将PHP文件都上传到这个文件夹中。

所有站点的程序文件都是放在 www 目录下的,一般的做法是按照域名在 www 目录下创建属于某个域名的文件夹,将这个站点的程序放进去。

添加站点

假设现在需要在 /var/www/doc 目录下添加某个网站程序,并用 127.0.0.1:8080 的形式访问。

1、添加 8080 端口监听

端口监听文件是 /etc/apache2/ports.conf,需要添加端口监听则添加Listen ** 即可。

root@debian:~# cd /etc/apache2
root@debian:/etc/apache2# vi ports.conf

ports.conf 文件:

# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default
# This is also true if you have upgraded from before 2.2.9-3 (i.e. from
# Debian etch). See /usr/share/doc/apache2.2-common/NEWS.Debian.gz and
# README.Debian.gz

NameVirtualHost *:80
Listen 80

# 添加8080端口监听
Listen 8080

<IfModule mod_ssl.c>
    # If you add NameVirtualHost *:443 here, you will also have to change
    # the VirtualHost statement in /etc/apache2/sites-available/default-ssl
    # to <VirtualHost *:443>
    # Server Name Indication for SSL named virtual hosts is currently not
    # supported by MSIE on Windows XP.
    Listen 443
</IfModule>

<IfModule mod_gnutls.c>
    Listen 443
</IfModule>

2、在 /etc/apache2/sites-available 文件夹下新建配置文件

root@debian:/etc/apache2/sites-available# touch doc && vi doc

3、复制默认站点的内容,修改端口号和文件夹地址

<VirtualHost *:8080>
        ServerAdmin webmaster@localhost

        DocumentRoot /var/www/doc
        <Directory />
                Options FollowSymLinks
                AllowOverride All
        </Directory>
        <Directory /var/www/doc/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride All
                Order allow,deny
                allow from all
        </Directory>

        ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
        <Directory "/usr/lib/cgi-bin">
                AllowOverride All
                Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
                Order allow,deny
                Allow from all
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log

        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn

        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

如果是跟域名绑定,而不是使用 127.0.0.1:8080 这样的地址,可以使用这个:

<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    ServerName domain.com
    ServerAlias www.domain.com
    DocumentRoot /var/www/domain.com/public_html/
    ErrorLog /var/www/domain.com/logs/error.log
    CustomLog /var/www/domain.com/logs/access.log combined
</VirtualHost>

以上配置表示:将 /var/www/domain.com/public_html/ 目录作为域名 domain.com 的运行目录,端口号为默认的 80

注意:

如果网站目录不在/var/www下,那么还需要修改/etc/apache2/apache2.conf文件,找到这个地方:

<Directory />
	Options FollowSymLinks
	AllowOverride None
	Require all denied
</Directory>

<Directory /usr/share>
	AllowOverride None
	Require all granted
</Directory>

<Directory /var/www/>
	Options Indexes FollowSymLinks
	AllowOverride None
	Require all granted
</Directory>

增加以下内容:

<Directory /home/lan/Websites/>
	Options Indexes FollowSymLinks
	AllowOverride All
	Require all granted
</Directory>

修改完之后需要重启apache服务。

4、启用新的配置文件

root@debian:/etc/apache2/sites-available# a2ensite doc

注意:

在Ubuntu16.04版本中,新的配置文件必须是.conf后缀格式的,如果不是,在执行上面命令时,报错:

ERROR: Site doc does not exist!

修改如下:

mv ./doc ./doc.conf
a2ensite doc.conf

5、根据需要停止运行配置文件

如果启用某配置文件后,想取消掉,不想再运行它,则执行以下命令:

root@debian:/etc/apache2/sites-available# a2dissite doc.conf

++注意:++

如果在删除( rm )某个配置文件后,直接重启Apache,将会报错。此时,需要先关闭此配置文件,再重启Apache。

6、增加hosts记录

如果是新增站点,需要添加hosts记录。
hosts文件一般在/etc目录下,如果没有,可以用whereis命令找一下。

127.0.0.1   domain.com
127.0.0.1   www.domain.com

7、重启Apache服务

root@debian:/etc/apache2/sites-available# service apache2 reload

## 或

root@debian:/etc/apache2/sites-available# service apache2 restart

8、上传程序文件

使用ssh等将文件上传到 /var/www/doc 目录下。

文件上传完成后,修改文件夹的权限:

## 测试示例 777表示全权限开放
root@debian:/var/www# chmod -R 447 ./doc

到此就完成了。可以访问 http://127.0.0.1:8080 查看站点。

Note

/etc/apache2 目录下可能会看到两个文件夹:

sites-available  sites-enabled

当运行某个配置文件时,这个配置文件会被复制到sites-enabled目录下,停止运行时,这个配置文件又会从sites-enabled 目录被删除。

日行一记

Debian Server 如何设置语言为英文?

debian2

前记:

在使用vmware安装Debian时,为图方便,选择了使用中文安装。安装过程很顺利,中文显示正确,没有问题,但是在安装完成之后,进入系统,发现命令也是中文的提示,而且中文不能正常显示,只能显示♦♦。不论输入什么命令,都无法知道命令的执行结果和提示信息。

这是由于系统中没有安装中文字体的问题。

有两种解决方案:

  1. 安装中文字体
  2. 将默认语言改为英文

这里使用方案2。

locale命令

在debian或者Ubuntu中,均可使用locale命令查看当前环境变量

locale

image

其中LANG和LANGUAGE就是语言环境。

en_US.UTF-8 表示英文的utf8,zh_CN.UTF-8 表示中文的utf8。如果安装时选择的是中文,则默认为zh_CN.UTF8。

修改locale环境变量

dpkg-reconfigure locales

输入此命令后将会跳出一个选择页面(全乱码),在列表中选择英文的en_US.UTF-8(按空格可以选择和取消选择),之后tab切换到确认按钮(选第一个按钮,按钮可能是中文乱码的)

确认之后会弹出另一个界面,选择默认语言,此时选择前一个页面选择的英文即可(选第二个选项,第一个选项是中文乱码)。

修改完成之后,需要重启系统才能生效

reboot

日行一记

linux的shell脚本

脚本辨识符

shell文件的第一行都是一样的,以此为辨识符:

#!/bin/bash

变量定义

sourceDir="/var/www/html/test"

注意:= 号左右不能有空格。

变量引用

echo ${sourceDir}
echo $sourceDir

花括号一般不是必须的,但是在某些情况下必须要,如:

echo "the source file is $sourceDirtestfile.md"

此时必须用花括号区分字符和变量:

echo "the source file is ${sourceDir}testfile.md"

变量和字符串拼接

versionFile=${sourceDir}"version"

逐行读取文件内容

cat ${versionFile} | while read line
do
    echo $line
done

while read line
do
  echo $line
done < $versionFile

第二种方式效率更高一些。

读取文件第一行或指定行内容

sedhead可以直接打印指定行内容:

head -1 ${versionFile} # 打印第一行内容
head -2 ${versionFile} # 打印第二行内容

sed -n '1p' ${versionFile}  # 打印第一行内容
sed -n '4p' ${versionFile} # 打印第四行内容
sed -n '6,10p' ${versionFile}  #打印文件的第6到10行

如果希望将内容赋值给变量,则需要用 ` 符号包起来

versionNum=`sed -n '1p' ${versionFile}`
# or
versionNum=`head -1 ${versionFile}`

if语句

if [${version} != ""]
then
    echo ${version}
fi

文件/文件夹重命名

重命名可以用renamemv两个命令,rename的效率更高(mv是移动文件,当文件夹中文件较多时,效率较低)

rename "s/Public_min/Public_${versionNum}/" ${destDir}"Webroot/Public_min"
# or
mv ${destDir}"Webroot/Public_min" ${destDir}"Webroot/Public_${versionNum}"

上面语句表示重命名 ${destDir}Webroot/Public_min文件夹,新文件夹名为Public_${versionNum},如Public_1.2

注意:

rename命令还有一个写法:

rename "Public_min" "Public_${versionNum}" ${destDir}"Webroot/Public_min"

上面的命令在Debian下是有效的,但是在Ubuntu下报错:

Bareword "Public_min" not allowed while "strict subs" in use at (user-supplied code)

获取当前时间戳(秒)

startTime=$(date +%s)

注意:date 后必须有个空格。

计算两个时间戳的差(秒为单位)

startTime=$(date +%s)
# do something
endTime=$(date +%s)

echo 'use time: '
echo $(( ${endTime}-${startTime} ))

做运算时,外层需要两对圆括号。

获取当前时间(日期格式)

current=$(date -u +%y%m%d)

上面代码表示:以年月日格式返回当前时间,如170614

获取毫秒及计算毫秒时间差

参考:http://blog.csdn.net/gengshenghong/article/details/7583580

shell文件执行

在命令行中执行:

sh ./test.sh

日行一记

使用rename修改数据表名

RENAME TABLE 功能在 MySQL 3.23.23 中被加入

# rename table 原表名 to 新表名;
mysql > rename table oldTable to newTable;

日行一记

PHP随机函数分析及获取不重复的随机字符串

hacker

以下做一些PHP中随机函数的查重测试

测试代码:

$arr = array();

for ($i=0; $i < 1000; $i++) { 
    $k = getNum();
    $arr[$k] = $i;
}

var_dump(count($arr));

测试中不断调整getNum()函数的返回值

1. uniqid()

uniqid()基于毫秒值,重复概率很大

function getNum(){
    return uniqid();
}

结果:int(8)

1000个循环中,只有8个数不重复

2. microtime()

microtime()即毫秒值,跟uniqid()类似,重复概率很大

function getNum(){
    return md5(microtime());
}

结果:int(9)

多次重复,结果在8和9直接徘徊

microtime()函数可以传入一个bool类型的参数,传入true时,返回一个浮点数,秒为单位

var_dump(microtime()); # => string(21) "0.31520100 1483498881"

var_dump(microtime(true)); # => float(1483498971.2852)
function getNum(){
    return md5(microtime(true));
}

结果:int(7)

效果相差无几

3. mt_rand()

mt_rand()比rand()产生随机数的的平均速度快4倍

当不传参数时,mt_rand返回0到RAND_MAX之间的随机数,传入(min, max)参数时,返回范围内的随机数

由此可见,不传参数会比传入参数得到的效果更好

function getNum(){
    return mt_rand(111111, 999999);
}

这次将循环次数从1000改为1000000

结果:int(600734)

多次重复,结果均在 600000 左右

function getNum(){
    return mt_rand();
}

结果:int(999767)

比带参数好很多

4. time() + mt_rand()

mt_rand()已经可以基本满足需求,但为了更好,可以多个函数结合使用

function getNum(){
    return time() . mt_rand();
}

结果:int(999759)

跟单独使用mt_rand()差别不大

5. microtime() + mt_rand()

function getNum(){
    return microtime() . mt_rand();
}

结果:int(1000000)

多次重复,并没有发现重复

注:microtime()是否传入参数关系不大

6. 总结

当需要循环的次数在百万以内时,可以采用 microtime() + mt_rand() 的方式获得不重复的随机值,重复概率低

/**
 * 生成32位不重复随机字符串
 */
function getRandString($prefix = ''){
    return md5($prefix . microtime() . mt_rand());
}

var_dump(getRandString('attachment_'));
# => string(32) "a7e0d3baf3940bcaaf70833ba852de61"

日行一记

Chrome extension中实现ajax跨域请求

跨域请求

问题描述

在chrome插件中,如果对非当前域作直接的ajax访问,会报错。

例:

假设当前域为:http://lc.testcontent.js执行以下代码:

$.get("http://lc.shop", function(data){
    console.log(data)
})
XMLHttpRequest cannot load http://lc.shop/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://lc.test' is therefore not allowed access.

例2:

假设当前域为:https://shimo.im(https),content.js同样执行上面代码:

Mixed Content: The page at 'https://shimo.im/' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://lc.shop/'. This request has been blocked; the content must be served over HTTPS.

这个就是ajax常见的跨域问题。

解决方法

有两种解决方法:

  • jsonp
  • 将请求转向background.js中

1、jsonp

使用jQuery可以很方便的完成jsonp,直接使用$.getJSON()方法即可,具体可参考: jQuery Ajax跨域请求之JSONP示例

需要注意的是,getJSON()方法只能用于于get请求,不能用于post请求。

2、利用backgroud.js

流程:

从content.js中发送消息(sendMessage)到background.js,在background.js中发起同步的ajax请求,并回传给content.js,content.js再通过返回的数据做判断和页面处理。

配置文件 manifest.json

{
    "background": {
		"page":"background.html"
	},
	"content_scripts": [
		{
		  "matches": ["https://shimo.im/folder/*"],
		  "css": ["css/app.css"],
		  "js": ["js/jquery.min.js", "js/content.js"]
		}
	]
}

嵌入的内容文件 content.js

chrome.extension.sendMessage({cmd: "checkFolder", folderId: folderId},function(response) {
	if(typeof response.code == 'undefined'){
		response = JSON.parse(response);
	}
	
	// 返回数据处理代码
});

sendMessage方法接收两个参数,第一个参数为发送的数据(json object),第二个参数为回调函数:

chrome.extension.sendMessage(object, function)

后台运行文件 background.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <script src="js/jquery.min.js" type="text/javascript"></script>	
    <script src="js/background.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>

后台运行脚本 background.js

$(function(){
    var baseUrl = "http://shimofav.applinzi.com/";
    chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
		// check
		if(request.cmd == 'checkFolder'){

			// 使用ajax同步解决异步不能sendResponse问题
			var folderId = request.folderId;
			ajax(baseUrl+"?op=check", "get", {id: folderId}, sendResponse);

		}
	});
})

/* ajax同步请求函数封装 */
function ajax(url, type, data, callback){
	$.ajax({
		type: type, // post,get
		async: false,
		url: url,
		data: data,
		success: function(response){
			callback(response);
		}
	});
}

chrome.extension.onMessage.addListener(function)方法接收从sendMessage()方法传递过来的参数,这个方法接收一个function参数。

function(request, sender, sendResponse)
  • request 传递过来的参数对象(json object)
  • sender 传递者
  • sendResponse 回调函数

值得注意的是,在这个消息传递过程中,必须保持同步,不能使用异步返回,否则会报错,提示消息发送之后没有收到任何回应。

chrome.extension.sendMessage({cmd: "checkFolder", folderId: folderId}, function(response) {
    // 打印出现的错误
    console.log(chrome.extension.lastError);
    // =>
    // Object {message: "The message port closed before a reponse was received."}
});

3、利用background.js(2)

除了使用同步ajax请求外,还可以这样处理:

1、在content.js中发送请求消息,在background.js中接收请求参数,发送异步请求到服务器,同时返回“正在请求”的消息;

2、background.js异步ajax请求完成后,向content.js发送消息,将异步ajax请求的结果作为参数发送给content.js;

3、content.js接收到消息之后,做出响应。

流程图

服务端调整

在做出以上调整之后,发现还是会有问题,不过这次是服务端的问题:

image

XMLHttpRequest cannot load http://lc.test/?op=check&id=uff5FOh4bLwrJ8oN. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'chrome-extension://fpapglfnhdkjcmcfhfipnkcfefnkobba' is therefore not allowed access.

意即:头信息不匹配。

在服务端入口代码顶部增加以下代码即可:

$referer = $_SERVER['HTTP_ORIGIN'];

if(preg_match("/chrome-extension:\/\/[a-z]{32}$/", $referer)){
	header('Access-Control-Allow-Origin:' . $referer);
}

其中,从chrome extension的background.js中发出的请求,请求头均为:chrome-extension://xxxx

这个xxxx就是插件的id,在插件页面有。

image

如果希望服务器端只接受这一个插件发送的请求,也可以这样:

$referer = 'chrome-extension://fpapglfnhdkjcmcfhfipnkcfefnkobba';
header('Access-Control-Allow-Origin:' . $referer);

需要注意的是,在chrome extension中,pem(私有密钥)文件决定一个插件的id。如果不希望有其他的插件请求发送过来,请保密、保存好pem文件。

在服务器端设置了header头信息,并不意味着连本域也无法访问。

如,服务器域名为:http://lc.test,在设置header之后,除了可以通过插件访问,也依然可以在本域(lc.test)下直接发起请求。

同样可以通过相同的方式对$_SERVER['HTTP_ORIGIN'] 进行过滤已防止此问题。

参考

日行一记

荒原听箫

转载自:荒原子作品《荒原听箫》

当所有的飞翔,端坐在荒原的额头。黑暗很直接地进入我的体内,一缕箫声,置身于凄凉的黄昏里,马背上,我听见风的心跳。

风总在我的上方吹过。马厩旁的火焰袅袅上升,在荒原,我隐约看到那个吹箫的人,在用迂回的箫声,迅速占领我可能想象的方位。

为谁祭献?为谁祭献你这颤抖的声音?

那个背对着我的人,像是最近的七月,用自己悠长的呼吸,任意切割着我的灵魂。箫声,用远距离的沟通,在苍茫中回响。

风大了起来。那个背对我吹箫的人,在含混得几近原始的荒原,一层层剥离黑暗的眼睛。所有的黑暗都只是被怜惜的寂静,但我却一直说不出那个吹箫人的名字。

风吹过马背,是谁?在迷惘的荒原,把一生的眷念交给延伸的遥远,风一般倾诉彻夜不眠的灵魂。我始终不知道那个背对我吹箫的人是谁?但他的箫声,我真的能听懂。夜一样的寂静,还有箫声淹没了我,我知道。

那管最后的箫,在荒原一隅的暗影里,我的一切喘息的神态,多么像那个吹箫的人。

是谁的心在风中颤抖?是谁的心在荒原饮泣?这风中噙满泪光的倾诉,这心灵微澜里的那一份永远的隽永。

含泪听箫。我和荒原,立于今夜。我们厮守。最后,歌尽而去。

怀着敬畏的心情将它打上,我们一起厮守。

赌命

雨巷

- 01 -

在我上下班的路上,总是经过一条巷子。

我不知道它叫什么,不过我管它叫雀神巷。因为在这条风光的巷子中住着的,都是雀神。

它出自老城区,四周有四通八达的小巷,但唯独只有它,很特别。

早上七点多,我经过它,能听见无数的喧哗和碰撞声,从每家每户传出来。从偶尔发出的哈欠声中,我了解到,这是一群熬过漫漫长夜的人。

下午六点多,我再次经过雀神巷。我想恐怕人都是要休息的吧?非也。事实告诉我,这是一群执着的人。

加班的日子,晚上十点经过。

聚会的日子,凌晨两点经过。

这是一群可怕的人,贪婪的人。

- 02 -

我很好奇,为什么住在这样一条破旧巷子里的人,可以不眠不休地打麻将呢?

我开始观察他们。

巷子里的路很破旧,坑坑洼洼。道路两边到处都是垃圾,或者垃圾堆。有些人在窗口上贴上了“丢垃圾死全家”的恶毒咒语,但这依然挡不住垃圾。

前段时间听说了一个词,叫“破窗效应”。实验表明,如果房子的窗户一旦破了不及时修复,那么这座房子将越来越破旧。

恐怕这也是如此,一旦有人习惯性地朝你的窗下扔了第一个垃圾,这个窗口成为垃圾堆的概率会相当高。

巷子口有三两小卖铺,它们不像大街上的小超市,没有华丽的装潢,商品也不齐全,仅仅一排货架在身后,一个储烟的货柜在前,一个旧冰箱横在旁边。就算是这样,却总有一群“膀爷”坐在小卖铺前,聊着天,打着扑克。

一两家的门前摆着板车,浓浓的花生香味和地瓜香味扑鼻而来。

大部分的家门都敞着,但是挂了布帘子,看不实际,只能听见响亮的碰撞声。

- 03 -

在政策的指导下,国家让一部分人先富了起来。

我有一个同事,90后,他的家里因为拆迁,得到了一笔补偿款。暂且称他为A君。

A君比较风趣。他有时候会冲着女同事的镂空针织衫喊:我知道你这个,叫洞洞毛衣!

A君明年就要结婚了,家里给安排了上百次相亲,他都觉得不错。有时候,他也问公司的女同事有没有合适的介绍对象。问到要求条件时,他无所谓地说:只要不超过20岁的漂亮点的大学生就行。

我们无聊的时候也会调侃A君:你的那10几个女朋友里,想好跟哪几个结婚了吗?A君总是回答:“愁啊,不知道怎么选……”

A君说,他想得其实很简单。房子家里有,车家里有,钱也花不完,只要找个媳妇过日子就行。

同事B君说:“现在孩子上学是个难题啊,学校难找,找到也得塞钱”。

A君在旁就回答说:“那有什么难的,扔老家让父母养不就好了。”

*- 04 - *

如果你家里有多套房,每个月收到的房租基本上足够一个月的生活开支。

如果你的银行账户里有一百万,每个月的利息也将近一万,足够一个普通家庭的日常开支。

有些人有钱之后,就过起了安居乐业的老年生活,就像雀神巷中的人们,不需要辛苦的工作就能“不劳而获”。有些人在其中获益不少,有些人亏了很多。

在我看来,他们不仅仅在赌钱,更多的是在赌自己的命。他们消磨的时光,是自己的人生,自己的生命。

也许,他们现在不愁吃穿用度,但迈入老年之后呢?有了子子孙孙之后呢?或许,他们的子孙们在问到老一辈的有趣故事时,就只有“自摸”了吧。

我无法从衣着品行去判断一个人的财产有多少,但雀神巷中或许也住着另外一群普通的劳动者。他们在雀神巷的熏陶之下,能抵挡住其中的诱惑吗?

住在雀神巷中的孩子们,你们放学之后是围在父母身边看电视、玩游戏还是围在父母身边看他们“碰”呢?

- 05 -

你的城市里 有这样一条雀神巷吗?

日行一记

Ubuntu中用SVN checkout单个文件

有时候会有只需要取出某个版本的单个文件的情况,在windows下可以方便的用小乌龟直接checkout这个文件,但是在Ubuntu命令行下使用时会报错:

> svn co http://svn.com/path/to/xx.php
svn: E200007: URL “http://svn.com/path/to/xx.php” 指向一个文件,不是目录

因此checkout命令只能针对文件夹操作。

正确的做法应该是这样的:

svn co --depth=empty http://svn.com/path/to/ pathname

以上命令会将文件所在的目录checkout下来,但是checkout下来的是一个空的目录,接着:

cd pathname && svn update xx.php

这样就能取出单个文件了

参考:

日行一记

如何让局域网其他用户访问虚拟机?

问题等效于:VMware虚拟机如何桥接?

虚拟机:VMware® Workstation 12 Pro

版本号:12.1.1 build-3770994

问题说明

在虚拟机上安装了一个Debian Server,在Debian上安装了PHP环境,IP地址是192.168...类型的,可以在本机(宿主机)上通过IP访问,但是局域网中其他成员不能访问。

出现此问题的原因是,我的虚拟机在创建完成之后默认使用的是NAT网络连接方式,这种连接方式只能允许我宿主机访问。

只需要将此虚拟机的连接方式改为桥接即可。

在桥接这种网络连接方式下,虚拟机可以获取到跟宿主机同一网段的IP地址,这样局域网中其他用户就可以通过IP访问虚拟机了。

虚拟机桥接

  • 打开虚拟机之后找到右下角的网络适配器图标

image

  • 在弹出来的菜单选项里点击设置
  • 设置网络适配器为桥接模式(直接连接物理网络)

image

  • 重启虚拟机
## 重启命令
reboot
  • 重启虚拟机之后,查看虚拟机的IP地址,就可以发现是跟宿主机IP地址在同一网段了。
## 查看服务器IP
ifconfig

日行一记

Debian Server定时任务的使用

1. shell文件

  • .sh为后缀
  • #!/bin/bash开始

Demo: cron.sh

#!/bin/bash
echo $(date "+%H:%M:%S")
token="abc123456"
sh -c "cd /var/www/dist/Webroot/; /usr/bin/php index.php /Api/Cron/index/token/$token;"
  • 上面的代码表示输出当前时间的时分秒,并传递token参数给index.php
  • 上面命令为:进入/var/www/dist/Webroot目录,使用php命令调用index.php文件,并传递/Api/Cron/index/token/$token参数,其中$token为上一行设置的变量

1.1 变量定义

token="abc123456"

不能有空格,写成下面这种是报错的:

token = "abc123456"

1.2 日期的写法

time=$(date "+%Y%m%d %H:%M:%S")
  • 分(M)、秒(S)需要大写
  • date后要有空格

1.3 ThinkPHP框架使用定时任务

index.php /Api/Cron/index/token/$token
  • index.php为入口文件
  • 后面的是参数,分别是模块(Api)、控制器(Cron)、动作(index)、get参数(token=$token)
  • 必须先进入入口文件所在的目录,才能正确执行
  • ThinkPHP版本:3.2.3(其他版本未测试)

1.4 参考

2. 运行shell

2.1 步骤

  • 先确认cron.sh文件是否有执行权限
  • 进入到cron.sh文件所在目录
  • 执行/bin/sh ./cron.sh命令即可

2.2 注意点

  • 执行时,需要用./cron.sh不能是cron.sh,否则可能报错或不执行

3. Demo

## /Application/Api/Controller/CronController.class.php

<?php
namespace Api\Controller;
use Think\Controller;

/**
 * 定时任务控制器
 * @author lan
 */
/**
 * ================= cron.sh ========================
 #!/bin/bash
 token="abc123456"
 sh -c "cd /var/www/dist/; /usr/bin/php cron.php /Api/Cron/index/token/$token;"
 * ================= cron.sh ========================
 */
class CronController extends Controller{
	/**
	 * 检查权限
	 * token === "abc123456" && CRON_AVAILABLE === true
	 * @return bool
	 */
	private function authCheck(){
		return (isset($_GET['token']) && $_GET['token'] === "abc123456" && CRON_AVAILABLE === true) ? true : false;
	}

	/**
	 * 主方法
	 */
	public function index(){
		if(!$this->authCheck()){
			exit("Access Deny");
		}

		set_time_limit(0);
		
		// do crontab 
		
	}
	
}

4. 延伸

日行一记

PHP中cookie和session跨子域不同步解决方案

跨域

本篇从APP接口通信安全策略及OAuth 2.0中截取而来,并加上一些补充。

问题说明

如果一个域名下有多个子域名,如:www.demo.comdemo.comm.demo.com,这三个域名都是不同的,如果使用时不注意,在这三个域名上可能会出现这样的问题:

  • ajax请求失败,无法跨域请求资源,所有的请求都被浏览器block
  • 在某一个域下登录之后,到另一个域下已有的session和cookie都不可用,导致多次登录

解决方案

#1 修改cookie作用域

在操作cookie时,将cookie的域设置为 .demo.com 而不是当前域。

如果在ThinkPHP框架下,在操作cookie之前,设置cookie域即可:

<?php
	public function read(){
		C('COOKIE_DOMAIN', ".demo.com");  // 设置cookie域
		$coo = cookie("test");
		dump($coo);
	}

	public function write(){
		C('COOKIE_DOMAIN', ".demo.com");  // 设置cookie域
		cookie("test", "hahaha");
	}

如果不在框架下,则在setcookie()函数操作时,添加domain参数即可:

setcookie('test', 'value', time()+3600, '', '.demo.com');
  • setcookie语法

setcookie(name,value,expire,path,domain,secure)

#2 修改session的cookie域

如果在ThinkPHP框架下,则找到 /ThinkPHP/Common/functions.php 文件中的session管理函数

在函数开头添加代码:

// 如果配置了session主域名,则设置
if(C('SESSION_MAIN_DOMAIN')) ini_set('session.cookie_domain', C('SESSION_MAIN_DOMAIN'));

修改后的函数部分代码:

function session($name='',$value='') {
    $prefix   =  C('SESSION_PREFIX');

    // 如果配置了session主域名,则设置
    if(C('SESSION_MAIN_DOMAIN')) ini_set('session.cookie_domain', C('SESSION_MAIN_DOMAIN'));

    if(is_array($name)) { // session初始化 在session_start 之前调用
    // 后面的代码省略了...

并在/Application/Common/Conf/config.php配置文件中,添加:

'SESSION_MAIN_DOMAIN'	=>		'mydomain.com'

修改完之后,就可以正常使用了:

<?php
	public function read(){
		$coo = session('test');
		dump($coo);
	}

	public function write(){
		session('test', '123456');
	}

如果不是在ThinkPHP框架下,则尝试在session_start()之前,加入 ini_set('session.cookie_domain', 'domain.com'); ,例如:

<?php
	public function read(){
		ini_set('session.cookie_domain', 'lc.dist2');
		session_start();
		dump($_SESSION['test']);
	}

	public function write(){
		ini_set('session.cookie_domain', 'lc.dist2');
		session_start();
		$_SESSION['test'] = 'aaaaaa';
		dump($_SESSION['test']);
	}

如果不能使用ini_set函数,则尝试在php.ini中设置。

日行一记

CSS 表单input控件禁用效果

效果:

image

image

<input type="text" placeholder="短信验证码" id="smscode" name="smscode" disabled>

<style>
#smscode[disabled]{
  cursor: not-allowed;
}
</style>

日行一记

windows 如何查看电脑uuid

13559

众所周知,iOS设备、Android设备都有一个设备唯一识别号,称之为UUID/UDID。那么windows有吗?

答案是,有的。

查看

  1. win+r 打开运行窗口
  2. 输入cmd 打开命令行窗口
  3. 输入:wmic csproduct list full
wmic csproduct list full

结果:

Description=Computer System Product
IdentifyingNumber=79K1JD2
Name=OptiPlex 3040
SKUNumber=
UUID=4C4C4544-0039-4B10-8031-B7C04F4A4432
Vendor=Dell Inc.
Version=

其中的 UUID 就是windows电脑的uuid了。

日行一记

ThinkPHP3.2 压缩前端HTML

利用ThinkPHP的Behavior行为拓展,对输出的HTML代码进行压缩,包括:过滤HTML注释、多余的空格、换行、js单行注释、js多行注释等。

效果:

image

1. 行为类文件

文件:/Application/Home/Behaviors/CommentFilterBehavior.class.php

<?php
namespace Home\Behaviors;
/**
 * 去掉HTML中的注释,压缩HTML
 * 在模板内容解析标签位使用
 * @author lan
 */
class CommentFilterBehavior extends \Think\Behavior
{
    //行为执行入口
    public function run(&$param){        
    	if(C('HTML_COMMENT_FILTER') == true){
    		/**
    		 * 生成的模板去掉html注释内容 <!-- -->
    		 */
    		$param = preg_replace("/<!--[\s\S]*?-->/", '', $param); // ?表示非贪婪模式,匹配尽可能少
    		/**
    		 * 去掉js注释,包括单行注释和多行注释
    		 */
    		$param = preg_replace("/\/\*[\s\S]*?\*\//", '', $param);
    		// $param = preg_replace("/\/\/.*/", '', $param); // 会将url中的//也替换掉,不可取
    		// 改成在script标签里执行,其他地方不执行
    		$param = preg_replace_callback("/<script(.*?)>([\s\S]*?)<\/script>/i", function($match) { if($match[1] != "" && trim($match[2]) == ""){ return $match[0]; }elseif(trim($match[2]) != ""){ return '<script'. $match[1] . '>' . preg_replace("/([^:])\/\/.*/", "$1", $match[2]) . '</script>'; } }, $param);
    		/**
    		 * 去掉换行和空格/tab
    		 */
			$param = str_replace("\r","",$param);
			$param = str_replace("\n","",$param);
			$param = str_replace("\t","",$param);
			$param = str_replace("\r\n","",$param);
			$param = preg_replace("/\s+/", " ", $param); //过滤多余空格
    	}
    }
}

2. 配置

tags配置:/Application/Common/Conf/tags.php

<?php
    return array(
        # 其他配置
        // 去掉HTML中的注释,压缩HTML
        'view_filter' => array('Home\\Behaviors\CommentFilterBehavior'),
    );
?>

config配置: Application/Common/Conf/config.php

增加以下配置项:

'HTML_COMMENT_FILTER'   =>  true,  // 是否在编译模板时把html注释删除

不需要使用时,将HTML_COMMENT_FILTER设置为false即可。

3. 注意

需要特别注意的是,如果HTML模板中有js代码,且没有很好的闭合,在压缩之后可能导致js失效。

比较常见的忘记闭合的场景有:定义变量后没有加;等。

$(function(){
    var h,w; // 这里的;必须加上
}); // 这里的;也必须加上,否则会报错

日行一记

GPS和大数据

GPS意即全球定位系统(Global Positioning System),已经有五六十年的历史了。它利用定位卫星,在全球范围内实时地进行定位和导航。具有全方位、全天候、全时段、高精度的特点。

在《一本书读懂大数据》中,提到大数据为生产、生活带来的改变。

1、 GPS不能做到完全、精确定位,往往会有几十米甚至更大的误差。

只要加上地图数据,便可以解决此问题。

2、 由于GPS的核心是卫星定位,这就导致GPS比较容易受到外界环境的影响。在天上的卫星们时时刻刻都在移动,在同一个位置上,可能白天信号很强,到了晚上却完全没有信号。甚至有时候可能连续几天定位状况都不好。

这个时候在惯性导航系统的帮助下,可以解决此问题。

惯性导航系统是利用惯性元件(加速度计、运动传感器)测量运动体本身的加速度,经过计算得到速度和位置,从而达到导航定位目的的系统。

3、 由于惯性元件(运动传感器)在室内会有一定的偏差,惯性导航系统会存在一定的累积误差。加上办公室里会有一定的磁传感器干扰,加大这种误差。

这个时候只要将WiFi的室内定位与地图相匹配,就可以解决此问题。

我们在使用GPS这个强大的定位系统的时候,其实就已经在使用大数据为我们服务了。

以上内容仅用于涨姿势。如有错误,还请不吝指正。

日行一记

SVN冲突解决命令

当发生冲突时,对比之后对文件做了修改调整,提交时发现不能提交,此时svn status命令最后一行显示:

冲突概要:
  正文冲突:1

使用resolveresolved两个命令即可:

svn resolve --accept working ./Application/Admin/Controller/OrderController.class.php 
svn resolved ./Application/Admin/Controller/OrderController.class.php

如果在执行resolve时报错,提示工作副本被锁定:

svn:  警告: W155004: 工作副本 '/home/lan/Websites/ytshop/source/trunk/Application/Admin/Controller' 已经锁定。
svn: E155027: 处理一个或更多的冲突失败

此时需要使用cleanup命令清除锁定:

svn cleanup

执行完之后,再重新执行上面两个命令即可。

参考

日行一记

jQuery Ajax跨域请求之JSONP示例

概述

jQuery的getJSON方法可以通过HTTP GET方法请求数据。

在 jQuery 1.2 中,可以通过使用JSONP形式的回调函数来加载其他网域的JSON数据,如 "myurl?callback=?"。jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。

参数

url,[data],[callback]


  • url:发送请求地址
  • data:待发送 Key/value 参数
  • callback:载入成功时回调函数

示例

JavaScript:

$.getJSON("http://mypic.4host.cn/api.php?name=9mouth&callback=?", function(data){
	console.log(data);
})

callback参数由服务器端决定,可以是其他任意名,如:jsonpCallbackjsonpcallback 等均可,参数的值始终为 ? ,jQuery在发送请求时,会将此 ? 替换成一个 jQuery 打头的,带有时间戳的字符串。

Request URL:http://mypic.4host.cn/api.php?name=9mouth&callback=jQuery19104399626062952804_1487125609998&_=1487125609999

PHP后端

$output = array(); // 输出结果数组

if(isset($_GET['callback']) && $_GET['callback'] != ""){
	// 是jsonp请求
	echo $_GET['callback'] . '(' . json_encode($output) . ')';
}else{
	echo json_encode($output);
}

参数修改

如果参数名不为callback,而是jsonpCallback,则相应的代码为:

<script>
$.getJSON("http://mypic.4host.cn/api.php?name=9mouth&jsonpCallback=?", function(data){
	console.log(data);
})
<script>
<?php
    $output = array(); // 输出结果数组
    
    if(isset($_GET['jsonpCallback']) && $_GET['jsonpCallback'] != ""){
    	// 是jsonp请求
    	echo $_GET['jsonpCallback'] . '(' . json_encode($output) . ')';
    }else{
    	echo json_encode($output);
    }

日行一记

Windows自带的问题步骤记录器

效果:

记录完成将生成一个zip格式文件,压缩包中有一个mht格式的网页,记录了详细的截图和步骤,以供bug追踪和再现

步骤:

  1. win+r
  2. 输入psr或psr.exe即可

日行一记

如何使用mysqldump导出数据库数据

# mysqldump -u username -p password databasename > targetfilename

mysqldump -uroot -proot crowdfunding > /var/www/crowdfunding.sql

2018-01-08更新

如何仅导出表结构或表数据?

仅导出表结构

mysqldump -uroot -proot --no-data -d databasename > structure.sql

仅导出表数据

mysqldump -uroot -proot --no-create-db --no-create-info databasename > data.sql

仅导出某一个表的数据

mysqldump  -uroot -proot --no-create-db  --no-create-info "databasename" "tablename" > data_table.sql

日行一记

高并发和集群下,全局唯一id的生成策略

高并发和集群下,全局唯一id的生成策略

1. md5+uniqid 有一定重复的概率(uniqid基于微秒值)

$rand = md5(uniqid(md5(microtime(true)),true));

2. uuid/guid

  • 缺点:uuid、guid 太长,占空间,基于uuid建立索引效率很低
  • 石墨的guid demo:https://github.com/shimohq/guid
  • 官方uniqid()参考手册有用户提供的方法,结果类似:{E2DFFFB3-571E-6CFC-4B5C-9FEDAAF2EFD7}
function create_guid($namespace = '') {     
    static $guid = '';
    $uid = uniqid("", true);
    $data = $namespace;
    $data .= $_SERVER['REQUEST_TIME'];
    $data .= $_SERVER['HTTP_USER_AGENT'];
    $data .= $_SERVER['LOCAL_ADDR'];
    $data .= $_SERVER['LOCAL_PORT'];
    $data .= $_SERVER['REMOTE_ADDR'];
    $data .= $_SERVER['REMOTE_PORT'];
    $hash = strtoupper(hash('ripemd128', $uid . $guid . md5($data)));
    $guid = '{' .   
            substr($hash,  0,  8) . 
            '-' .
            substr($hash,  8,  4) .
            '-' .
            substr($hash, 12,  4) .
            '-' .
            substr($hash, 16,  4) .
            '-' .
            substr($hash, 20, 12) .
            '}';
    return $guid;
}

3. ThinkPHP中的生成uuid方法

# Org\Util\String

/**
 * 生成UUID 单机使用
 * @access public
 * @return string
 */
 function uuid() {
    $charid = md5(uniqid(mt_rand(), true));
    $hyphen = chr(45);// "-"
    $uuid = chr(123)// "{"
           .substr($charid, 0, 8).$hyphen
           .substr($charid, 8, 4).$hyphen
           .substr($charid,12, 4).$hyphen
           .substr($charid,16, 4).$hyphen
           .substr($charid,20,12)
           .chr(125);// "}"
    return $uuid;
}

4. 高并发 php uniqid 不重复唯一标识符生成方案

5. Mysql实现全局唯一ID

6. mysql全局唯一ID生成方案

7. mysql生成随机字符串

8. 在sql中生成随机数

select floor(rand()*50+1) as rd; # 生成1-50的随机数

select md5(rand()) as md; # 对随机数进行md5加密

select concat("abd",rand()*50) as num; # abd20.0393... 

select md5( concat("attachment_", rand()* 999999) ) as md; # md5('attachment_9283.0239384')

insert into `rand` values( null, md5( concat("attachment_", rand()* 999999) ) );

9. 在MySQL中循环执行sql

利用存储过程进行循环,需在sql命令行中执行

delimiter $$        # 修改结束符
drop procedure if exists wk; # 如果存在同名存储过程,先删除
create procedure wk() # 创建存储过程

begin

declare i int;

set i = 1;

while i < 101 do
insert into `rand` values( null, md5( concat("attachment_", rand()* 10 ) ) );
set i = i + 1;
end while;

end $$

delimiter ;  # 改回结束符
call wk();   # 调用存储过程

日行一记

PHP中各种content-type设置

// 来源:http://www.oschina.net/code/piece_full?code=48926

//定义编码
header( 'Content-Type:text/html;charset=utf-8 ');
 
//Atom
header('Content-type: application/atom+xml');
 
//CSS
header('Content-type: text/css');
 
//Javascript
header('Content-type: text/javascript');
 
//JPEG Image
header('Content-type: image/jpeg');
 
//JSON
header('Content-type: application/json');
 
//PDF
header('Content-type: application/pdf');
 
//RSS
header('Content-Type: application/rss+xml; charset=ISO-8859-1');
 
//Text (Plain)
header('Content-type: text/plain');
 
//XML
header('Content-type: text/xml');
 
// ok
header('HTTP/1.1 200 OK');
 
//设置一个404头:
header('HTTP/1.1 404 Not Found');
 
//设置地址被永久的重定向
header('HTTP/1.1 301 Moved Permanently');
 
//转到一个新地址
header('Location: http://www.example.org/'); 
//文件延迟转向:
header('Refresh: 10; url=http://www.example.org/');
print 'You will be redirected in 10 seconds';
 
//当然,也可以使用html语法实现
// <meta http-equiv="refresh" content="10;http://www.example.org/ />
 
// override X-Powered-By: PHP:
header('X-Powered-By: PHP/4.4.0');
header('X-Powered-By: Brain/0.6b');
 
//文档语言
header('Content-language: en');
 
//告诉浏览器最后一次修改时间
$time = time() - 60; // or filemtime($fn), etc
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $time).' GMT');
 
//告诉浏览器文档内容没有发生改变
header('HTTP/1.1 304 Not Modified');
 
//设置内容长度
header('Content-Length: 1234');
 
//设置为一个下载类型
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="example.zip"');
header('Content-Transfer-Encoding: binary');
// load the file to send:
readfile('example.zip');
 
// 对当前文档禁用缓存
header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
header('Pragma: no-cache');
 
//设置内容类型:
header('Content-Type: text/html; charset=iso-8859-1');
header('Content-Type: text/html; charset=utf-8');
header('Content-Type: text/plain'); //纯文本格式
header('Content-Type: image/jpeg'); //JPG***
header('Content-Type: application/zip'); // ZIP文件
header('Content-Type: application/pdf'); // PDF文件
header('Content-Type: audio/mpeg'); // 音频文件
header('Content-Type: application/x-shockw**e-flash'); //Flash动画
 
//显示登陆对话框
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Top Secret"');
print 'Text that will be displayed if the user hits cancel or ';
print 'enters wrong login data';

HTTP Content-type 对照表

日行一记

Ubuntu16.04将openvpn客户端设置为开机启动项

问题

公司内网需要使用vpn登录打开,所以每次开机都需要手动开启openvpn登录,并且总有一个终端窗口在,有时候觉得麻烦。

解决方法(步骤)

1. shell脚本

平时使用时,基本上都是这样操作的:

cd /path/to/key_dir
sudo openvpn client.ovpn

之后再输入密码,shell脚本也一样:

openvpn-init.sh

#!/bin/bash

cd /path/to/key_dir && sudo openvpn --config ./client.ovpn

exit 0

Tip :当使用开机启动项时,是有足够的权限的,所以此时不会弹出窗口让你输入用户密码。

2. 配置启动项

所有的启动项都在 /etc/init.d/ 目录下,先将脚本复制到这个目录下:

sudo cp openvpn-init.sh /etc/init.d/

将文件权限设置为 755

cd /etc/init.d
sudo chmod 755 openvpn-init.sh

然后再配置:

sudo update-rc.d openvpn-init.sh defaults 88

其中88表示执行顺序,值越大表示越迟执行。一般在执行多个脚本且多个脚本之间有前后顺序的情况下比较有用。

3. 重启电脑

4. 附

移除Ubuntu开机脚本:

sudo update-rc.d -f openvpn-init.sh remove

参考

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.