Jex’s Note

檔案誤刪處置

第一件事最重要 - 不要執行任何會覆寫硬碟的事情

  • 也就是事情發生當下,停止一切複製,移動,安裝等等會讀寫硬碟的事情
  • 如果硬碟在自架的主機,最保險的作法是馬上拆下來,接到其他電腦
  • 如果是誤刪了遠端租用的主機資料,那也能靠遠端連線還原去救

使用R-Studio還原,他的特色:

  1. 要錢 (免費版只能還原64K以下的資料,而且不能還原RAID)
  2. 可以選擇要用本機還原或是遠端還原
  3. 線上付費後授權碼馬上就寄到你的E-Mail了
  4. 評價不錯,有興趣可以google看看

必看:

  1. 要還原硬碟要先掃描, 可能因為共有400~500G, 所以掃描很容易crash, 我掃了幾次才成功, 建議本機還原掃描時不要做其他事, 掃描後要進入硬碟前會有個讀取結構的動作, 我的會固定到55﹪就會當掉, 這軟體似乎並不是那麼靠譜的..
  2. 所以要確定能還原成功再買授權,反正它的授權付完錢就寄給你了

註 :

  • 如果是還原 ext 2, 3, 4 版可以使用R-Linux這個方案,它免費最重要的是還原可以不受64K限制
  • 我是使用win8去還原外接的ext4硬碟所以我買的是R-Studio NTFS的方案
  • 它的設計是你可以先用免費的版本,開啟時都會問你要不要輸入授權,如果選擇試用,那版號就是試用,如果輸入授權碼(例如我輸入R-Studio NTFS授權碼),那啟動後就是R-Studio NTFS版

Ubuntu

查 ubuntu 版本

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 13.10
Release:        13.10
Codename:       saucy

升級 Linux kernel

查看目前版本

確切版號
$ uname -r
4.8.3-x86_64-linode76

$ uname -a
Linux 139-162-12-42 4.8.3-x86_64-linode76 #1 SMP Thu Oct 20 19:05:39 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux

尋找要的 Kernel image

sudo apt-get update
sudo apt-cache search linux-image

    linux-image-3.19.0-15-generic - Linux kernel image for version 3.19.0 on 64 bit x86 SMP
    linux-image-3.19.0-15-lowlatency - Linux kernel image for version 3.19.0 on 64 bit x86 SMP
    (...略)

apt-get install linux-image-xxx

重開後移除原本的版本

reboot
sudo apt-get remove linux-image-ooo

升級 Linux 小版

sudo apt-get update
sudo apt-get dist-upgrade

更換mirror, 官方認可Mirror名單

[1] 修改Mirror源檔

sudo vim /etc/apt/sources.list

[2] 取代成 NCHC, Taiwan (National Center for High-performance Computing, Taiwan)

%s/http:\/\/tw.archive.ubuntu.com\/ubuntu\//http:\/\/free.nchc.org.tw\/ubuntu\//g

apt 指令

  • apt-cache search 'openjdk' : 尋找套件
  • apt-get update : 更新套件列表
  • apt-get upgrade : 更新已安裝套件列表
  • apt-get dist-upgrade : 更新已安裝套件(含相依)列表
  • apt-get install phpmyadmin : 安裝套件 , 例如 : phpmyadmin
  • apt-get remove phpmyadmin : 移除套件 , 例如 : phpmyadmin
  • apt-get autoremove phpmyadmin : 移除(含相依)套件 , 例如 phpmyadmin
  • apt-get -purge phpmyadmin : 移除套件及設定檔 , 例如 phpmyadmin
  • apt-get clean : 清除暫存檔

install 時找不到某個套件

apt-get install software-properties-common
apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db
sudo add-apt-repository "deb http://ftp.osuosl.org/pub/mariadb/repo/5.5/ubuntu $(lsb_release -c -s) main"
sudo apt-get update
sudo apt-get install mariadb-server mariadb-client

dpkg 指令

指令安裝.deb

sudo dpkg -i 檔案名稱.deb   //apt-get 是安裝套件(包含相依套件) , dpkg是個別檔案

移除套件.deb

sudo dpkg -r 檔案名稱.deb

查詢是否安裝某個套件 (例如git)

dpkg --get-selections | grep git

查詢已安裝套件的版本(例如php)

dpkg -l | grep php

查看套件版本等資訊

dpkg -s xfsprogs

檢查套件是否安裝

dpkg -s libapache2-mod-php5 | grep installed
Status: install ok installed
  • -s|–status … Display package status details.
  • -S|–search … Find package(s) owning file(s).

更新單一套件

sudo apt-get install packageName

This will upgrade the package even if is already installed

or

sudo apt-get --only-upgrade install packageName

重新安裝套件

user@hostname:~$ sudo apt-get purge mysql-server-5.5 mysql-client-5.5
user@hostname:~$ sudo apt-get autoremove
user@hostname:~$ sudo apt-get install mysql-server-5.6 mysql-client-5.6

sudo add-apt-repository : command not found

execute :

sudo apt-get update
sudo apt-get install python-software-properties

or

sudo apt-get install software-properties-common

apt-get purge 與 remote 的差別

remove : remove - Packages installed are removed (Does NOT include configuration files)

purge : purge - Purge is identical to remove except that packages are removed and purged. Purge meaning that any configuration files are deleted too.

修改 hostname

[1] 修改 /etc/hostname, ex: ec2

[2] 修改 /etc/hosts, 加上 127.0.1.1 ec2

[3] sudo hostname -F /etc/hostname

-F, –file : read host name or NIS domain name from given file

Set Time zone

查看目前的 timezone

$ cat /etc/timezone
Etc/UTC

設定 time zone

sudo tzselect

執行 command, 或加在 .bashrc 裡

TZ='Asia/Taipei'; export TZ

重啟 time zone

sudo dpkg-reconfigure tzdata

設定完成後立即生效

cat /etc/timezone
Asia/Taipei

(Optional) 重啟 crontab

sudo service cron restart

Set Locale

查看目前 default

$ cat /etc/default/locale
LANG="en_US.UTF-8"

查看目前有的

$ locale
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE=UTF-8
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

perl warning

perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
    LANGUAGE = (unset),
    LC_ALL = (unset),
    LC_CTYPE = "UTF-8",
    LANG = "en_US.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").

你用 SSH 連到主機而主機的 SSH 設定

/etc/ssh/sshd_config :

# Allow client to pass locale environment variables
AcceptEnv LANG LC_*

你的 OS 不認識 en_US.UTF-8

解決方法1 : 使用以下方式連到 server

LC_ALL=C ssh your_server

解決方法2 : 加在 .bashrc

export LANGUAGE=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
export LC_TYPE=en_US.UTF-8

內建自動啟動的套件

取消自動啟動

sudo update-rc.d nginx disable

移除

sudo update-rc.d -f apache2 remove

PHP

Install php package

sudo apt-get install php5 php5-curl php5-gd php5-imagick imagemagick php5-intl php5-mcrypt php5-memcached php5-mysql php5-json

顯示PHP版本資訊

$ php -v
PHP 5.3.10-1ubuntu3.5 with Suhosin-Patch (cli) (built: Jan 18 2013 23:40:19)
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies

檢查語法錯誤

$ php -l test.php
No syntax errors detected in test.php

啟動php shell模式

$ php -a
Interactive shell
php > echo "Hello World!!";
Hello World!!
php > exit                      #離開shell模式

開一個SERVER

php -S test.google.com:8002

直接執行php指令

$ php -r "echo 'hello';"
hello

顯示已載入的 extension

echo "<pre>";
print_r(get_loaded_extensions());

判斷某個 extension 是否載入,未載入時載入

if ( ! extension_loaded('Memcached'))
{
    dl('memcached.so');
}

include vs require & require vs require_once

  • The require() function is identical to include(), except that it handles errors differently. If an error occurs, the include() function generates a warning, but the script will continue execution. The require() generates a fatal error, and the script will stop.
  • The require_once() statement is identical to require() except PHP will check if the file has already been included, and if so, not include (require) it again.

json_encode 中文亂碼

預設轉為json時, 如果裡面包含中文, 會被轉成編碼, 如:\u53f0\u6e7e \u53f0\u5317\u5e02

如果加上第二個參數 JSON_UNESCAPED_UNICODE:

json_encode($data, JSON_UNESCAPED_UNICODE)

出來json就會是中文了 : 台湾 台北市

將字串切割成陣列

$row_arr = preg_split('/\s+/', $row, $limit = 5);

第三個參數指定要切幾次 array preg_split ( string $pattern , string $subject [, int $limit = -1 [, int $flags = 0 ]] )

字串也能用陣列來取字元

$a = "238dsfjkh2378afhkj234d";
php > echo $a[0];
2
php > echo $a[1];
3

php multiple replace

