反引号 (`) 与 单引号 (') 以及 双引号 ('') 的使用

反引号 ` (backtick) 与单引号 '(single quote) 以及双引号 " (double quote) 在 PHP 及 MySQL 中都有不同的含义,下面将分别说明并给出它们在 PHP 和 MySQL 结合使用时的一些注意事项。

在 PHP 中

` 在 PHP 中是 执行运算符,效果与调用 shell_exec() 相同 。

' 在 PHP 中用来输出字面量值,被单引号括起来的内容会被原样输出。

" 在 PHP 中可以用来解析变量。

在 MySQL 中

` 被用来在查询,告诉解析器反引号内的内容表示一个字面量,直接读取而不用做变量替换。

"' 用来均被用来解析 MySQL 字符串及 特殊字符 ,不过这与 MySQL 的 SQL mode 设置有关。当 ANSI_QUOTES 开启时," 将被当作标识符。

SQL mode

MySQL 中开启 ANSI mode 时将会使用 ' 来告诉解析器,' 内包裹的字符串字面量直接读取,无需解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> SET SESSION sql_mode ='ANSI_QUOTES';
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT 'hello', '"hello"', '""hello""', 'hel''lo','\'hello','\r';
+-------+---------+-----------+--------+--------+---+
| hello | "hello" | ""hello"" | hel'lo |'hello | |
+-------+---------+-----------+--------+--------+---+
|hello | "hello" | ""hello"" | hel'lo |'hello |
+-------+---------+-----------+--------+--------+---+
1 row in set (0.01 sec)

mysql> SELECT "hello", '"hello"', '""hello""', 'hel''lo','\'hello','\r';
ERROR 1054 (42S22): Unknown column 'hello' in 'field list'

mysql> SELECT hello, '"hello"', '""hello""', 'hel''lo','\'hello','\r';
ERROR 1054 (42S22): Unknown column 'hello' in 'field list'

mysql> SELECT `hello`, '"hello"', '""hello""', 'hel''lo','\'hello','\r';
ERROR 1054 (42S22): Unknown column 'hello' in 'field list'

PHP 执行 MySQL 查询

为了防止 SQL 注入,最好使用预处理语句及参数绑定来执行查询。建议使用 PDO 或者 MySQLi

  • PDO

    1
    2
    3
    4
    5
    <?php    
    $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
    $stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
    $stmt->bindParam(':name', $name);
    $stmt->bindParam(':value', $value);
  • MySQLi

    1
    2
    3
    4
    <?php
    $mysqli = new mysqli("localhost", "user", "password", "database");
    $stmt = $mysqli->prepare("INSERT INTO `test`(`id`) VALUES (?)");
    $stmt->bind_param("i", $id)

实践

SQL 标准规定使用 ' 来包裹字符串,但不同的数据库有不同的实现,最好的方式,当然是使用 PDO ,这样就不用对 sql 语句单独进行处理,而且在转换数据库时业务逻辑中的 sql 也无需修改和转换。

最好的实践其实应该在代码设计上规避错误:

  1. 取名不与 MySQL 保留字 冲突
  2. 清楚 MySQL 服务器 SQL mode SELECT @@sql_mode
  3. 使用 ` 包裹表名及列名
  4. 使用 ' 包裹查询条件

解决 PHP "headers already sent" 错误

这篇文章是 stackoverflow 上的一个问题的回答,答案集中了其它答案的想法,并做了归纳,所以翻译过来,以后遇到 “headers already sent” 的错误就可以直接一一排查,或者干脆在代码动手及设计上避免这类问题了。

发送 header 前不要有任何输出

发送或者修改 HTTP 头信息的方法必须在任何输出被输出之前被调用。否则调用将会出错:

Warning: Cannot modify header information - headers already sent (output started at script:line)

这些方法可以修改(modify) HTTP 头信息:

输出(output)可以是:

  • 无意的:
    • <?php 之前或者 ?> 之后的空格
    • UTF-8 BOM
  • 有意的:
    • printecho 以及其他能产生输出的方法
    • <?php 前原始的 <html> 区块

为什么这个错误会产生

为了理解为什么 HTTP header 必须在输出之前发送出去,我们有必要了解看一下一个典型的 HTTP 相应。PHP 脚本主要用来生成 HTML ,但它也会发送一系列的 HTTP/CGI 头信息到 web 服务器:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Powered-By: PHP/5.3.7
Vary: Accept-Encoding
Content-Type: text/html; charset=utf-8

<html><head><title>PHP page output page</title></head>
<body><h1>Content</h1> <p>Some more output follows...</p>
and <a href="/"> <img src=internal-icon-delayed> </a>

页面或者输出总是紧跟在头信息后面。PHP 必须先把头信息发送给 web 服务器,并且它只能发送一次,在这之后就再也不能修改头信息了。

当 PHP 第一次接收到输出时(print ,echo,<html>) 它会清掉所有收集到的头信息。在此之后它能把输出所有想输出的内容,但是再想发送 HTTP 头信息就不可能了。

怎么找到到底是哪里提前产生了输出?

header() 头信息包含所有与问题产生相关的信息:

Warning: Cannot modify header information - headers already sent by (output started at /www/usr2345/htdocs/auth.php:52) in /www/usr2345/htdocs/index.php on line 100

在上面的警告中,line 100 指向调用 header() 失败的脚本行数。

圆括号里的 output started 这条信息更加重要。它指出了先于 header() 前的输出的源头。在这个例子中是 auth.php 的 第 52 行,这就是你要去找的过早的输出的地方。

典型的原因有这些:

  1. print,echo
    有意的 printecho 语句输出将会中断输出 HTTP 头信息的机会。应用程序流必须重组以避免这种行为,可以使用 function 和模版来重组,从而保证 header() 调用是在信息被写出之前。
    产生输出的方法包括:

    • print, echo, printf, vprintf
    • trigger_error, ob_flush, ob_end_flush, var_dump, print_r
    • readfile, passthru, flush, imagepng, imagejpeg

    以及其他用户自定义的方法。

  2. 原始的 HTML
    在一个 PHP 文件中未被解析的 HTML 区块也是输出。脚本中各种可能触发调用 header() 的条件都必须在任何 <html> 区块前声明。

  3. <?php 前的空格导致的 "script.php line 1" 警告
    如果警告指向第 1 行的输出,那么它很有可能指向的是在 <?php 之前的空格,文本或者 HTML 。

    1
    2
     <?php
    // 在 <?php 前有个空格

    同样它可能出现在附加的脚本或者脚本区块上:

    1
    2
    3
    ?>

    <?php

    PHP 确实在闭合标签后占据了一个换行符,但是它不会在上面的空白处插入换行符、制表符或者空格(也就是说这是我们自己造成的)。

  4. UTF-8 BOM
    换行符或者空格可能导致问题,但是不可见的字符序列同样可以。最著名的就是大多数文本编辑器并不会显示的 UTF-8 BOM 。它是在 UTF-8 编码的文档里可选甚至是多余的,被标示为 EF BB BF 的字节序列。但是 PHP 必须把它当作原始的输出来处理。它可能以  这样的符号输出(如果客户端以 Latin-1 来解释这个文档)或者其他这样的“非法输出”。
    以某种图形化的编辑器或者基于 JAVA 的 IDE 查看这类文件时,你可能察觉不到 UTF-8 BOM 的存在。它们没有把 UTF-8 BOM 形象化(受制于 Unicode 标准)。然而大多数程序编辑器和控制台编辑程序会这样处理:

    像这样就能简单地提早发现问题了。其他的编辑器在设置某些选项后也能纠正这样的问题(Windows 上的 Notepad++ 可以识别并且 纠正 BOM 问题 ),另一个发现 BOM 的方法就是借助十六进制的编辑器。在 *nix 系统上,大都提供了 hexdump ,如果没有的话,其他图形化的变种也可以用来简化审计这些问题的步骤:

    一个简单的修正方法就是将文本编辑器设置为 以 UTF-8 (no BOM) 保存文件save files as UTF-8 (no BOM))或者其他类似的设置。

    修正程序

    有很多自动化的工具可以检测并修改文本文件(sed / awk 或者 recode )。PHP 里有 phptags 。它可以把打开标签和关闭标签重写成长标签(<?php)或者短标签(<?)的形式。也可以轻松地解决前导或尾随的空格、Unicode 和 UTF-x BOM 问题:

    1
    phptags  --whitespace  *.php

    同样,你可以在某个目录或整个项目目录使用这个命令。

  5. ?> 后的空白
    如果错误代码在闭合标签 >? 这一行的前面,那么这就是 >? 后的空格或者原始文本输出导致的问题。PHP 的结束标记并不会在遇到闭合标签时终止执行脚本,任何 ?> 之后的文本或者空格字符都会被当作页面内容输出。
    通用的被鼓励的做法,特别是针对新手,是避免在 PHP 文件后加上闭合标签 ?> 。这样就能避免一部分产生这类问题的情况。

  6. 错误源提示:”Unknown on line 0”
    如果没有给出具体的错误源,那么这就是典型的 PHP 扩展或者 php.ini 设置的问题:

    • 偶尔是 gzip 编码设置或者是 ob_gzhandler
    • 也有可能是 php.ini 设置里模块加载了两次导致 PHP 产生了启动 / 警告信息
  7. 先前的错误导致输出了错误信息
    如果前面的 PHP 语句或者表达式造成了 warning 或者 notice 信息导致输出,这些输出也被认为是过早地输出。
    在这种情况下你需要避免错误,推后这些语句的执行,或者抑制这些信息的输出,可以使用 isset() 进行判断,或者使用抑制符 @,前提是它们不会阻止后续的调试。

没有错误信息输出

如果你禁用了 php.ini 里的 error_reporting 或者 display_errors 设置,那么将不会产生 warning 。但是忽略错误并不会让问题消失,头信息仍然不能在过早的输出输出之前发送出去。

所以当 header("Location: ...") 跳转静默地失败时,建议你去查看 warnings 。在脚本的最前面用下面的两条命令重新开启错误报告设置:

1
2
error_reporting(E_ALL);
ini_set("display_errors", 1);

或者如果其他的设置都失败了那就设置 set_error_handler("var_dump");

至于跳转的 header ,在执行至最后的代码时你应该遵循下面的这种风格:

1
exit(header("Location: /finished.html"));

最好是提供一个方法,特别是当 header() 执行失败时打印出用户信息。

变通方法:输出缓冲

PHP 的输出缓冲的方法是缓解这种问题的一种变通方法。它运行起来可靠,但是你绝不要使用它来替代你架构良好应用程序结构,从控制逻辑中分离输出。它的真实目的是用来减轻大块数据传输至服务器时的压力。

  1. output_buffering 设置php.ini 或者 .htaccess 或者甚至在最新的 FPM/FastCGI 的 .user.ini 中设置;
  2. 同样你可以在脚本的最前面使用 ob_start() 来设置,但是它并没那么可靠:
    • 即使 <?php ob_start(); ?> 在第一个脚本里,空格或者 BOM 也有可能在此之前被输出
    • 它可以隐藏 HTML 输出里的空格(将空格放到 buffer 中),但是只要应用程序逻辑企图发送二进制内容(比如生成的图片),缓冲里的无关的输出就会成为问题(这样 ob_clean() 方法就成为下一步的变通方法了)。
    • 缓冲有大小限制,并且在默认配置下很容易超出,并且这种情况并不少见,一旦发生也不太容易追踪。

因此这两个方法变得不可靠了,特别是当你需要更改开发环境或者生产环境的配置的时候。这就是为什么输出缓冲被认为只是一种蹩脚的变通方法。

建议参考官方手册里的基本 使用方法 ,以及它的优缺点:

但是在其他的服务器上是好的?

如果你之前没有收到过头信息的 warning ,那么 php.ini 里的 output_buffering 设置改变了。在现在的/不同的服务器上很有可能没有设置。

使用 headers_sent() 检查

你可以使用 headers_sent 来检查是否可以发送头信息。这种方法可以有效地检查以便输出一个错误信息或是应用其他的逻辑。
不错的回退变通方法有:

  • HTML<meta> tag
    如果你的应用程序很难在结构上解决这个问题,有个简单但显得不专业的做法是在 HTML 标签中来跳转网页。可以这样实现:

    1
    <meta http-equiv="Location" content="http://example.com/">

    或者加上一个延迟时间

    1
    <meta http-equiv="Refresh" content="2; url=../target.html">
  • JavaScript 跳转
    另一个可选的方法就是使用 JavaScript 跳转 来实现网页跳转:

    1
    <script> location.replace("target.html"); </script>

这种方式相比较 方法起来更兼容 HTML 标准,它只依赖于可以运行 JavaScript 的客户端。

这两种方式在 HTTP header() 调用失败时都提供了可以接受的回退方式。理想化的处理方式应该是将跳转与其它方式结合,给出对用户友好的辅助信息并且提供一个可点的链接以供后续操作。

为什么 setcookie()session_start() 都会被影响

setcookie()session_start() 都需要发送一个 set-cookie: 的 HTTP 头信息。这种情况就和前面输出 header() 的情况类似,所以同样会出现由于过早地输出错误信息导致的错误。

(当然它们受影响也有可能是因为客户端禁止了 cookie 导致的,设置可能是代理的问题。很明显,session 也取决去剩余磁盘空间大小或者 php.ini 里的其它设置)


原文链接:http://stackoverflow.com/questions/8028957/how-to-fix-headers-already-sent-error-in-php

理解 Exit Code 并学会如何在 Bash 脚本中使用

Exit Codes 是什么

在 Unix 和 Linux 系统中,程序可以在执行终止后传递值给其父进程。这个值被称为退出码(exit code)或退出状态(exit status)。在 POSIX 系统中,惯例做法是当程序成功执行时传递 0 ,当程序执行失败时传递 1 或比 1 大的值。

传递状态码为何重要?如果你在命令行脚本上下文中查看状态码,答案显而易见。任何有用的脚本,它将不可避免地要么被其他脚本所使用,要么被 bash 单行脚本包裹所使用。特别是脚本被用来与自动化工具 SaltStack 或者监测工具 Nagios 配合使用。这些工具会执行脚本并检查它的状态,来确定脚本是否执行成功。

其中最重要的原因是,即使你不定义状态码,它仍然存在于你的脚本中。如果你不定义恰当的退出码,执行失败的脚本可能会返回成功的状态,这样会导致问题,问题大小取决于你的脚本做了什么。

如果不指定退出码会发生什么

在 Linux 里,任何在命令行中执行的脚本都有退出码。在 Bash 脚本中,如果脚本里没有指定退出码,退出码将会是脚本最后一个命令执行后产生的状态码。为了更好地解释退出码,下面将给出一个脚本来说明。

脚本

1
2
3
#!/bin/bash
touch /root/test
echo created file

上面的脚本既会执行 touch 命令也会执行 echo 命令。当我们以非 root 用户执行这个脚本时 touch 命令将会执行失败,理想情况下,当我们执行 touch 命令失败时我们希望通过脚本的退出码来表明有命令执行失败。我们可以通过打印 Bash 的特殊变量 $? 来获取退出码。这个变量将会打印出脚本最后一个命令执行的退出码。

执行输出

1
2
3
4
5
$ ./tmp.sh 
touch: cannot touch '/root/test': Permission denied
created file
$ echo $?
0

你可以看到,当执行 ./tmp.sh 后退出码是 0 ,而 0 代表脚本执行成功,虽然 touch 命令执行失败了。上面的脚本执行了两个命令:touchecho。因为我们没有指定退出码所以脚本以最后一个命令执行后的状态码退出。在这个例子中,最后运行的是 echo 命令,这个命令确实执行成功了。

脚本

1
2
#!/bin/bash
touch /root/test

如果我们去掉脚本中的 echo 命令,我们将看到 touch 命令的退出码。

执行结果

1
2
3
4
$ ./tmp.sh 
touch: cannot touch '/root/test': Permission denied
$ echo $?
1

你可以看到,因为最后运行的命令是 touch ,所以脚本的退出码正确地反应了脚本的状态:执行失败。

在你的 Bash 脚本中使用退出码

当从我们的脚本中去掉 echo 命令后脚本跑通了并且返回了恰当的退出码。当我们想在 touch 命令执行成功执行一个操作,而在执行失败时执行另一个操作会发生什么。比如脚本执行成功时输出至 stdout ,执行失败时输出至 stderr 这种操作。

测试退出码

前面的代码中,我们使用了特殊变量 $? 来打印脚本的退出码。我们同样可以在脚本中使用它来测试 touch 命令是否执行成功。

脚本

1
2
3
4
5
6
7
8
#!/bin/bash
touch /root/test 2> /dev/null
if [$? -eq 0 ]
then
echo "Successfully created file"
else
echo "Could not create file" >&2
fi

在上面的修改版代码中,如果 touch 的退出码是 0 ,脚本将会输出成功的消息。如果退出码是除 0 以外的其他数字,这表示执行失败,脚本将会打印失败的消息到 stderr

1
2
$ ./tmp.sh
Could not create file

在程序中提供你自己的退出码

在上面的程序中,虽然 touch 命令执行失败时将会提供一个错误信息提示,但它仍给出了表示执行成功的状态码 0 。

1
2
3
4
$ ./tmp.sh
Could not create file
$ echo $?
0

既然脚本执行失败了,但它仍然传递执行成功的退出码给其他需要执行此脚本程序显然很不适合。为添加我们自己的退出码到这个程序里,我们可以简单地通过 exit 来实现。

脚本

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

touch /root/test 2> /dev/null

if [$? -eq 0 ]
then
echo "Successfully created file"
exit 0
else
echo "Could not create file" >&2
exit 1
fi

通过脚本里的 exit 命令,我们可以在 touch 命令执行成功时输出成功的消息并返回状态码 0 。当 touch 执行失败时我们将打印失败的消息至 stderr 并返回一个表示失败的状态码 1 。

执行结果

1
2
3
4
$ ./tmp.sh
Could not create file
$ echo $?
1

在命令行中使用退出码

现在我们的脚本既可以通知用户也能通知程序命令是否执行成功,我们可以使用这个脚本配合其他的管理工具,或者简单地通过 Bash 单行命令使用它。
Bash One Liner:

1
2
3
4
$ ./tmp.sh && echo "bam" || (sudo ./tmp.sh && echo "bam" || echo "fail")
Could not create file
Successfully created file
bam

上面的命令组使用了在 Bash 里称为 list constructs 的工具。它允许你通过 &&(代表 and) 和 || (代表 or) 将命令串到一起。上面的命令将会执行 ./tmp.sh 脚本,如果退出码是 0 命令 echo "bam" 将被执行。但如果 ./tmp.sh 的退出码为 1 ,圆括号里的命令将在之后被执行。圆括号里的命令通过 &&|| 被串到一起。

list constructs 使用退出码来知晓一个命令是否执行成功。如果脚本不恰当地使用退出码,其他使用更高阶的命令(比如 list constructs)调用此脚本的用户就可能得到与预期不符的结果。

更多关于退出码的信息

Bash 里的 exit 命令接受不了从 0 - 255 的整形数值,大多数情况下 01 就够用了,不过其他的数值也提供了,为更具体的错误信息做储备。The Linux Documentation Project 有一个不错的 保留状态码 列表,告诉大家这些状态码作何使用。


原文链接:http://bencane.com/2014/09/02/understanding-exit-codes-and-how-to-use-them-in-bash-scripts/

使用 Chrome 扩展程序 JSON Viewer 进行调试

PHP 调试时有很多种方法,其中最简单的无非是 echo 与 var_dump 、 print_r 以及 debug_backtrace 了。这篇文章要介绍的是平常用的比较多的,适合在调 HTTP 接口以及平常开发中需要输出变量内容的情况。其实在 IDE 中比较适合使用 XDebug 或者断点调试,这样能在运行时直接查看当前上下文的各个变量,不过这种情况不在此篇讨论之列,后续我也将介绍相关的方法,来完善这个系列。

现在步入正题。在平常和前端的配合中经常会有一些 RESTful 接口输出,而其中最常用的内容传输格式就是 JSON ,JSON 在 PHP 中可以很方便地转化为对象或者数组,在打印变量时使用 JSON 再配合浏览器的扩展进行解析,无疑能提高开发效率。

JSON

先用两张图来了解一下 JSON 的格式,这样就不会混淆其在 PHP 中所代表的意思。

对象是一个无序的“‘名称 / 值’对”集合。一个对象以“{”(左括号)开始,“}”(右括号)结束。每个“名称”后跟一个“:”(冒号);“‘名称 / 值’ 对”之间使用“,”(逗号)分隔。

数组是值(value)的有序集合。一个数组以“[”(左中括号)开始,“]”(右中括号)结束。值之间使用“,”(逗号)分隔。

值(value)可以是双引号括起来的字符串(string)、数值 (number)、true、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。

Chrome 扩展

JSON viewer 扩展的地址是 https://chrome.google.com/webstore/detail/json-viewer/gbmdgpbipfallnflgajpaliibnhdgobh

JSON view GitHub 官网介绍了其有如下 feature

  • 语法高亮
  • 23 个内置的主题
  • 节点可折叠
  • 可点击的 url
  • 在控制台「console」输入 json 回车中查看你的 JSON (通过访问 id 为 json 的 HTML 元素)
  • 显示行号选项
  • 在 url 中加入时间戳 header

等等。

PHP 配合 JSON viewer 进行输出调试

PHP 中用来处理 JSON 使用最多的是 json_encode 和 json_decode ,比如你有 array 有如下结构

1
2
3
4
5
6
7
8
$arr = array(
'key1' => 1,
1 => 'string',
'array' => array(
'value1',
'value2',
)
);

如果使用 var_dump 输出,那么长的是这样

1
2
3
4
5
6
7
8
9
10
11
Array
(
[key1] => 1
[1] => string
[array] => Array
(
[0] => value1
[1] => value2
)

)

当层级不够多,数组元素不够多,肉眼观察根本不是问题,但是一旦数据变得复杂,输出将会非常难看且不易查找,这时候一个美观的输出外带查看层级和数据复制功能会显得很方便,此时,你只需要 echo json_encode($arr);exit; ,扩展会自动解析 JSON 。

最终你得到的可能是这样的 UI:

好了,其实还有很多输出调试工具,比如 symfony 的 var-dumper 既能输出 HTML 的调试信息,又能在 cli 下输出带格式的调试信息。还有 debug helper Kint 、Yii 的 yii-debug-toolbar 以及强大的 XDebug 。


FaceBook 命令行 UI PathPicker

早先在逛 Facebook 后端开源项目时看到了一个命令行工具 PathPicker ,安装后试用了一下感觉还不错,恰好项目开发时在使用 Git ,发现结合 PathPicker 后免去了很多麻烦。

试想以下场景:

  1. 你完成一个功能会涉及到不少文件,你得小心翼翼地一个个文件选择,防止不必要的文件出现在同一个待提交列表中;

  2. 你改了很多文件,然后执行了 git add . ,这时你有两个选择,要么 git rest 不想在这次提交中包含的文件,要么一个个选择,然后 添加到 git commit 的后面。

于是 PathPicker 出现了,专为解决此种费力不讨好的事情,UI 选择器,命令行,一起上。

介绍

FaceBook PathPicker 是一个简单的命令行工具,用来处理选择 bash 文件输出时的问题。

PathPicker 做以下事情:

  • 通过管道处理输入中所有表现出文本特性的文件
  • 在一个方便选择的 UI 容器里展现管道输入
  • 然后你可以对输入做以下事情:

    • 用你最喜欢的编辑器处理选择的所有文件
    • 利用输入执行任意命令

下面通过一段视频来了解 PathPicker:

PathPicker 是怎么运行的

PathPicker 是 bash 脚本和一些 Python 模块 结合后的产物。 它主要有以下三个步骤:

  1. 首先在 bash 脚本中,它将所有的标准输入重定向到一个 Python 模块,然后通过解析取出其中所包含的文件名候选列表。每个候选文件名都将被用来与文件系统做比对确保其存在,再然后,会把处理结果保存至临时文件并终止 python 脚本的运行。
  2. 第二部,bash 脚本会切换至命令行输入模式,并且另一个 python 模块会读取出已保存的条目并使用 curses 在一个 UI 选择器展现它们。
  3. 最后,python 脚本会输出一条命令至 bash 文件,最终被原始的 bash 脚本解析执行。

使用示例

当改了很多文件需要提交提交时,查看当前状态 git status

git add before

将输出重定向至 PathPicker git stataus | fpp

git status|fpp

  • 已选中为绿色
  • 已选中且当前光标悬停显示为红色
  • 未选中且当前光标悬停显示为蓝色
  • 无法选中的光标无法跳到此项,显示为纯文本

提交选定的文件 git commit $F

git commit $F

PathPicker 官网见 http://facebook.github.io/PathPicker/


Git 常用操作


Git 忽略已追踪文件的修改

git update-index –assume-unchanged /path/to/file

Git 取消忽略文件的修改(再次追踪此文件)

git update-index --no-assume-unchanged /path/to/file

导入 Git 仓库至 Bitbucket

  1. create repository in Bitbucket
  2. do upstreaming in local
1
2
3
4
cd /path/to/my/repo
git remote add origin https://username@bitbucket.org/username/reponame.git
git push -u origin --all # pushes up the repo and its refs for the first time
git push -u origin --tags # pushes up any tags`

分支重命名

  1. 修改当前分支 git branch -m <newname>
  2. 未提交至 remote git branch -m <oldname> <newname>
  3. 已提交至 remote 需新加分支 git push origin <newname>:<newname> 并删除旧的远程分支 git push origin :<newname>

从某个分支上线

场景:当需要从某个分支的指定版本上线,又需要保留当前分支的修改

  1. checkout 到指定分支 git checkout [revision] [revision] 代表指定 commit 的 hash
  2. 以当前 commit 创建分支 git checkout -b [branchName] [branchName] 为分支名
  3. 推送至远程 git push origin branchName:branchName

添加多个远程分支

1
2
git remote set-url origin --push --add <a remote>
git remote set-url origin --push --add <another remote>

错误 merge 了某个分支后撤销

场景:当某个功能开发并自测完毕,需要 merge 到测试环境进行测试时,如果不小心 merge 错了分支,这时候需要撤销这个 merge 。如果是使用 Pull Request 进行分支合并的话,直接 close 就可以了,但如果是使用命令行进行操作的话,本地的分支实际上已经与待 merge 的分支进行合并了,需要回到 merge 前。

只需要两个一个操作:找到 merge 前的 commit hash ,并 reset –hard

1
git reset --hard <commit hash>

强制提交,覆盖错误的提交

场景:如果不小心将某个提交推送到远程,又不想这个提交留在历史里,就需要强制覆盖远程。

1
2
git checkout -b <branchName> <commit hash> # commit hash 为错误提交的前一个提交 hash
git push origin branchName -f # 强制提交

备注:对于个人开发或者比较私密的信息不小心提交了才建议这么做,否则还是直接 revert 错误的提交。

MySQL 单机多实例主从

environment

[root@iZ25qtxg0q6Z ~]# uname -a
Linux iZ25qtxg0q6Z 2.6.32-431.23.3.el6.x86_64 #1 SMP Thu Jul 31 17:20:51 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
[root@iZ25qtxg0q6Z ~]# cat /etc/redhat-release
CentOS release 6.7 (Final)
[root@iZ25qtxg0q6Z ~]# mysql –version
mysql Ver 14.14 Distrib 5.1.73, for redhat-linux-gnu (x86_64) using readline 5.1

set-up replication steps

  1. enable binary logging of master
  2. stop writing FLUSH TABLES WITH READ LOCK;
  3. obtain binary log file name and position for slave
  4. transfer data from master to slave (mysqldump 、 mysqlimport)
  5. on the master UNLOCK TABLES;
  6. Setting the Replication Slave Configuration

    1
    2
    3
    4
    5
    6
    CHANGE MASTER TO
    MASTER_HOST='master_host_name',
    MASTER_USER='replication_user_name',
    MASTER_PASSWORD='replication_password',
    MASTER_LOG_FILE='recorded_log_file_name',
    MASTER_LOG_POS=recorded_log_position;

  7. check master and slave status SHOW VARIABLES LIKE 'server_id'

  8. in slave START SLAVE

安装过程中的命令及问题解决方法

mysql_secure_installation

mysql_secure_installation 作用:

  • set root password,
  • disallowing root login remotely,
  • removing anonymous user accounts after first installation
  • removing test database which can be accessed by any users

但是 mysql_secure_installation 无法指定命令参数(5.7 可以指定参数以后可以直接指定,之前的版本无法指定),会使用默认的配置,导致新安装的实例安全配置无法更新
配置位置 /var/lib/mysql/mysql2.sock

有两种方法可以解决执行 mysql_secure_installation 时应用到指定实例:
[root@iZ25qtxg0q6Z ~]# which mysql_secure_installation /usr/bin/mysql_secure_installation

  1. 在配置文件中制定,修改为对应的实例配置
  2. 使用 软链

启动 mysql instanse

mysqld_safe --defaults-file=/etc/my2.cnf

主从数据保持一致(导出导入)

  1. 导出数据 mysqldump --all-databases --master-data --events &gt; dbdump.db -p
  2. 导入至指定实例 mysqlimport -uroot -P 3380 -h 127.0.0.1 dbdump.db -p

stop mysql instance by specifing sockct

mysqladmin -S /var/lib/mysql/mysql2.sock shutdown -p

  1. mysql 安全设置
  2. 使用软链
  3. kill process
  4. config slave error handlering
  5. restore mysqldump file
  6. reset mysql root password
  7. create-multiple-mysql-instance
  8. multiple-mysql-servers
  9. Setting Up Replication with New Master and Slaves

MySQL 索引类型


有必要对数据库的索引类型及其实现做个了解及总结。

Question:

  1. 书中所说索引分为单列索引,主键索引以及联合索引;
  2. 在数据库的 GUI 软件中却可以对一个表设置三种类型的索引:分别为 normal、unique、full text;
  3. 在 MySQL manual 中创建 index 的语法为
    1
    2
    3
    4
    5
    CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name
    [USING index_type]
    ON tbl_name (index_col_name,...)
    index_col_name:
    col_name [(length)] [ASC | DESC]

Answer:

疑惑解决: 第一种分法是按照列的数量进行分类,第二、三种是按照索引类型进行分类

  • normal index 为普通索引
  • primary index 为主键索引,索引列的值必须唯一且不为空
  • unique index 为唯一索引,索引列必须唯一但可以为空
  • fulltext index 为全文索引,只能对 CHAR,VARCHAR 和 TEXT 列编制索引,并且只能在 MyISAM 表中编制
  • spatial index 为空间索引,只能对空间列编制索引,并且只能在 MyISAM 表中编制

有时间再对其具体实现及背后使用的算法做了解及总结。
可能的关键字如下:

  1. HASH
  2. B-TREE
  3. B+TREE

为网站全站链接添加参数

需求

为网站链接添加参数,可以统计用户在网站中的流向,以及确定网站经常访问的部分。

解决方案

  1. 在程序生成链接的时候附带转向,这样需要改变所有生成链接的地方
  2. 使用 jQuery 在网站加载时动态改变网站页面内所有链接

利弊

使用第一种方案时当需要改变某一页面的来源时,所有与本页面有关的链接都需要改动,好处是页面输出到浏览器后即为用户最终所得,不存在链接参数错误的问题。
使用第二种方案好处是修改页面链接时只需在页面标签的父级标签添加某一属性(如例子给出的 from),即可定位相当精准,但弊端在于此方法必须在页面 DOM 树加载完成之后执行,假如中途 DOM 树出问题或者标签属性输出不全可能导致链接错误,另外当标签内的链接由 JavaScript 单独进行定义时需额外修改,这是两种方案都要面对的问题。

使用 jQuery 实现的版本

思路:

  1. 查找所有 a 节点
  2. 获取其父类,一级一级往上找,使用 closest()查找,获取其 from 参数值
  3. 判断 a 节点的 href 属性是否包含?,包含则直接添加 &from ,否则添加?&from。如果添加 href 为 javascript: 或者 javascript:void(0) 时则不改变其 href 属性
    前提:链接不是 javascript: 且链接父级标签含有属性 from
    如网页链接为 www.jayxhj.com
    标签层级为
    <p from="top"> <a href="/test"></a> </p>

则执行 addparam()之后变为

<p from="top"> <a href="/test?f=top"></a> </p>

样例:www.baidu.com 转换后为 www.baidu.com?f=top
www.baidu.com?r=test 转换后为 www.baidu.com?r=test&f=top

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function addparam() {
$("a").each(function () {
var realhref = $.trim($(this).attr('href'));
var href = '';
if (realhref.indexOf('?') === -1) {
href = realhref;
} else {
if (realhref.indexOf('&') === -1) {
if (realhref.indexOf('f=') === -1) {
href = realhref;
} else {
var hrefarr = realhref.split('&');
var length = hrefarr.length - 1;
for (var i = 0; i < length; i++) {
href += hrefarr[i] + '&';
}
href = href.substring(0, href.length - 1);
}
} else {
var hrefarr = realhref.split('&');
if (realhref.indexOf('f=') === -1) {
href = realhref;
} else {
var length = hrefarr.length - 1;
for (var i = 0; i < length; i++) {
href += hrefarr[i] + '&';
}
href = href.substring(0, href.length - 1);
}
}
}
if (!~href.indexOf('javascript:') && href !== '') {
var parent = $(this).parents();
for (var j = 0; j <= parent.length; j++) {
if (typeof ($(parent[j]).attr('from')) != 'undefined') {
$(this).attr('href', href + (href.indexOf('?') === -1 ? '?' : '&' ) + 'f=' + $(parent[j]).attr('from'));
break;
}
}
}
});
}

具体可参看 股票雷达网站

我的 web 开发学习之路

最初了解到 web 是从自己使用网站,又因为专业的缘故,所以对编程对 web 开发逐步有了概念。学校论坛算是启蒙,因为管理着后台,其实并不怎么需要编码,无非是挖掘 Discuz 的功能,整合现有资源,并应用到论坛,服务论坛上的用户,比如找朋友、签到打卡、搬运工(学校通知、考试安排等)这些功能。

学校的课程体系中包含 HTML、CSS、JavaScript、JSP 动态网页、XML、JAVA 这些课程,正是这些课程让我逐步对 web 开发感兴趣,让我花时间去了解在这之外的东西,才慢慢地走上了 web 开发之路。但不得不说道路是曲折的,有过弯路有过徘徊,还好度过了那个时期,没有中途离开,否则也就没有这篇文章了。

一无所知

静态网页–> 动态网站

最开始对 web 的理解就是网页,互相连接起来的网页,才有可能形成 world,大家都在使用才能 wide,所以加上网页就形成了 WWW。后来又发现网站都是动态的啊,纯静态网站那是十多年前的事情了呢,于是知道原来有动态网站和动态语言。最先看到的当属 asp 了,在 w3school 上,形形色色的网页都是 asp 后缀的呢。于是开始在 w3chool 上学习 HTML 与 CSS,刚开始的过程无疑是新奇有趣又略有困难的,很多概念不懂,需要不断回顾再学习。这时候 JavaScript 是让我觉得神奇的语言,因为不需要服务器,只需要浏览器的引擎即可操纵网页,轻易修改。

动态网页最先接触的是 JSP,因为恰好学习 Java,使用 JSP 无疑是比较好的选择,使用一本叫作 《Servlet/JSP 深入详解》 的书籍作为入门以及应付考试。了解了 9 大内置对象,了解了如何使用 JSP 与后台 JAVA 进行交互,但始终没有用它做点什么东西出来。后来还是跟着解丹老师做某一个项目,使用 FLEX 作为前端代替了 JSP,这是后话。

逐步形成概念

网站组成

由于身处计算机专业,又待在网络中心,与学校论坛的前辈们在一个地方做事情,所以难免学着去了解 Discuz 这个系统。因为种种原因,加入了论坛团队,担任技术组组长一职。论坛可谓我网站启蒙。因为网站的运行运营以及各种困难问题的解决离不开团队成员的配合与努力,这段期间可谓各自努力学习,都希望把前人所作的工作做好同时让论坛在学校火热起来,其实还有一个目的,就是让我们院系能在学校被熟悉起来,因为曾说自己是信工的,被问居然还有这个院系!!

由于论坛工作的原因,那时对网站粗浅的理解就是网站是社区。由于要和其他院系以及学校组织做活动,因此得准备活动的素材来应对这些活动。所以对网站的组成做了初步的了解。最先做的大概就是 Banner 了,使用阿里妈妈的工具做,只需找好素材,然后根据活动主题做相应改变。

网站大概由前后端以及相应的组件组成,这些组件包括 Banner、flash 广告,图片轮播等等。

网站思考

这段时期我对网站做了相应的思考,其实论坛说到底是一种以话题做主线的社区,大家因为相同的志趣而集合到一起,因为相同的话题而产生交流。失去了话题论坛活跃度必然降低,所以可以看到,学校论坛中学校通知、租房、杏林广播、聚餐吃饭、爆照这些话题是经久不衰,其他话题如灌水吐槽等不能长久,但是可以作为润滑剂,毕竟干货以及热门话题需要时间,不会一直都有,既需要引导也需要刻意制造。

技能学习

静态部分学习

由于动态网站课程的学习,于是顺带地学习了HTML、CSS、jQuery。但也只是很粗浅的入门。一方面原因是眼界不够,觉得无非就这些知识,类似于 XX 天精通 XXX,但实际上有很大的误导,因为任何一门技术研究起来实际上都不是三言两语就可以解释清楚的。比如你学习了 HTML,觉得它无非标签的嵌套以及网页的布局,有什么难的呢。可是写过之后会知道根本不是这样。问题:行内元素和块级元素有什么区别,它们能相互嵌套吗?如果只是学习了标签是不会知道这个问题的答案的。即使了解了这个问题的答案,又会发现它们嵌套后在不同浏览器下效果是不同的,因为浏览器的解析引擎不一样,效果就有所区别了。

实习时由于要做一个内部的系统,其中有个需求是根据日期选择数据,这种需求如果是自己来实现必然费时,而如果使用 jQuery 的组件就是几段代码的工作量。jQuery 给我最大的益处就是其丰富的 DOM 对象 API,基于这些 API,对 DOM 元素的操作变得无比简单,而且由于其对多浏览器的支持,可以简单地开发出适配手机端的网页。

技能的学习让我深深觉得,每一门技术都有好坏,重要的根据需求选择技术并根据应用场景合理地配置,以便达到开发的目的。

服务器配置

每一种服务背后都有前人巨大的工作量作为铺垫,比如 Apache、Memcache、Redis、Nginx 等。

最初搭建论坛使用的是 WAMP 套件,因为不熟悉 Linux 而且 Windows 服务器也够用,所以长久以来也没有更换。

搭建起网站并运行起来最基本的就是设置网站文件目录,并支持动态语言文件的解析,即转发给指定的解析引擎进行解析并返回。套件的好处就是这些设置有可视化的界面,而且有基本够用的设置,不怎么需要额外修改,坏处是可定制性差一点,但是每个阶段有每个阶段的重点。

实际上做 WEB 开发最好还是在 Linux 平台下。因为常用的服务大都是在 Linux 下进行更新,Windows 系统下这些服务一般也会有人维护,但总是会慢一拍,而且有些服务根本不能在 Windows 下运行。

域名、空间以及 DNS

最初由于想使用自己的名字作为网站名搭建博客程序,就去了解了空间及域名。后来由于使用 jayxhj 网名,就购买了 jayxhj.com 这个域名。空间最初准备使用万网的空间以及域名,后来发现实在太贵,一年保守估计 1K 以上,索性购买美国的空间,使用的是 老薛主机, 优点是不需要备案,而且客服支持不错。

工具

最初接触网站并上传文件使用的是 FlashFXP, 后来才了解到使用 SVN 或者 Git 利用 hook 机制上传代码既方便快捷又能回溯,而且由于天然的版本机制,对代码控制和 new feature 的并入是非常好的选择。

WEB 开发体系逐步形成

由于大四实习的需要,在找到 实习 后得以有机会了解到真实的网站开发以及团队协作的流程。

技术选择

对于个人而言,选择一门主要的语言,使用合适的工具,了解足够多的技术并应用,业余学习新技术新语言,无疑是大多数开发者的常态。而对于公司而言,最初会选择一定的技术满足线上的需求,当选择的技术无法支撑需求时会考虑进行优化或者重构以及更换技术方案。由于论坛的接触,以及 PHP 天然地适合 web 开发,于是我选择了 PHP 作为自己的主要开发语言。

开发流程

最初加入的是 股票雷达,公司是创业型互联网金融公司,十人左右的团队,每个人负责一大块,PHP 这边有三个人,iOS 开发一人,技术总监一人,Java 开发一人,产品经理一人,CEO 一人,再加上实习几人,平常的业务会外包给别人做。

公司使用 Asana 作为团队协作工具,任务的分发以及任务的标记完成,使得需求转化成具体可操作的技术任务。一般是由产品经理与 CEO 推出需求,然后与开发人员协商,给出工作安排,CEO 负责资源的配置,即将任务合理分发,产品经理跟进项目的执行并及时反馈,再根据反馈及时调整,以合理控制项目进度。当前端切好页面并调好效果时一般总监会把架构设计好了,后端也把基本的代码设计及接口做好了,这时候就是前后端的整合,整合完成后移交给产品经理做基本测试,然后就是 beta 版发布,发布之后就是根据运营数据做调整,同时跟进开发新的功能,以及修复过去开发的产品的缺陷及 bug。

开发框架的选择

有句话叫作:不要重复造轮子。由于有太多的框架给我们选择,所以从头开发实无必要。框架好在把路由、逻辑分发、代码包含、分层架构做的非常好,而且内置变量还能让开发过程更简洁高效,代码可读性也更好,最重要的是 OOP 的框架能让开发者注重逻辑的实现而无需纠结于细节。

团队配合

无论是即时交流还是延时交流,这些都涉及到工具的选用,比如团队协作工具,比如企业邮箱选择,比如 bug 管理,比如 IM,比如版本控制。实习给我最大的感触就是不论什么事情,要尽心尽力,及时沟通,搞的定的及时做好后期继续优化,搞不定的请教别人,而不是拖到 deadline。因为项目的完成一般是需要协作的,涉及到常见的业务逻辑问题,肯定需要多人的参与,这时及时的沟通就显得尤为重要。工作不比学校,别人为你的工作付出了薪水,所以理应尽最大努力保质完成,即使不能完成也要能找到最好的平衡点,让项目流畅运转。

WEB 开发提高

就好比 PHP 不仅仅是一门脚本或者模板语言,要想学好技术,绝不能仅仅局限在某一门技术上,因为技术的应用范围有限,没有哪个技术能通吃所有方面,在合适的场景下选用合适的技术考虑实现难易综合成本才是比较明智的选择。而技术上的积累,在深度和广度上的平衡,最终会带来质变。

Linux 基础

推荐学习 鸟哥的 Linux 私房菜. 基础学习篇,无论是计算机基础知识的回顾,还是 Linux 的基本概念,这本书都讲的浅显易懂又不致枯燥无味。

面向对象开发

面向对象最大的特点就是封装继承与多态。使用面向对象开发能较容易地实现代码的封装与隔离,使用封装实现数据与数据处理的结合,使用继承可以方便扩展已有封装模块,而多态则实现多变的需求,统一的接口,不同的实现。

MVC

建议看看这个回答

三层构架和 MVC 不同

数据库

无论是关系型数据库还是非关系型数据库(Not Only SQL),数据库都是应用的核心,而且往往应用的瓶颈集中在数据库,这也是 NoSQL 兴起的一大原因,互联网巨大的用户量与数据量使得传统的关系型数据库不堪重负,而 NoSQL 能分担部分压力且表现良好。

代码结构与分层设计

代码结构的设计可以使得资源放在合适的目录,且结构清晰的代码能降低后期维护的成本。提前画好层次结构图,理清代码的设计思路,将应用分层设计,把接口定义良好,可以为后续的开发减轻不少压力。

新技术的学习

新技术的学习无疑是不被社会淘汰最有可能的途经。因为淘汰的常常是不肯学习的人,多学习多思考,跟上先进技术,才能有更好的为应用带来本质的改进。

比如 Node.js 与 PHP 的配合可以使得 API 设计变得更为合理,使用 MongoDB 作为日志分析收集器等等。这些技术的应用往往来自现实中的需求,而只要你的公司你的产品在成长,这些问题迟早会遇到。

涨薪是原动力,做更好的应用是实际需求,更好的服务用户是愿景。

架构

只有从初级工程师一级一级往上爬,当技术积累到一定程度,能带领一个团队做事时,这时候是技术人证明自己的时候,通过好的产品来诠释自己,让同行让团队尊重,也是对自身最好的嘉奖。

总结

一路走来,最大的感觉是成就感与辛苦是同在的。通过实习找到了互联网公司,敲开了互联网之路。而这一路上难免会有犹豫和徘徊,学校非主流语言,不太好的成绩,这些在后来并不被重视的东西当初却是让我对自己产生怀疑的源泉。但好在互联网这一行,不讲出身不相信命运,相信的是个人的努力,相信的是努力之后的结果。现在仍有太多的东西需要学习,仍有不少东西值得去尝试,好在,热血常在,心未凉,一切都值得去探索。