$patterns = array("this", "that", "other");
$regex = '/(' .implode('|', $patterns) .')/i';
if (preg_replace($regex, "me")) { ...

rawurlencode 與 urlencode 的差異

編碼後都一樣的符號:

` ! @ # $(錢號) % ^ & * ( ) _ - + = < > , . / ? | \ ' " : ; { } [ ] 數字 英文 中文

不同的:

~

urlencode('~') : %7E
rawurlencode('~') : ~

(空格)

urlencode(' ') : +
rawurlencode(' ') : %20

PHP 設定檔路徑

  • php.ini : /etc/php5/apache2/php.ini
  • session 路徑 : /var/lib/php5
  • pecl (php 5.4) 預設安裝路徑 /usr/lib/php5/20100525/

server variable

.htaccess :

setEnv STATIC_DEBUG 1

php :

$_SERVER['STATIC_DEBUG']

php 傳json物件給javascript

php :

<input type="hidden" id="json-uids" value='<?php if (isset($uids)) echo json_encode($uids); ?>'/>

javascript :

var j = JSON.parse($('#json-uids').val());

不顯示錯誤訊息 : @

@ rename($old_name, $new_name)

加上@後,發生錯誤時頁面上不顯示錯誤訊息,但還是會記log (codeigniter測試)

脫逸

只有單引號裡的\需要特別脫逸 : '\\',其他符號都不需脫逸

php debug

echo "<pre>" . print_r($user_list) . "</pre>";

browser 顯示結果 :

Array
(
    [0] => Array
        (
            [id] => 1
                            [name] => jex_lin
        )
 )

隱藏錯誤

error_reporting(E_ALL ^ E_STRICT);

PHP 內部常數

DIRECTORY_SEPARATOR 是 php 內部變數, 在 windows 為 \, linux 為 /

php script 宣告

#!/usr/bin/php
<?php
// code..
?>

php script 接收指令參數

執行 php script 並接收入進來的參數, 指令 : php t.php Hello-World

t.php :

echo $argv[1];              // Hello-World

簡寫

if else :

php > ($a > 0) ? $b = 2 : $b = 3; echo $b;
2

另種寫法 :

php > $a = 1;

// 成立
php > ($a > 0) && $b = 2; echo $b;
2

// 不成立
php > ($a < 0) && $b = 3; echo $b;
2

檢查及引入某個 module (ex: mcrypt)

php -m | grep mcrypt : 檢查 mcrypt 有沒有引入

沒有則執行以下

sudo apt-get install php5-mcrypt
sudo php5enmod mcrypt

使用 nginx 記得重啟 fpm : sudo service php5-fpm restart

Session 存活時間

php.ini :

session.cookie_lifetime = 0             // The value 0 means "until the browser is closed."
session.gc_maxlifetime = 1440           // session 最多存活時間 (seconds)

Out of memory 錯誤

發生原因

Out of memory (allocated 2097152) (tried to allocate 1052672 bytes) ...略

不知道為什麼明明流量很小但 php-fpm 記憶體卻吃這麼多,後來發現 API 在結束之後,記憶體都不會自動釋放,有可能程式中有 memory leak 的問題, 造成越積越多在 php-fpm 的 process,最後沒記憶體用就 crash 了

當時總記憶體吃到 900 M+,過很長的時間 memory 都沒下降的趨勢

             total       used       free     shared    buffers     cached
Mem:           995        921         73          0        103        123

Restart php-fpm 強迫釋放總記憶體後剩 426M

$ sudo /etc/init.d/php-fpm restart
Stopping php-fpm-7.0:                                      [  OK  ]
Starting php-fpm-7.0:                                      [  OK  ]

             total       used       free     shared    buffers     cached
Mem:           995        426        568          0        103        123

可以看的出來 php-fpm 吃掉很多記憶體,雖然 restart php-fpm 可以暫時解決問題,但最好的辦法還是讓 php-fpm 自已管理好記憶體

如何解決

方法相當簡單,只需修改幾個 php-fpm 的設定 (/etc/php-fpm-7.0.d/www.conf) 就可以解決了,詳細可以參考這篇教學

改成 dynamic (固定數量+彈性增減)

pm = dynamic
pm.max_children = 15
pm.start_servers = 5            // php-fpm 一啟動就會開5個 child process 起來
pm.min_spare_servers = 5        // 最少 5 個
pm.max_spare_servers = 15       // 最多到 15 個
pm.max_requests = 500

或改成 ondemand (有需要才服務)

pm = ondemand                   // ondemand 是最少 0 個,最多到 max_children 設定的數量
pm.max_children = 15
pm.process_idle_timeout = 10s   // 當 child process 閒置 10 秒自動 kill 掉
pm.max_requests = 500

補充說明 :

max_children 要設定幾個取決於你的 php-fpm child process 吃多少記憶體,假如一個 process 吃 20MB,總記憶體有 1G, 假設扣掉系統等等雜7雜8的程序記憶體剩 300,那你的 max_children 就是 300MB/20MB = 15 個,可以設定保守點避免 php-fpm 把記憶體吃光光

max_requests 建議一定要設定這個,它意思是說當 php-fpm 的 child process 處理一個特定數量的 request 後就會把它 kill 再重新產生新的 php-fpm process, 它可以避免你因為 memory leak 問題造成整個 server crash,尤其你引入很多第三方的話你根本不知道什麼時候會出現 memory leak,即使知道了可能當下也沒辦法解, 最保險的方法就是設定這個值,即時有 leak 但到一定 request 數量就會立即釋放讓 process 及記憶體重新來過,而且它只會影響單個 process 不用擔心會有 downtime 問題

後記

原本以為 memory leak 發生在使用 aws sqs.sendMessage,因為除了更新 sdk,也另外單獨把 sqs 拉成一個獨立且單純的 api,並且一直戳它, memory 如預期一直緩慢增加且不會釋放,以為可以確認是 sqs 導致 memory leak,但是連續做 執行 sqs ->GC->看記億體使用量,卻又很正常, 判斷應該不是 sqs 造成的 leak,但確實是執行 sqs 會發生 leak 的狀況,或許是 php-fpm 底層有問題?最後實在找不出原因,也沒辦法解決,所以只好繞過這個問題, 得知一個 request 大概會有 20k 的 memory leak,根據前面設定的 pm.max_requests = 500 以及 pm.max_children = 15, 所以至少需要保留 500 * 20k * 15 = 150M 以上的記憶體做緩衝以確保 php-fpm 不會沒記憶體可用

如何確認並找出 memory leak 問題

用另一個程式一直戳你覺得有問題的 api,邊用 ps 去查看 php-fpm process memory 成長的狀況,

  PID %CPU %MEM   RSS COMMAND
19269  0.0  1.1 11872 php-fpm-7.0
19271  2.2  2.8 28696 php-fpm-7.0

當 php-fpm 執行時,mem 有增長是很正常的,但執行同一個 api 幾次到一個量後就不太會變動了,如果它一直保持穩定增長, 就表示程式有可能存在 memory leak 問題,這時候我們就要細看問題出在哪裡了。

可以把出問題的 api 分成幾個部份,在不同地方結束,邊戳邊看 memory 的變化,看哪一段會讓 memory 突然的增加, 如果找到在有問題的地方,可以在外層包一個迴圈印出記憶體使用量,如果 GC 後記憶體用量一直增加就能確定有 memory leak 的問題了

while (true) {
    // do

    gc_collect_cycles();
    echo memory_get_usage() . "\n";
    usleep(2000000);                    // wait for 2 seconds
}

MySQL 回覆 Too many connections 錯誤

發生原因是 request 量太大,並且操作 MySQL 造成 MySQL server 無法處理他同時最大能處理的連線數所導致的

可以查看目前與 MySQL 建立的 connections 數量

SHOW STATUS WHERE `variable_name` = 'Threads_connected';

最多支援的數量

SHOW VARIABLES LIKE 'max_connections';

或查看哪個 process 有建立連線但沒有用到 (Sleep)

SHOW FULL PROCESSLIST;

升級/安裝 PHP (Ubuntu)

刪除舊版本 5.0

sudo apt-get autoremove --purge php5-*

安裝 7.0

sudo add-apt-repository ppa:ondrej/php
sudo apt-get -y update
sudo apt-get install php7.0 php7.0-fpm php7.0-mysql php7.0-mcrypt php7.0-curl -y

安裝完 php 的 extension (e.g. mcrypt) 會自動幫你建立 /etc/php/7.0/mods-available/mcrypt.ini 並 link 到 /etc/php/7.0/fpm/conf.d/20-mcrypt.ini

Optional:

  • php7.0-bz2 (壓縮)
  • php7.0-dom (xml)
  • php7.0-mbstring (處理編碼)

PHP.ini/PHP-FPM 設定

(updated at 2017-08-07)

/etc/php/7.0/fpm/php.ini: cgi.fix_pathinfo 安全性設定

預設為1, 可能會導致安全性問題: www.example.com/1.jpg, 改用www.example.com/1.jpg/xxx.php 會被執行 (此段未試過, 從此篇抄過來)

解法有兩種

1) 將 cgi.fix_pathinfo = 0

2) 調整 nginx 設定

if ( $fastcgi_script_name ~ \..*\/.*php ) {
    return 403;
}

/etc/php/7.0/fpm/pool.d/www.conf: process manager 相關設定

pm = dynamic
pm.max_children = 15
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 500


access.log = /var/log/php-fpm/$pool.access.log
slowlog = /var/log/php-fpm/$pool.log.slow


php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on

/etc/php/7.0/fpm/php-fpm.conf:

# 修改 log path
error_log = /var/log/php-fpm/php7.0-fpm.log`

php-fpm 執行失敗

/var/log/nginx/error.log 顯示 /run/php/php7.0-fpm.sock 不存在 :

2017/08/07 09:03:09 [crit] 4711#0: *1 connect() to unix:/run/php/php7.0-fpm.sock failed (2: No such file or directory) while connecting to upstream, client: 127.0.0.1, server: astra.cloud, request: "GET /api/ HTTP/1.1", upstream: "fastcgi://unix:/run/php/php7.0-fpm.sock:", host: "127.0.0.1:8080"

表示可能 php-fpm 沒執行成功, 發現重新執行 php-fpm 雖然成功, 但是 process 啟動後就被 kill 了

$ sudo /etc/init.d/php7.0-fpm start
php7.0-fpm start/running, process 7801

檢查 /var/log/php7.0-fpm.log 有沒有 error message

如果上面的 log 沒有顯示 error message, 看看 /var/log/syslog 有沒有相關線索

Aug  7 09:47:45 ip-10-0-13-120 kernel: [23129.971713] init: php7.0-fpm main process ended, respawning
Aug  7 09:47:45 ip-10-0-13-120 kernel: [23129.997879] init: php7.0-fpm main process (14519) terminated with status 78`

或直接使用 test 參數看 php-fpm 問題出在哪

$ sudo php-fpm7.0 -t
[07-Aug-2017 09:56:26] ERROR: failed to open access log (/var/log/php-fpm/www.access.log): No such file or directory (2)
[07-Aug-2017 09:56:26] ERROR: failed to post process the configuration
[07-Aug-2017 09:56:26] ERROR: FPM initialization failed

php-fpm 沒有權限建立我指定的 log path, 當我建立好之後就正常了
$ sudo php-fpm7.0 -t
[07-Aug-2017 09:58:19] NOTICE: configuration file /etc/php/7.0/fpm/php-fpm.conf test is successful

安裝其他 extension

redis

1) sudo vim /etc/php/7.0/mods-available/redis.ini
```
extension=redis.so
```

2)
sudo ln -s /etc/php/7.0/mods-available/redis.ini /etc/php/7.0/cli/conf.d/20-redis.ini
sudo ln -s /etc/php/7.0/mods-available/redis.ini /etc/php/7.0/fpm/conf.d/20-redis.ini

3) restart php-fpm

4) 如果依然顯示 `Exception: Class 'Redis' not found` 就執行 `sudo apt-get install php-redis`, 再 restart php-fpm

將 session 改使用 redis, /etc/php/7.0/fpm/pool.d/www.conf

php_value[session.save_path] = tcp://redis.astra.ap-northeast-1.local:6379

MySQL

(最後更新: 2016-04-14)

Install

ubuntu :

sudo apt-get install mysql-server-5.6 mysql-client-5.6 mysql-client-core-5.6

mac :

brew install mysql
mysql.server restart

// client only
brew install mysql --client-only

mac : 開機自動啟動

mkdir -p ~/Library/LaunchAgents
ln -sfv /usr/local/opt/mysql/*.plist ~/Library/LaunchAgents

登入不用密碼

如果你連 mysql 都進不去,從步驟[1]開始,如果你可以進到 mysql console 從步驟[3]開始

[1] 停止 mysql deamon

sudo /etc/init.d/mysql stop

[2] 加上 --skip-grant-tables option 啟動 mysql 指令

sudo /usr/sbin/mysqld --skip-grant-tables --skip-networking &

[3] edit password

mysql -u root
FLUSH PRIVILEGES;

修改密碼

SET PASSWORD FOR root@'localhost' = PASSWORD('');
或
UPDATE mysql.user SET Password=PASSWORD('') WHERE User='root';

[4] Flush privileges:

FLUSH PRIVILEGES;

[5] Restart mysql

sudo service mysql restart

設定/修改密碼

設定 root 密碼 (安裝時沒有設定過密碼)

mysqladmin -u root password

修改 root 密碼

mysqladmin -u root -p password

Enter password:
New password:
Confirm new password:

解除只有本機 IP 能連到 MySQL 的限制

[1] 預設 MySQL 的 3306 port 只有監聽本機的連結

root@jexpoyi:/etc/apache2# netstat -an | grep 3306
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN

[2] 修改 /etc/mysql/my.cnf, 註解 bind-address = 127.0.0.1

# bind-address      = 127.0.0.1

[3] 重啟

/etc/init.d/mysql restart

如果有使用 phpmyadmin, 要修改設定

權限登錄資料主機改成任意主機(%)

[4] 現在port就已經開啟讓外部ip可以連入了

root@jexpoyi:/etc/mysql# netstat -an | grep 3306
tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN

[5] Done!

[error] 如果phpmyadmin沒辦法登入, 直接command登入也出現錯誤:

ERROR 1045 (28000): Access denied for user 'test'@'localhost' (using password: YES)

可能原因是host改成 % (任意主機),但批對不到localhost造成本機沒辦法登入

解決 : 再去phpmyadmin將主機改成localhost, 這時候host資料就有兩筆了%localhost, 就可以登入了

Access denied for user 'worker'@'10.0.21.77' (using password: YES)

有可能是你正在轉移 DB, 但轉移時並不會幫你把 account 的密碼也轉移過去, 所以必須到修改 account 的密碼把密碼填上

Host '172.18.0.4' is not allowed to connect to this MySQL server

修改 root 的 host

USE mysql;
mysql> SELECT user,host FROM user WHERE User='root';
+------+-----------+
| user | host      |
+------+-----------+
| root | localhost |
+------+-----------+
mysql> UPDATE user SET host='%' WHERE user='root';
mysql> FLUSH PRIVILEGES;
mysql> SELECT user,host FROM user WHERE User='root';
+------+------+
| user | host |
+------+------+
| root | %    |
+------+------+

Slow query

目的 : 將拖慢速度的 query 找出來, 進一步改善效能

開啟 slow query

1) /etc/mysql/mysql.conf.d/mysqld.cnf :
# 將原本被註解的 slow query 參數打開
slow-query-log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log     # slow query記錄檔的路儲存路徑
long_query_time = 1                                     # query超過1秒時,則會記錄
log-queries-not-using-indexes                           # 會記錄沒有索引的記錄

注意!! The --log-slow-queries option was removed in MySQL 5.6.1

2) 重啟動後就會在 /var/log/mysql/ 下看到這個檔案了 : mysql-slow.log

分析 Slow query

安裝 mysql-server 就會有 mysqldumpslow 這個工具了

mysqldumpslow /var/log/mysql/mysql-slow.log

找出花最多時間的 5 個 queries

mysqldumpslow -a -s r -t 5 /var/log/mysql/mysql-slow.log

依最常執行的 query 做排序

mysqldumpslow -a -s c -t 5 /var/log/mysql/mysql-slow.log

參數 :

-t NUM       just show the top n queries
-a           don't abstract all numbers to N and strings to 'S'
-s ORDER     what to sort by (al, at, ar, c, l, r, t), 'at' is default
              al: average lock time
              ar: average rows sent
              at: average query time
               c: count
               l: lock time
               r: rows sent
               t: query time

其他分析工具 : pt-query-digest, mysqlsla

尋找 slow query

找出排名前 5 筆花最久時間的 query

SELECT * FROM slow_log ORDER BY query_time DESC LIMIT 5;

找出某天前 5 筆花最久時間的 query

SELECT * FROM slow_log WHERE start_time BETWEEN '2018-06-28 00:00:00' AND '2018-06-29 00:00:00' ORDER BY query_time DESC LIMIT 5;

Binlog

目的 : 將執行過的 query 紀錄下來, 當有一天操作失誤或數據遺失還可以復原回來

開啟 binlog

1) /etc/mysql/mysql.conf.d/mysqld.cnf :
# 將原本被註解的 log_bin 打開
log_bin = /var/log/mysql/mysql-bin.log

其他 :

# 只紀錄指定的 databases
binlog-do-db=db_name1
binlog-do-db=db_name2

# 忽略指定的 database
binlog-ignore-db=db_name1

# 限制 binlog 上限 size
max_binlog_size=100M

# 10 天後自動清除 binlog
expire_logs_days=10
2) 重啟動後就會在 /var/log/mysql/ 下看到這兩個檔案了 : mysql-bin.index mysql-bin.000001
3) 測試

隨意 Inster 兩筆資料

使用 mysqlbinlog 指令將 binglog 輸出成 SQL

mysqlbinlog --start-datetime="2015-05-25 00:00:00"  --stop-datetime="2015-05-26 9:00:00" /var/log/mysql/mysql-bin.000001 > test_binlog_is_working.sql

執行還原

mysql> SOURCE test_binlog_is_working.sql;

其他指令

查看狀態

SHOW MASTER STATUS

重置

RESET MASTER

刪除指定日期以前的 binlog

PURGE BINARY LOGS BEFORE '2015-05-24 07:00:00';

刪除某個流水號以前的 binlog

PURGE BINARY LOGS TO ‘mysql-bin.000113′;

將超過 n 天的 binlog

FLUSH LOGS

輸出 mysql-bin 為檔案

mysqlbinlog /var/log/mysql/mysql-bin.000001 > qq.txt

恢復數據

1) 確定 binlog 是否有開啟, 如果有找出 binlog 位置
SHOW VARIABLES LIKE 'log_bin'
  • 如果回應為 ON, 表示有開啟, 到 /etc/mysql/mysql.conf.d/mysqld.cnf 尋找 log-bin 的值
  • 回應為 OFF 則表示沒有開啟, 不用執行以下步驟, 因為無法恢復了

假設 my.cnf 記錄著 :

log_bin = /var/log/mysql/mysql-bin.log  # 檔名前綴字串

/var/log/mysql :

(..略..)
-rw-rw----  1 mysql mysql  12967 May 24 01:19 mysql-bin.000001
-rw-rw----  1 mysql mysql     32 May 24 01:01 mysql-bin.index
2) 恢復最近一次備份的數據 (Optional)

如果 Server 之前有輩份, 先還原

mysql> SOURCE my_db.bak.sql
3) 用 mysqlbinlog 輸出 binlog

[1] 輸出整個 database

mysqlbinlog 無法直接輸出 table, 只能是一整個 database, 所以先取得 database 再到裡面找 table

指令需要指定 --database, --start-date, --stop-date, 在這期間內可能跨了很多 binlog

mysqlbinlog --database=my_db --start-datetime="2015-05-25 00:00:00"  --stop-datetime="2015-05-26 9:00:00" /var/log/mysql/mysql-bin.000001 > my_db.sql

//如果跨越了幾個 binlog, 採用追加的方式加到 bbs.sql
mysqlbinlog --database=my_db --start-datetime="2015-05-25 00:00:00"  --stop-datetime="2015-05-26 9:00:00" /var/log/mysql/mysql-bin.000001 >> my_db.sql

也可以用 /var/log/mysql/mysql-bin.00012* 模糊比對的方式指定多個 binlog 文件

[2] 取得指定的 table

cat my_db.sql | grep 'comment' > comment.sql
4) 執行要恢復的 table 的 binlog

[1] 找出 comment.sql 誤操作的語法並刪除, 如果只是要恢復數據可以不用做動作

[2] 執行 mysql> SOURCE comment.sql

ref : 參考恢復數據

MariaDB

MariaDB 是 mysql 的分支,效能比 MySQL 好,吃的資源也較少,用法與 MySQL 一模一樣,可以直接拿它來替代 MySQL

Install

sudo apt-get install mariadb-server mariadb-client

test :

mysql -u root

啟動

sudo service mysql start

Q&A

某些中文字無法存入問題

unicode 編碼一般常用字都是 3bytes, 但因為它是 4 byte 而無法寫入, 例如 : 𡟛參考 wiki

Mysql2::Error: Incorrect string value: '\xF0\xA1\x9F\x9B'

(未試過) 解法方法 The utf8mb4 Character Set (4-Byte UTF-8 Unicode Encoding)

如果 MySQL Ram 吃到 45%+

在一台乾淨的 EC2 t1.micro 1G Ram,居然被吃到 45%,而執行 Rails app 後 Memory 就快被吃快了,網頁也變很頓,簡單的登入登出就會掛掉,

接著我試著增加 Swap,解決此問題,指令請到這篇增加 Swap 那段照著執行,

再執行一次 mysql start,一樣 Memory 是衝到爆,但它會自動分配一些到 swap,之後 MySQL 的 Memory 降到 26% -> 19% -> 16% ..

欄位 Type

  • Datetime 後面可以接長度,多出來的長度是小數點,可以細到毫秒以上 e.g. datetime(3) = 2017-06-28 03:14:35.280

SELECT * 及 SELECT {特定欄位} 的差別

當取出來的資料只有 1 筆可能還沒什麼差別, 但當資料有 1000, Query 的時間會微變慢,

但更慢的是當從 MySQL 取出來的資料傳輸到程式裡所花的時間, 而時間取決於 Query 出來的資料量大小, 很容易有 2s+ 的差距, 需要注意!

區分 : 全形 半形 大小寫

在某些情況下, 需要全形半形 db 的欄位 collation 就不要用 utf8_unicode_ci

要改用 utf8_general_ci

如果要區分大小寫就改用 utf8_bin

QPS & TPS

  • QPS:Queries Per Second 查詢量/秒, 是一台服務器每秒能夠相應的查詢次數, 是對一個特定的查詢服務器在規定時間內所處理查詢量多少的衡量標準
  • TPS : Transactions Per Second 是事務數/秒, 是一台數據庫服務器在單位時間內處理的事務的個數

查詢方法是在 MySQL console 輸入 SHOW STATUS;, 並指到以下對應的欄位

  • QPS = Questions
  • TPS = Com_commit/s + Com_rollback/s

SSH 連線至主機

產生 ssh key 並且設定免密碼直接連入遠端主機

本機:

cd ~
ssh-keygen -t rsa
(enter到底)
cd .ssh
scp id_rsa.pub 遠端主機網址:~/.ssh/qq  (把本機的公鑰複製到遠端主機)

登入遠端主機:

ssh 遠端主機網址
(會要你輸入密碼)
cd .ssh
cat qq >> authorized_keys   (將主機的公鑰加入到遠端的驗證key裡)
rm qq                       (加入到authorized_keys後就沒有用了, 刪除它)
exit                        (大功告成, 離開遠端主機)

回本機:

ssh 遠端主機網址
(不會要求你輸入密碼而直接進入)

安全性設定

Open /etc/ssh/sshd_config (注意!! 不要改錯檔案, 是 sshd_config 不是 ssh_config) :

允許密碼驗證

PasswordAuthentication yes

改 SSH default port

Port 2222
Protocol 2
PermitRootLogin no
  • 1025~65536 的 port 都可以使用
  • PermitRootLogin 設為 no 的話, root 登入還是會要求你輸入密碼, 只是即使輸入的是對的密碼, 也會告訴你是錯的, 即不讓你登入就對了

只允許某個 User

UseDNS no
AllowUsers jex

Reload 新設定

別急著登出, 先測試修改是否成功

sudo service ssh restart
ssh -p 2222 jex@localhost

如果有設定防火牆記得把 2222 port 打開

當我的主機重灌後, 我要 ssh 進去時發生這樣的錯誤 :

device-f33d32:~ apple$ ssh jex@jexpoyi.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
e2:c8:73:6b:01:ad:29:cf:7c:1c:54:68:93:4c:a8:7e.
Please contact your system administrator.
Add correct host key in /Users/apple/.ssh/known_hosts to get rid of this message.
Offending RSA key in /Users/apple/.ssh/known_hosts:1
RSA host key for jexpoyi.com has changed and you have requested strict checking.
Host key verification failed.

意思是 .ssh/known_hosts 原本記著這樣的紀錄

jexpoyi.com,142.4.51.133 ssh-rsa AAAAB3NzaC1yc2EAAA(..略..)

但是重灌後的 rsa 不一樣了, 所以為了安全性的考量而無法登入, 解決方法很簡單

只要把 .ssh/known_hosts 裡主機那行紀錄刪除, 再 ssh 進去重新產生紀錄就行了

設定 push 或 pull github 不用再輸入帳密

將你的 repository 的 ssh URL 加到你的 origin, 不要用 https URL

然候將你的 ssh public key 加到 github 後台的 ssh 管理

設定 alias - 每一次連線不用輸入過多參數

~/.ssh/config 加上要 alias 對應的欄位

HOST test
    HostName 192.168.79.21
    User jex
    Port 2229
    ...
    IdentityFile aws.pem
    LocalForward 35729 192.168.79.21:35729      (將主機的 35729 導到本機的 35729)
    ...

之後輸入 ssh test 即可

.pem 的權限要調低, 否則會報錯, chmod 400 aws.pem

使用 .pem 連進主機,除了上述方法設定也可以用參數 -i

sudo ssh ubuntu@123.123.123.123 -i aws.pem

AWS Ubuntu 主機預設 User 叫 ubuntu

在同一台主機避免跟同事用到同一個 session 方法

可在 .bashrc 設定 :

sssh (){ ssh -t "$1" 'tmux attach -t jex || tmux new -s jex || screen -DR'; }

之後執行 sssh 就好了!

Port Forwarding

  • Mac :

本機執行 ssh -L 127.0.0.1:35729:54.199.219.49:35729 ubuntu@54.199.219.49

或者在 ~/.ssh/config 設定 :

Host ec2
    HostName 54.199.219.49
    User ubuntu
    Port 22
    LocalForward 35729 54.199.219.49:35729
  • LocalForward 是將遠端主機 54.199.219.49 的 35729 導到本機的 35729
  • 如果要連到其他 ssh port 的寫法 ssh jx@54.164.251.31 -p 4223 -L 35729:54.164.251.31:35729
  • Windows :

如果使用 putty 要多設定 Conection->SSH->Tunels

Source port : 35729
Destination : 54.199.219.49:35729
(下面選項維持預設 Local 及 Auto)

forward agent - 本機登錄到 basion 後再登入到其他主機時不需要把 private key 放到 basion

1) 將本機的私鑰加入到 keychain

ssh-add -K

ssh-add -lssh-add -L 就可以看到剛剛加入的那把私鑰了

在本機及 basion 加入以下,當從 basion 登入到其他台時, basion 那台就不需要有本機 private key 就能連到其他台了

Host *
        ForwardAgent yes
        ServerAliveInterval 300
        ServerAliveCountMax 5

一行指令直接經由 basion 登入主機 (proxycommand)

大部份我們的主機架構會長這樣,每次要到 server 都要經由 basion,所以會輸入兩次 ssh 很麻煩

laptop -> basion -> server

我們可以用一行指令就能直接登入 server

ssh -tt basion ssh -tt server

但這樣雖然縮短時間,不需要等到進去 basion 再 ssh 到主機,但 command 還是有點太長,可以用 proxycommand 做到,它的原理是用 nc 指令建立 TCP 連線連接 basion 與 server

(TODO)

PuTTY免密碼以SSH登入主機

[1] 主機要先產生完 SSH Key (ssh請選擇rsa)

[2] 下載 WinSCP,並使用它登入主機把私鑰的 SSH Key 下載下來(id_rsa)

[3] 將私鑰名稱(id_rsa)加上副檔名(.ppk) => id_rsa.ppk

[4] 到 PuTTY 官網頁下載 puttygen.exe

[5] 打開PuTTYgen

  1. 點選 “Load” 讀取 id_rsa.ppk
  2. 選擇 SSH-1 (RSA)
  3. 點選 Save private key

[6] 打開PuTTY

  • 左邊 Connection→Data 輸入 Auto-login username
  • 左邊 Connection→SSH→Auth

1) 打勾 Attempt authentication using Pageant 2) 打勾 Attempt TIS or CrptoCard auth(SSH-1) (其他不要打勾) 3) 下面瀏覽框要將 PuTTYgen 儲存的私鑰讀進來

[7] 完成,記得回到 Session 點 save 儲存這些設定,這樣下次就不用又從頭設定一次喔!

將 AmazonEC2MicroInstance.pem 轉成 ppk

開啟 puttygen.ext -> Load (選擇 AmazonEC2MicroInstance.pem) -> Save private key (存成 amazon-ec2.ppk)

左邊 SSH -> Auth 的 Private key file for authentication 選擇剛剛生出來的 amazon-ec2.ppk

Apache 減少瀏覽器向server請求次數

當瀏覽器第一次讀取圖檔

status : 200 OK

request header:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Connection:keep-alive
Host:jex.com
User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36

response header:

Accept-Ranges:bytes
Connection:Keep-Alive
Content-Length:45860
Content-Type:image/jpeg
Date:Thu, 11 Jul 2013 08:09:03 GMT
ETag:"bc003e-b324-4e136f4c01a37"
Keep-Alive:timeout=5, max=100
Last-Modified:Thu, 11 Jul 2013 07:00:57 GMT
Server:Apache/2.2.22 (Ubuntu)

內容會有圖檔

第二次讀取讀檔會跟cache要所以

status : 304 Not Modified

request header:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:max-age=0
Connection:keep-alive
Host:jex.com
If-Modified-Since:Thu, 11 Jul 2013 07:00:57 GMT
If-None-Match:"bc003e-b324-4e136f4c01a37"
User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36

response header:

Connection:Keep-Alive
Date:Thu, 11 Jul 2013 08:09:51 GMT
ETag:"bc003e-b324-4e136f4c01a37"
Keep-Alive:timeout=5, max=100
Server:Apache/2.2.22 (Ubuntu)

內容不會有圖檔,只會送出request然候apache比對ETag及Last-Modified,如果檔案有被修改才是status:200

調整apache設定,當網址一樣時讓瀏覽器連304都不送直接取cache

[1] 打開模組

  1. /etc/apache2/mods-available
  2. ls | grep expire 及 ls | grep header 確定有沒有這兩個模組,如果沒有就開啟
  3. Once you install the module, the module will be available in the /etc/apache2/mods-available directory. You can use the a2enmod command to enable a module. You can use the a2dismod command to disable a module. Once you enable the module, the module will be available in the the /etc/apache2/mods-enabled directory.

[2] 在 /etc/apache2/sites-available/default 加上

ExpiresActive On
ExpiresByType image/jpg "access plus 10 years"
ExpiresByType image/jpeg "access plus 10 years"

[3] sudo service apache2 restart

設定後的測試:

[1] 第一次load圖

status : 200 OK

request header:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:no-cache
Connection:keep-alive
Host:jex.com
Pragma:no-cache
User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36

response header:

Accept-Ranges:bytes
Cache-Control:max-age=315360000
Connection:Keep-Alive
Content-Length:45860
Content-Type:image/jpeg
Date:Thu, 11 Jul 2013 08:11:46 GMT
ETag:"bc003e-b324-4e136f4c01a37"
Expires:Sun, 09 Jul 2023 08:11:46 GMT
Keep-Alive:timeout=5, max=99
Last-Modified:Thu, 11 Jul 2013 07:00:57 GMT
Server:Apache/2.2.22 (Ubuntu)

有加上expire了

[2] 第二次load圖

status : 304 Not Modified

request header:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:max-age=0
Connection:keep-alive
Host:jex.com
If-Modified-Since:Thu, 11 Jul 2013 07:00:57 GMT
If-None-Match:"bc003e-b324-4e136f4c01a37"
User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36

response header:

Cache-Control:max-age=315360000
Connection:Keep-Alive
Date:Thu, 11 Jul 2013 08:25:53 GMT
ETag:"bc003e-b324-4e136f4c01a37"
Expires:Sun, 09 Jul 2023 08:25:53 GMT
Keep-Alive:timeout=5, max=99
Server:Apache/2.2.22 (Ubuntu)

[3] 當從外面直接連進來後會直接從cache拿了

Request URL:http://jex.com/static/a/ce/ce9600a437486fbfd9b13856f2e1c55f.jpg?1373516123
Request Method:GET
Status Code:200 OK (from cache)
Query String Parametersview sourceview URL encoded
1373516123:

並不會有request header及response header,因為根本沒送request chrome也會顯示是 (from cache)

結語:

快取機制還是要看瀏覽器自已的實作, 如果到已造訪過的網站, chrome第一次可能拿快取的圖片, 但reload後chrome還是會去問一次得到304才去拿cache的檔案, 所以快取機制沒有一定, 或許chrome 再下一版又換方法了

註:

大頭貼檔名後加上從DB取的timestamp(ex: avatar.jpg?1373530721),當更新大頭貼時要更新DB的欄位(紀錄修改時間),才能讓瀏覽器判斷是否是修改過的圖

ref : 夯哥

PHP - Codeigniter

判斷DB是否執行成功

判斷select

if ($query->result_array())
{
    // display data..
}
return FALSE;

判斷insert

if ($this->db->insert_id())
{
    return TRUE;
}

判斷update是否成功更新

if ($this->db->update('shares'))
{
    return TRUE;
}

return ($this->db->affected_rows() > 0) ? TRUE : FALSE;

判斷delete是否成功刪除

$this->db->delete('question_options');
if ($this->db->affected_rows() > 0)
{
  return TRUE;
}
return FALSE;

欄位更新 ex: +1

$this->db->set('lose', 'lose+1', FALSE);
$this->db->where('guid', $data['guid']);
$this->db->update('users');

Form Validation 的 callback

 array(
     'field' => 'current_passwd',
     'label' => 'lang:users-current_passwd',
     'rules' => 'trim|required|callback_check_password[13]'
 )

 function check_password($current_passwd, $uid)
 {
      // $current_passwd : CI 會自已捕捉 HTML 裡 name="current_passwd" post 進來的值,放入第一個參數
      // $uid = 13

      if ( // Condition )
      {
          return TRUE;
      }
      return FALSE;
 }

給 callback 的參數是從第二個參數開始給,第一個參數都是保留給自已那個欄位的值(current_passwd的值)

Load model in Library

private $CI = NULL;

public function __construct()
{
    $this->CI =& get_instance();
    $this->CI->load->model('test_model');
}

codeigniter 寄信錯誤 “無法使用 mail() 函式發送信件. 主機可能未設定此方法發送信件”

可能沒有安裝sendmail

sudo apt-get install sendmail

測試sendmail 寄信

$ sendmail jex_lin@example.com      <= enter
Subject: test                        <= enter
test                                 <= enter
.                                    <= enter

OR

echo "My test email being sent from sendmail" | /usr/sbin/sendmail jex_lin@example.com

CI input會自動過濾 %00~%08 %0b %0c %0e %0f

system/core/Input.phpCI_INPUT class__contruct() 會執行 _sanitize_globals metheod

_sanitize_globals method 會過濾一切 $_GET$_POST 變數所以即使拿原生函數 $_GET 去抓都沒有用,會先被ci的 method 給過濾掉

而在這裡面它執行的是 _clean_input_data 這個 method 而它又會去執行

remove_invisible_characters 這個 function,而它是在 system/core/Common.php

就是由它去過濾 %00-%08 %0b %0c %0e %0f,把它們轉成空字串

可藉由 remove_invisible_characters 的第二個參數指定為 false 關閉過濾

自訂 controller

/application/core/MY_Controller :

require_once 'Base_Controller.php';

class MY_Controller extends CI_Controller {

CI 的 log_message

log_message("INFO", "data: " . var_export($data, TRUE));

第二個參數預設為 FALSE : If you would like to capture the output of print_r(), use the return parameter. When this parameter is set to TRUE, print_r() will return the information rather than print it.

crop avatar

$crop_x = $this->input->post('crop_x');
$crop_y = $this->input->post('crop_y');
$crop_w = $this->input->post('crop_w');
$crop_h = $this->input->post('crop_h');

// calculate new coordinate, width and height
list($ori_width, $ori_height) = getimagesize($original_image_path);
list($pre_width, $pre_height) = getimagesize($preview_image_path);
$width_percent = round($ori_width / $pre_width, 2);
$height_percent = round($ori_height / $pre_height, 2);
$new_x = (int) round($crop_x * $width_percent);
$new_y = (int) round($crop_y * $height_percent);
$new_w = (int) round($crop_w * $width_percent);
$new_h = (int) round($crop_h * $height_percent);

//crop
$config = array(
    'image_library'     => 'gd2',
    'source_image'      => $original_image_path,
    'new_image'         => $destination_image,
    'quality'           => 100,
    'maintain_ratio'    => FALSE,
    'x_axis'            => $new_x,
    'y_axis'            => $new_y,
    'width'             => $new_w,
    'height'            => $new_h
);
$this->image_lib->initialize($config);
if ($this->image_lib->crop())
{
    // resize to 180 * 180
    $config = array(
        'image_library'     => 'ImageMagick',
        'library_path'      => '/usr/bin',
        'source_image'      => $destination_image,
        'quality'           => 100,
        'maintain_ratio'    => TRUE,
        'width'             => 180,
        'height'            => 180
    );
    $this->image_lib->initialize($config);
    if ($this->image_lib->resize())
    {
        // update last_avatar_time
        $this->load->model(array('users_model', 'userlog_model'));
        $time = time();
        if ($this->users_model->update($user['account'], array('last_avatar_time' => $time)))
        {
            // update session
            $update_session_data = array('avatar_url' => get_avatar_path($user['id'], FALSE) .'?'. $time);
            $this->login->update_session($update_session_data);

            $this->userlog_model->insert($user['id'], $user['name'], UserLog_model::USER_AVATAR_UPDATE);
        }

        $this->_outputJSON(array('status' => 'ok'));
    }
}

parseImg

$url = $this->input->get_post('url');
if ( ! $html_content = @file_get_contents($url))
{
    return $this->_outputJSON(array('status' => 'fail', 'error' => 'Wrong url'));
}
$patten = "/<img.*src=\"(((http[s]?):\/\/|www\.)([^\s\[\\\"'])+)\".*>/";
if (preg_match_all($patten, $html_content, $matches, PREG_SET_ORDER))
{
    foreach ($matches as $key => $match)
    {
        if (list($width, $height) = @getimagesize($match[1]))
        {
            if ($width >= 100 OR $height >= 100)
            {
                return $this->_outputJSON(array('status' => 'ok', 'url' => $match[1]));
            }
        }
        else
        {
            unset($matches[$key]);
        }
    }
    if ( ! empty($matches))
    {
        reset($matches);
        return $this->_outputJSON(array('status' => 'ok', 'url' => current($matches)[1]));
    }
}
return $this->_outputJSON(array('status' => 'fail', 'error' => 'Image not found'));

Git 常用指令

(最後更新: 2016-04-27)

git add

加入空的目錄

建立 gitkeep.keep 在空的目錄

git branch

  • 都不加 : 顯示 local branch
  • -r : 顯示 remote branch
  • -a : 顯示所有 branch (local + remote)
  • -v : 顯示branch的詳細資料
  • --merged : 列出已經merge的branch
  • --no-merged : 列出尚未merge的branch

Rename branch

git branch -m {old_name} {new_name}

Rename current branch

git branch -m {new_name}

刪除branch

git branch -d developXD

git merge

讓 commit log 紀錄您是開分支出去再 merge 回來的。

git merge --no-ff develop

Undo 某一個 merged 但未 push 的 branch

有幾種方法

1) git reset --hard HEAD~1

2) 先用 git reflog 看 log, 再把 HEAD@{1} 的 SHA1 記下來 (不是 HEAD@{0} 的, 因為那是你目前的點, 我們要還原到前一動), 再執行 git reset --hard {SHA1}

3) 用 git reset --hard {COMMIT_ID} 到早一點的點, 然候再慢慢補齊後來的 commit

如果對結果不太確定的話, 建議先用目前的 branch 建一個測試用的 branch 練習, 確定結果符合你要的再做在原本的 branch

git rebase

rebase 基本操作概念

類似 merge, 但將兩個不同的 branch 合併成同一條線且不會有 merge commit, tig 的線圖也會比較漂亮

1) rebase master

git checkout feature
git rebase master

2) merge 回 master

git checkout master
git merge feature

rebase 其他指令

如果有發生 conflict 並修好後要繼續 rebase

git rebase --continue

git merge 與 git rebase 差異

兩者結果是一樣的,產生的 history 不同點 :

  • git merge
    • 當 dev merge master, dev 會在另一條線做 merge, 所以 history 會看起來比較亂
    • conflict 只需要處理一次
  • git rebase
    • 當 dev rebase master, dev 會接在 master 最後面, 即使 master 有新的 commit 也會接在最後面, 所以它的 history 會很漂亮, 就只有一條
    • conflict 需要一個一個處理, 假如 dev 有兩個新的 commit id, 就需要處理兩次
    • 或用 git pull --rebase 也能達到一樣效果

使用時機 :

git checkout

新增 develop branch 並且切換過去

$ git checkout -b develop

新增由 develop 分支出來的 myfeature

$ git checkout -b myfeature develop

使檔案回復成最近一次commit的狀態

$ git checkout -- test.php

強制回復己在 add 狀態被修改過的檔案, Untracked files 則不受影響

git checkout -f

所有track中且修改過的檔案回復成最近一次commit的狀態

git checkout .

切到某一個 commit

git checkout 62a4a5c9a6e8a323a1ea12ec54ac35da7ce1b662

從某個 commit 切回原本 branch

git checkout -
相當於 git checkout master (不一定是 master 取決於在哪一個 branch)

git commit

  • --amend : 修改最後一次commit message

git reset

  • --soft : 回復到之前的狀態, 但修改過的檔案仍不變
  • --hard : 回復到之前的狀態, 但修改過的檔案仍會保留

回復到某一個 commit id

git reset --hard {commit id}

回復上一個 commit (包含 merge) ,但條改的資料不保留,也就是回覆到上一個 commit 的初始狀態

git reset HEAD^ --hard

回復上一個 commit 但修改的資料保留下來

git reset HEAD^ --soft

回復到 origin/master 的狀態 (當 merge 或做了什麼後悔的事, 在還沒 push 前都可以使用此指令還原到 remote 的最新狀態)

git reset --hard origin/master

git reset HEAD^ --hard 如何救回

流程

git reflog                  # 會有你的每條 git 操作的 log, 左邊有 commit id, 記下你要回復的 commit id
git reset --hard bceefb7    # 這樣就可以救回了

如果不放心可以自行測試

$ git init
Initialized empty Git repository in .git/

$ echo "testing reset" > file1
$ git add file1
$ git commit -m 'added file1'
Created initial commit 1a75c1d: added file1
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 file1

$ echo "added new file" > file2
$ git add file2
$ git commit -m 'added file2'
Created commit f6e5064: added file2
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 file2

$ git reset --hard HEAD^
HEAD is now at 1a75c1d... added file1

$ cat file2
cat: file2: No such file or directory

$ git reflog
1a75c1d... HEAD@{0}: reset --hard HEAD^: updating HEAD
f6e5064... HEAD@{1}: commit: added file2

$ git reset --hard f6e5064
HEAD is now at f6e5064... added file2

$ cat file2
added new file

ref: stackoverflow

git revert

與 reset 不同的事他回復是會有紀錄的,log 會疊下去,所以如果你不小心 push 了一個 commit 到 master,你想回復,用 revert 會比 reset 簡單操作

回復上一個 commit

git revert HEAD

回復到某一個 commit id

git revert {commit_id}

將某一個已經 commit/merge 的 commit_id 回復

git revert --strategy resolve 305a9bd07ae40c585ac2f2761dea4374e7fee93e

回復後它會有一個 commit 紀錄, 如果想再把它加回來, 無法用 merge (Already up-to-date.), 但可以用 cherry-pick 加回來

git cherry-pick

將某一個 branch 的某一個 commit 的變動 merge 到另一條 branch

假如 develop 只想要 merge feature 的某一個 commit, 則需要先把 feature 的那條 commit id 記下來, 再切到 develop 做 cherry-pick

git checkout develop
git cherry-pick f08515bc579a06dd9c8bd7f2dfc30ad4d5a73646

git show

查看某一個 tag 的變動

git show v1.0.0

git log

  • --stat : 列出有增減的檔案
  • --oneline : 精簡的log,每一次的commit資訊為一行,只顯示前7碼的SHA1及message

最近一次 commit 改過的檔案

git log -n 1 --stat

最近一次 commit 的更改細節

git log -n 1 -p

git reflog

顯示 git 的每個 log (包括操作 command 及 commit 內容等等)

git diff

  • default or -- : 非 staged 裡的與目前版本的差異
  • --staged or --cached(1.6版前) : stage 裡的與目前版本的差異
  • HEAD^ : 比較上一個commit的差異
  • branch : 比較branch的差異
  • --stat : 查看差異的概要 (顯示一堆 +, -)
  • sha1..sha1 : 比較兩個commit差異

未加入 stage 時直接下 :

git diff qq.php

已加入 stage 要下 :

git diff HEAD qq.php

比較兩個 branch :

git diff develop master

git config

  • --list : 查看 config 內容,也可以用另種方式看 cat ~/.gitconfig
  • --unset : reset 某個 config. e.g. git config --global --unset {key}

git rm

  • --cache filename.php : 將已被 git 追蹤的檔案取消追蹤, 放心! 它並不會將檔案刪除, 而將狀態變成 Untracked
  • --cached filename.php : 結果似乎和 --cache 一樣

git push

強制 push, 之前的commit 紀錄會不見,只會有目前這個branch的commit 紀錄

git push origin master -f

建立遠端 branch

git branch dev          // 先建立 local branch
git push origin dev:refs/heads/dev

git pull

git pull --rebase 等於以下兩步

git fetch
git merge origin dev

git pull –rebase 會使 merge commit 不見, 改用 :

git fetch origin
git rebase -p origin/develop

在目前 branch 下 git pull origin {branch_name}: 代表將 {branch_name} (remote branch) merge 到目前這個 branch

git stash

放進暫存

git stash

查看暫存

git stash list

取出暫存

git stash pop

清空暫存

git stash clear

如果暫存有多個,可以指定要還原哪個commit ID

git stash [commit ID]

stash specific files

git add a1.go                   // files that You don't want to stash
git stash save --keep-index     // this command will stash the rest of files

git tag

查看目前 tag

git tag
v1.0.0

新增 tag

git tag -a v1.4 -m "my version 1.4"
git push --tags

git fetch

更新 git branch -r 的名單

git fetch

(建議) 更新 remote branch list (遠端已刪除的 branch, 本機 branch 會幫你刪除,同時也會更新新增的 remote branch)

git remote update origin --prune

更新 remote branch list (遠端已刪除的 branch, 本機 branch 不會刪除,只會更新新增的 remote branch)

git fetch --all

當要 merge 遠端 branch 前,先看修改了什麼,再merge

git fetch origin dev
tig
git merge origin dev

git remote

新增遠端 repo

git remote add origin https://git.heroku.com/my-app.git

查看remote有哪些branch

git branch -r
git remote show origin

有時候用 git branch -r, 查看 remote branch 時, 發現明明刪掉的 branch 為何還在? 使用 git remote prune origin 來更新暫存檔, 就可以取得最新的 remote branch list

git rev-parse

latest commit hash

git rev-parse HEAD

remote branch

create

git push origin develop:refs/heads/branch_to_create
git fetch origin
git branch --track branch_to_create origin/branch_to_create
git checkout branch_to_create

track

git fetch origin
git branch --track branch_to_track origin/branch_to_track

delete remote branch

git push origin --delete <branch_name>

rename

# 如果 branch 已存在要先 remove branch
git push origin --delete <branch_name>


git push origin develop:refs/heads/branch_to_rename            # 這步只是將遠端 develop copy 成 branch_to_rename, develop 還在
git fetch origin
git branch --track branch_to_rename origin/branch_to_rename
git checkout branch_to_rename
git push origin :refs/heads/develop
git branch -D develop

publish

git push origin branch_to_publish:refs/heads/branch_to_publish
git fetch origin
git branch -u origin/branch_to_publish branch_to_publish
git checkout branch_to_publish

其他常用指令

  • git blame test.qq : test.php這份檔案顯示每行的編輯者
  • git branch -r : 查看遠端 branch
  • git add -u : 自動將所有的檔案都放到 stage 等待 commit, 而重要的是被刪除過的檔案也會一起放進去, 不需用 git rm 一個一個點被刪除過的檔案, 很方便!
  • tig : 顯示樹狀
  • gitk : 顯示樹狀 (GUI version)

prune 是在 grb delete 後才需要做的

減少 Merge branch 'master' of XXX 這種多餘節點

加上--rebase :

git pull --rebase

若在不同的 branch 要接遠端的 master 更新的話,執行:

git pull --rebase origin master

不過使用--rebase是在沒有conflict的情況下使用,它並不會像git merge那樣聰明地處理conflict,原理是rebase並沒有參考parent節點做同步

遇到conflict作法

如果產生了衝突回復 pull 前的狀態 :

git rebase --abort

然候合併code與 master 同步 :

git merge origin master

但這樣就還是會產生 Merge branch 'master' of XXX 節點

ref : 小莊

更新從別人 fork 過來的 repository

IMAG1680.jpg

git remote add upstream git@...(略)...
git remote -v
git pull upstream master

註 :

git pull upstream master 也等於

git fetch upstream
git merge upstream/master

將 commit 的東西 merge 到 qa

commit 後copy commit id

git checkout qa
git pull
git cherry-pick d69ad27d92cad1021481ecab731c0cc6552432bf    // 只 merge 我剛修改的
git push
git log -1
git show 41e24b9349acc9bbc1b8853284866f498892ce4b // 查看此 commit 改了什麼

ref : 夯哥

查看 commit id 修改內容

git show 41e24b9349acc9bbc1b8853284866f498892ce4b

.gitignore not working

git rm -r --cached .
git add .
git commit -m ".gitignore is now working"

將 diff 以 vimdiff 方式顯示

git config --global diff.tool vimdiff       # 使用 difftool 就可以啟動 vimdiff
git config --global difftool.prompt false   # 執行上面指令, 會問你是否執行, 很麻煩所以關掉它
git config --global alias.d difftool        # 使用 `git d` 相當於 `git difftool`

Trouble shooting

git clone error

$ git clone https://github.com/jex-lin/conf.git
Cloning into 'conf'...
error: Problem with the SSL CA cert (path? access rights?) while accessing https://github.com/jex-lin/conf.git/info/refs
fatal: HTTP request failed

解決 :

git config --global http.sslVerify false

contributing (pull request)

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am ‘Add some feature’)
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

如何寫 commit messages

1) Subject 與 body 以一行空格分開

Derezz the master control program

MCP turned out to be evil and had become intent on world domination.
This commit throws Tron's disc into MCP (causing its deresolution)
and turns it back into a chess game.

這樣git log --oneline or git shortlog就可以只顯示主旨

2) Subject 不要超過 50 字元, 開頭要大寫, 結尾不需句號

3) 簡明的 subject

Refactor subsystem X for readability
Update getting started documentation
Remove deprecated methods
Release version 1.0.0
也可以用以下的句子
If applied, this commit will refactor subsystem X for readability
If applied, this commit will update getting started documentation
If applied, this commit will remove deprecated methods
If applied, this commit will release version 1.0.0
If applied, this commit will merge pull request #123 from user/branch

4) Body 在 72 字元換行,確保加上 git 本身的 indent 每行可以在 80 字元內

5) Body 用來解釋 what changed and why,主角不是 how

Simplify serialize.h's exception handling

Remove the 'state' and 'exceptmask' from serialize.h's stream
implementations, as well as related methods.

As exceptmask always included 'failbit', and setstate was always
called with bits = failbit, all it did was immediately raise an
exception. Get rid of those variables, and replace the setstate
with direct exception throwing (which also removes some dead
code).

As a result, good() is never reached after a failure (there are
only 2 calls, one of which is in tests), and can just be replaced
by !eof().

fail(), clear(n) and exceptions() are just never called. Delete
them.

ref : http://chris.beams.io/posts/git-commit/

其他參考 message

Other bug fixes
    * Fixed an issue that of forcibly terminating while running Peel remoate application.
    * The latest Android security patch has been applied.
    * Device security has been further enhanced.
Fixed the XXX bug in YYY

該用 present tense 還是 past tense?

兩邊都有支持者, git 本身的專案的 message 是用 present tense, 但有人覺得 present tense 是告訴別人將要做什麼, 用 past tense 比較正確, 但查了一輪下來, 支持 present tense 的人比較多一些, 我的建議是取決於團隊的 style, 當多數人用哪個就用哪個

tig

只顯示 merge 線圖

tig --merges

use Git’s default commit order

tig --topo-order

PHP Examples

轉換容量顯示方式 (ex: 15.31GB)

function reable_size($bytes)
{
    if ($bytes == 0)
    {
        return '0 MB';
    }

    $unit = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB');

    $show_size = $bytes / pow(1024, ($i = floor(log($bytes, 1024))));
    if (intval($show_size) == $show_size)
    {
        return $show_size . ' ' . $unit[$i];
    }

    return number_format($show_size, 2) . ' ' . $unit[$i];
}

IP:

function get_ip()
{
    $inspect_type = array(
        "HTTP_CLIENT_IP",
        "HTTP_X_FORWARDED_FOR",
        "HTTP_X_FORWARDED",
        "HTTP_X_CLUSTER_CLIENT_IP",
        "HTTP_FORWARDED_FOR",
        "HTTP_FORWARDED"
    );

    foreach ($inspect_type as $type)
    {
        if (array_key_exists($type, $_SERVER))
        {
            $ips = explode(",", $_SERVER[$type]);
            foreach ($ips as $ip)
            {
                 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE))
                 {
                     return $ip;
                 }
            }
        }
    }
    return $_SERVER["REMOTE_ADDR"];
}

產生唯一字串(共32個小寫英文及數字)

echo md5(uniqid(rand(), TRUE));

產生亂數陣列(每個數字唯一,srand指定亂數產生)

php > $a = range(0, 29);
php > srand(333);           <= 隨意指定隨機數字
php > shuffle($a);          <= 按照隨機數字做排序
php > echo print_r($a);
Array
(
    [0] => 26
    [1] => 21
    (..略..)
    [28] => 15
    [29] => 24
)

判斷是否為ajax 傳進來的值(而不是submit傳入)

if (isset($_SERVER['HTTP_X_REQUESTED_WITH']))
{
    if (mb_strtoupper($_SERVER['HTTP_X_REQUESTED_WITH']) === "XMLHTTPREQUEST")
    {
        // do something...
        exit;
    }
}

日期與時間

設定台北時區

date_default_timezone_set("Asia/Taipei");

現在的時間(秒)

time();                                         //13551194547

將日期時間轉換為秒

strtotime('05/29/2013');

獲取目前的日期與時間

date('Y-m-d H:i:s');                            //2012-12-11 02:55:08

日期加1天、加5個月

date('Y-m-d', strtotime('+1 day'));             //加一天
date('Y-m-d', strtotime('+5 month'));           //加五個月

將時間(秒)還原回日期與時間正常格式

strftime('%m/%d/%Y %H:%M:%S', 1283846400);      //09/07/2010 16:00:00

產生時間數字型態(同strtotime)

mktime(0, 0, 0, date('m'), date('d'), date('Y')) + (60 * 60 * 24 * $day_offset);

Timestamp 產生至小數位

microtime(true);    // 1504695682.0988

註: .0000 也不會出錯
1504695796.9996
1504695796.9998
1504695796.9999
1504695797.0001
1504695797.0003
1504695797.0004

1504695867.9995
1504695867.9997
1504695867.9999
1504695868
1504695868.0002
1504695868.0004
1504695868.0005

DateTime 產生至小數位(推薦此方法)

$now = DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
echo $now->format('Y-m-d H:i:s.u')."<br>";

DateTime 產生至小數位 DateTime createFromFormat error: call to a member function format() on boolean

不要用 $now = DateTime::createFromFormat('U.u', microtime(TRUE)) 當0000, $now->format('Y-m-d H:i:s.u') 會得到 fatal error : call to a member function format() on boolean

即使用 if (is_bool($now)) $now = DateTime::createFromFormat('U.u', $aux += 0.001) 在超過 9999 有一段極短的時間日期會是 1970-01-01

    2017-08-25 03:06:59.999900
    2017-08-25 03:06:59.999900
    2017-08-25 03:06:59.999900
    1970-01-01 00:00:00.001000
    1970-01-01 00:00:00.002000
    1970-01-01 00:00:00.003000
    1970-01-01 00:00:00.004000

DateTime 使用 time zone

$timezone = new DateTimeZone("Asia/Taipei");
$time = new DateTime("now", $timezone);
$now = $time->format("Y-m-d H:i:s");

DateTime 使用 time zone 取前一分鐘

$timezone = new DateTimeZone("Asia/Taipei");
$time = new DateTime("1 minutes ago", $timezone);
$timestamp = $time->format("YmdHi");

Convert time and date from one time zone to another

$date = new DateTime('2000-01-01', new DateTimeZone('Pacific/Nauru'));
echo $date->format('Y-m-d H:i:sP') . "\n";

$date->setTimezone(new DateTimeZone('Pacific/Chatham'));
echo $date->format('Y-m-d H:i:sP') . "\n";

刪除test.txt檔再將日期寫入test.txt檔100000次

$ts = array_sum(explode(" ", microtime()));

unlink("./test.txt");
for ($i = 0; $i < 100000; ++$i) {
    file_put_contents("./test.txt", date("Y-m-d H:i:s"), FILE_APPEND);
}
echo array_sum(explode(" ", microtime())) - $ts, "\n";

FILE_APPEND參數可參考php manual

輸出 csv 供下載

產生時間做為檔名,並且開啟檔案,檔案若不存在會自行建立

$file_name = array_sum(explode(" ", microtime())) . '.csv';
$file_path = '/var/www/csv/' . $file_name;
$fp = fopen($file_path, 'w');

將陣列寫入csv檔

fputcsv($fp, array('field1', 'field2', 'field3');
foreach ($record as $row)
{
    fputcsv($fp, array($row[1], $row[2], $row[3]));
}
fclose($fp);

讓瀏覽器可以輸出下載

header('Content-Type: application/csv');
header("Content-Disposition: attachment; filename={$file_name}");
header('Pragma: no-cache');
readfile($file_path);

php 將陣列組回url

EX:

echo urldecode(http_build_query(array('a' => array(1, 2, 3))));

結果 :

a[0]=1&a[1]=2&a[2]=3

uids array :

$uids = ["1","2","4","5"];
echo urldecode(http_build_query(array('uids' => $uids)));

結果 :

uids[0]=1&uids[1]=2&uids[2]=4&uids[3]=5

顯示目錄結構

$dir = dir('/var/www/test');
while ($folder = $dir->read())
{
    if ($folder == '.' || $folder == '..')
    {
        continue;
    }
    echo "{$folder}\n";
}

php two files combine

$file1_path = '/home/jex_lin/test/qq';
$file2 = file_get_contents('/home/jex_lin/test/cc');
file_put_contents($file1_path, $file2, FILE_APPEND);

連接 DB 及測試是否正常

建立測試的 DB 及 Table, 並透過 PDO 執行測試結果

sql :

建立 testDB Database 及 tt Table, 及建立一筆測試資料

php :

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'root', '');
$stat = $db->prepare("Select * from tt");
$stat->execute();
$result = $stat->fetchAll();
print_r($result);

$stat = $db->prepare("SELECT * FROM users WHERE account = ? AND password = ?");
$stat->execute(array($account, $password));
$result = $stat->fetch(PDO::FETCH_ASSOC);  // 如果沒找到是 false,有的話是 array

Server push by http1.1 chunked (都由 server 推給 client)

view :

<html lang="zh-TW">
<head></head>
<body>
<img src="image server 的連結">
</body>
</html>

image server :

<?php
$boundary = 'thisIsTheBoundary';

header( "Content-Type: multipart/x-mixed-replace; boundary=\"$boundary\"\r\n" );

echo "--$boundary\n" ;

for( $i=1 ; $i<= 10 ; $i++ )
{
        echo 'Content-Type: image/jpeg'."\n\n";

        echo file_get_contents( "images/$i.jpg" );

        echo "\n--$boundary\n" ;

        flush();
        sleep(1);
}
?>

PHP Memory

function convert($size)
{
   $unit=array('b','kb','mb','gb','tb','pb');
   return @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i];
}
echo $this->convert(memory_get_usage(true)); // 123 kb

費式數列

function fibonacci($n,$first = 0,$second = 1)
{
    $fib = [$first,$second];
    for($i=1;$i<$n;$i++)
    {
        $fib[] = $fib[$i]+$fib[$i-1];
    }
    return $fib;
}

print_r($this->fibonacci(50));

寄信

PHP 原生 mail 函數是使用 sendmail 來寄送, 請先安裝 sendmail

$email = "jxxxlin@gmail.com";
$subject = "Test";
$msg = "Hello world!";
$header = "From: Jex Lin<jxxxlin@gmail.com>\r\n";
$header .= 'Reply-To: jxxxlin@gmail.com' . "\r\n";          // 預設收件者回覆的對像
$header .= 'Cc: jex@example.com' . "\r\n";                  // 副本, email 會出現在信的副件上
$header .= 'Bcc: jex@example.com' . "\r\n";                 // 也是副本, 但 email 不會出現在信的副件上
$header .= "Content-Type: text/html; charset=utf-8\r\n";    // 可使用 HTML Tag
if (mail($email, $subject, $msg, $header))
{
    echo "success";
}
else
{
    echo "fail";
}

mb_substr 問題

之前碰到一個問題, 在 apache 與 cli 下 mb_substr 的結果不一樣,

如果只是將解析出來的 string 在 log 印出來是長一樣的, 但是將它寫進 file 的話結果就不同了

以下兩個環境解析出來字串的差別, 幾乎長一樣, 不一樣的是在最後面的字元

  • apache : % lt<8b>^@~<8f>¡ÅU¼0^Nß|×ÈI
  • cli : % lt<8b>^@~<8f>¡ÅU¼0^Nß|

最後去查官方文件 string mb_substr ( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]] )

原來最後一個編碼的參數是抓 PHP 的系統參數, 然候我再把這個參數印出來看

  • apache : UTF-8
  • cli : ISO-8859-1

確認是採用的編碼不同, 造成字串截取的結果不一樣

解決的方法可以設定在系統變數, 或寫在 mb_substr 參數裡或直接設定 mb_internal_encoding('ISO-8859-1');

AES 加解密

  • 密鑰 : AES 使用對稱式密鑰,加解密都要用同一把密鑰
  • IV (Initialization Vector) : 避免加密結果每次都一樣
  • Paadding : 對未加密的資料做填充, 補滿 128 位元, 常見的方式為 PKCS5

example :

$iv_length = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_length, MCRYPT_RAND);
$key = 'jex';
$text = 'secret data';

# 加密
$encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
echo base64_encode($encrypt) . "\n";        // 0gSYCbCcf/u1S78uIVXWZw==

# 解密
$decrypt = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypt, MCRYPT_MODE_CBC, $iv);
echo $decrypt . "\n";                       // secret data

JSONP

protected function _outputJSON($data)
{
    $callback = $this->input->get_post('callback', TRUE);
    if (empty($callback))
    {
        header("Content-type: application/json");
    }
    else
    {
        header('Content-Type: application/javascript');
    }
    $json_str = json_encode($data);
    echo (empty($callback)) ? $json_str : $callback . "(" . $json_str . ")";
    exit;
}

linux 檔案脫逸,支援 windows 支援的符號(也就是除了 \ / : * ? “ < > |)

private function _escape_cmd_argv($path)
{
    $path = str_replace(
        [ ' ', '"',  '`',  '^',  '!',  '$',  '&',  '(',  ')',  '[',  ']',  '{',  '}',  ',',  ';',   "'"],
        ['\ ','\"', '\`', '\^', '\!', '\$', '\&', '\(', '\)', '\[', '\]', '\{', '\}', '\,', '\;', "\\'"],
        $path
    );
    return $path;
}

將 JSON 輸出在 html hidden 欄位裡

php :

$convenience_store = [
    array(
        'value'     => 70,
        'label'     => '7-11',
        'formatted' => '12552 間',
    ),
];

html :

<input type="hidden" id="getData" value=""<?php echo htmlspecialchars($convenience_store, ENT_QUOTES); ?>"/>

注意 htmlspecialcharsENT_QUOTES 參數

js :

var getData = JSON.parse($('#getData').val());

output mp4 - support range

<?php
$file = 'test.mp4';
$fp = @fopen($file, 'rb');
$size   = filesize($file); // File size
$length = $size;           // Content length
$start  = 0;               // Start byte
$end    = $size - 1;       // End byte
header('Content-type: video/mp4');
//header("Accept-Ranges: 0-$length");
header("Accept-Ranges: bytes");
if (isset($_SERVER['HTTP_RANGE'])) {
    $c_start = $start;
    $c_end   = $end;
    list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
    if (strpos($range, ',') !== false) {
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start-$end/$size");
        exit;
    }
    if ($range == '-') {
        $c_start = $size - substr($range, 1);
    }else{
        $range  = explode('-', $range);
        $c_start = $range[0];
        $c_end   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
    }
    $c_end = ($c_end > $end) ? $end : $c_end;
    if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start-$end/$size");
        exit;
    }
    $start  = $c_start;
    $end    = $c_end;
    $length = $end - $start + 1;
    fseek($fp, $start);
    header('HTTP/1.1 206 Partial Content');
}
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: ".$length);
$buffer = 1024 * 8;
while(!feof($fp) && ($p = ftell($fp)) <= $end) {
    if ($p + $buffer > $end) {
        $buffer = $end - $p + 1;
    }
    set_time_limit(0);
    echo fread($fp, $buffer);
    flush();
}
fclose($fp);
exit();
?>

Ref : http://www.tuxxin.com/php-mp4-streaming/

Send a notification to android device.

define("GOOGLE_API_KEY", "***************************************");
define("GOOGLE_GCM_URL", "https://android.googleapis.com/gcm/send");

function send_push_notification($registatoin_ids, $message)
{
    $fields = array(
            'registration_ids' => $registatoin_ids,
            'data' => array('message'=>$message)
            );

    $headers = array(
            'Authorization: key=' . GOOGLE_API_KEY,
            'Content-Type: application/json'
            );

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, GOOGLE_GCM_URL);
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
    $result = curl_exec($ch);
    if ($result === FALSE) {
        return array(FALSE, 'curl error : ' . curl_error($ch));
    }
    curl_close($ch);
    $result_arr = json_decode($result, TRUE);
    if ( ! $result_arr)
    {
        return array(FALSE, 'json err : ' . $result);
    }
    if ($result_arr['success']=='1')
    {
        return array(TRUE, 'Message successfully delivered');
    }
    else
    {
        return array(FALSE, json_encode($result_arr['results']));
    }
}

/*
$regids = array('*********************************-Cv4vK8p54hH674CHvCfHQq0_dAw749k********************k6cuKsO_O_riIx_EyFGZEgNrHA**********************');
$message = 'hello world ' . date('Y-m-d H:i:s');

echo "start\n";
list($s, $e) = send_push_notification($regids, $message);
var_dump($s);
echo $e . "\n";

*/

Send a notification to ios device.

function send_notify2APNS($deviceToken, $message, $env = 'development', $debug=FALSE)
{

    log_message("INFO", "token is : " . $deviceToken);
        $passphrase = '';

        $fp  = FALSE;
        $ctx = stream_context_create();
        if ($env == 'development')
        {
            $pem_file = '/home/qq/pem/jemco_dev_push.pem';
            stream_context_set_option($ctx, 'ssl', 'local_cert', $pem_file);
            stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
            $fp = stream_socket_client(
                'ssl://gateway.sandbox.push.apple.com:2195', $err,
                $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
        }
        else
        {
            return array(FALSE, 'Env is not development');
        }

        if (!$fp)
        {
            return array(FALSE, "Failed to connect: $err $errstr");
        }
        $body['aps'] = array(
                'alert' => $message,
                'sound' => 'default'
                );
        $payload = json_encode($body);
        $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
        $result = fwrite($fp, $msg, strlen($msg));
        fclose($fp);

        return ($result) ? array(TRUE, 'Message successfully delivered') : array(FALSE, 'Message not delivered');
}
/*
$deviceToken = '6529a573393c*********************************27639b';
$message = file_get_contents('http://whatthecommit.com/index.txt');
list($s, $m) = send_notify2APNS($deviceToken, $message);
echo $m . "\n";
*/

php array 處理

確保 json_encode 後不會有索引

array_unique 做 json_encode 時,會得到索引->值, 當其他強型態語言(e.g. golang)在事先指定只接收 array 的話,在 decode 時會出錯, 可以用此方法做 unique 但 encode 不會有 key

array_keys(array_flip($test_array))

array_map(‘strval’, 將 value 都轉成 string,避免值是 int 造成其他強型態語言錯誤

array_map('strval', array_keys(array_flip($test_array)))

curl 上傳

php 5.6 用 @

'image' => '@' . $_FILES['image']['tmp_name']   // e.g. @/tmp/phpDV7N6h

php 7.0 用 CURLFile

'image' => new CURLFile($_FILES['image']['tmp_name'], 'image/jpeg', $_FILES['image']['name']),

code

$post_data = [
    'image' => new CURLFile($_FILES['image']['tmp_name'], 'image/jpeg', $_FILES['image']['name']),
];
$ch = curl_init();
curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER  => 1,       // return response body
        CURLOPT_TIMEOUT         => 10,
        CURLOPT_URL             => "http://127.0.0.1:8080/user/avatar/upload",
        CURLOPT_POST            => 1,
        CURLOPT_POSTFIELDS      => $post_data,
    ]);
$resp_body = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close ($ch);
if ($httpcode == 200)
{
    // do something
}

Apache

基本設定

/etc/apache2/sites-available/jex.conf :

<VirtualHost *:80>
        ServerName example.com
        ServerAdmin admin@example.com

        DocumentRoot /var/www/jex
        <Directory /var/www/jex>
                RewriteEngine on
                RewriteCond $1 !^(index\.php|static|crossdomain\.xml|robots\.txt|favicon\.ico)
                RewriteRule ^(.*)$ /index.php/$1 [L]

                Options Indexes FollowSymLinks MultiViews
                AllowOverride All
                Order allow,deny
                allow from all

                ExpiresActive On
                ExpiresByType image/jpg "access plus 10 years"
                ExpiresByType image/jpeg "access plus 10 years"
        </Directory>
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
        LogLevel warn
</VirtualHost>

啟動 conf

sudo a2ensite jex

Options

  • All: 以下諸功能皆有
  • None: 以下諸功能皆無
  • Indexes: 自動產生目錄的索引, 將Indexes拿掉, 點選連結目錄就不會秀出目錄來, 會改成403 forbidden的訊息頁面, 如果網站下的 public 下有放 symbolic link 就要將此開啟, 才可以被 access
  • Includes: 提供 SSI (Server-Side Inclues) 功能, 即使用Apache的指令在html檔中寫程式, 須先載入 includes_module
  • FollowSymLinks: 遵循符號鏈接, 即能夠連到其它的目錄去執行, 會壓過 SymLinksIfOwnerMatch。
  • SymLinksIfOwnerMatch: 對符號鏈接及其每一層父資料夾, 都進行權限檢查, 當連結檔本身的owner跟連結目的地的owner不同時拒絕存取, 比 FollowSymLinks 更安全
  • ExecCGI: 可以執行CGI程式。
  • MultiViews: 送出多國語言支援的頁面. 此功能必須被明確指定, Options All並不會提供這個功能

打開 Rewrite 模組

cd /etc/apache2
a2enmod
rewrite

設置轉向自訂的404頁面

ErrorDocument 404 /404.php

建立多個virtual host

  1. /etc/apache2/sites-available建立virtual host, ex: test
  2. sudo a2ensite test
  3. sudo service apache2 restart

設定環境變數

多用在不同的virtual host使得每個都擁有不同的環境變數例如:

不同的virtual host要共用同一個環境變數可以在/etc/apache2/apache2.conf設定

SetEnv BRAND test

各別的virtual host變數可在/etc/apache2/sites-available/ 設定:

  <VirtualHost *:80>
    <Directory /var/www>
    ...
    SetEnv BRAND test[n]
    ...
    </Directory>
  </VirtualHost>

php取得apache環境變數:

  echo getenv('BRAND');

如果網址只指到資料夾,不顯示目前資料夾的檔案列表

<VirtualHost *:80>
    Options -Indexes FollowSymLinks
</VirtualHost>

apache2: Could not reliably determine the server’s fully qualified domain name

$ sudo service apache2 restart
[sudo] password for jex:
 * Restarting web server apache2
apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1 for ServerName
... waiting apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1 for ServerName

在/etc/apache2/sites-available/default:

ServerName jex.com
<VirtualHost *:80>
    ServerName jex.com
    (...略...)
</VirtualHost>

ServerName宣告為global

在/etc/apache2/httpd.conf加上:

ServerName jex.com

但apache2.conf要記得Include httpd.conf

不存在的virtual host會自動導向(問題發生在將default不存在)

問題: 原本的default被我刪掉了,而加上兩個virtual host

  • w1.test.com
  • w2.test.com

如此一來 w3.test.com 因為不存在,所以會導向w1.test.com

解決方法:

/etc/apache2/sites-available/default 加回來, 即可解決此問題, 當不存在的domain會導向到default

後記 :

  • ServerName變數不可以在每一個virtual host都宣告成global,不然apache會誤判domain
  • 建議ServerName只在default宣告為global變數

apache log

在/etc/apache2/sites-available/default可以找到apache的log存放位置,例如:

ErrorLog ${APACHE_LOG_DIR}/error.log

預設存放在/var/log/apache2/error.log

讓主機上的帳號都有自己的 public 資料夾 - public_html

load /etc/apache2/mods-enabled :

userdir.load

/etc/apache2/mods-enabled/userdir.conf:

 <IfModule mod_userdir.c>
         UserDir public_html
         UserDir disabled root

         <Directory /home/*/public_html>
                 AllowOverride FileInfo AuthConfig Limit Indexes
                 Options MultiViews Indexes FollowSymLinks IncludesNoExec

Indexes、FollowSymLinks

Options -Indexes +FollowSymLinks
  • -Indexes : 不允許顯示檔案目錄結構
  • +FollowSymLinks : 允許使用 .htaccess 檔案 override 預設的設定

特定副檔名忽略同源政策

<Directory> 裡加上

<FilesMatch "\.(ttf|otf|eot|woff|svg)$">
    <IfModule mod_headers.c>
        Header set Access-Control-Allow-Origin "*"
    </IfModule>
</FilesMatch>