- 安裝 node

# 可以先到 git 確認一下 node 最新版本
curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash -
sudo apt-get install -y nodejs

 

- 安裝 laravel-echo-server

sudo mkdir -p /usr/src/app
sudo chown ubuntu:ubuntu  /usr/src/app -R
sudo npm install -g laravel-echo-server
laravel-echo-server init
laravel-echo-server start

 

- 安裝 Redis

sudo apt update
sudo apt install redis-server

 

- 配置 Redis

sudo vi /etc/redis/redis.conf
# 開啟 supervised,把原本的 no 設定成 systemd(ubuntu)
supervised systemd

# 開放外部連線,修改原本的 bind 127.0.0.1 ::1
bind 0.0.0.0

# 設定 Redis 密碼,修改原本的 requirepass foobared 為指定的 password 字串
requirepass password
# 重新啟動 Redis 服務
sudo service redis restart

 

- 安裝 Supervisor

sudo apt-get install supervisor

 

- 配置 Supervisor

# 新增 log 檔案
mkdir /usr/src/app/logs
touch /usr/src/app/logs/laravel-echo-server.log

 

# 新增設定檔
sudo vi /etc/supervisor/conf.d/laravel-echo-server.conf

 

[program:laravel-echo-server]
process_name=%(program_name)s_%(process_num)02d
directory=/usr/src/app
command=laravel-echo-server start
autostart=true
autorestart=true
user=root
numprocs=1
redirect_stderr=true
stdout_logfile=/usr/src/app/logs/laravel-echo-server.log
stdout_logfile_maxbytes = 20MB
stdout_logfile_backups = 20

 

# 重啟 Supervisor 並觀察狀態
sudo service supervisor restart
sudo service supervisor status

 

- 安裝 nginx

sudo apt-get update
sudo apt-get install nginx

 

- 安裝 certbot

sudo apt-get update
sudo apt-get install software-properties-common
# 載入 certbot 的 ppa
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
# 安裝 python 的 certbot for nginx
sudo apt-get install python-certbot-nginx

 

# 只要產生憑證檔
sudo certbot certonly --nginx -d www.test.com

 

- 配置 laravel-echo-server

laravel-echo-server init
vi /usr/src/app/laravel-echo-server.json

 

{
        "authHost": "https://www.test.com", // 修改成專案的 domain
        "authEndpoint": "/broadcasting/auth",
        "clients": [
                {
                        "appId": "myAddId", // init 時自動產生
                        "key": "myKey" // init 時自動產生
                }
        ],
        "database": "redis",
        "databaseConfig": {
                "redis": {
                        "port": "6379",
                        "password": "password", // 修改成 redis 的 password
                        "host": "localhost",
                        "db": 9 // 指定 redis 使用的 db index
                },
                "publishPresence": true,
                "sqlite": {
                        "databasePath": "/database/laravel-echo-server.sqlite"
                }
        },
        "devMode": true,
        "host": null,
        "port": "6001",
        "protocol": "https",
        "socketio": {},
        "secureOptions": 67108864,
        "sslCertPath": "/etc/letsencrypt/live/www.test.com/fullchain.pem", // 設定剛剛 certbot 產生的檔案路徑
        "sslKeyPath": "/etc/letsencrypt/live/www.test.com/privkey.pem", // 設定剛剛 certbot 產生的檔案路徑
        "sslCertChainPath": "",
        "sslPassphrase": "",
        "subscribers": {
                "http": true,
                "redis": true
        },
        "apiOriginAllow": {
                "allowCors": false,
                "allowOrigin": "",
                "allowMethods": "",
                "allowHeaders": ""
        }
}

 

# 重啟 laravel-echo-server 並觀察狀態
sudo laravel-echo-server stop
sudo laravel-echo-server start

 

- 設定 certbot 自動更新

sudo crontab -l

 

# certbot renew at 00:00 on day-of-month 20 in every 2nd month
0 0 20 */2 * /usr/bin/certbot renew --quiet --no-self-upgrade

 

# 確認目前憑證期限
sudo certbot certificates

 

- certbot 更新憑證後需要重啟服務

# 新增 restart_services.sh 在更新後執行
sudo vi /etc/letsencrypt/renewal-hooks/post/restart_services.sh

 

#!/bin/sh
service nginx restart
cd /usr/src/app
laravel-echo-server restart

 

touch ~/last_run_certbot_renew_date_time.log

 

# 透過測試更新驗證 restart_services.sh 是不是真的被正確執行
sudo /usr/bin/certbot renew --dry-run

 

Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/www.test.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator nginx, Installer nginx
Renewing an existing certificate

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed with reload of nginx server; fullchain is
/etc/letsencrypt/live/www.test.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/www.test.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: /etc/letsencrypt/renewal-hooks/post/restart_services.sh

 

- 設定 nginx 反向代理

sudo vi /etc/nginx/sites-enabled/default

 

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    
    root /var/www/html;
    
    index index.html index.htm index.nginx-debian.html
    
    server_name _;
    
    location /ws/ {
        # 反向代理到同一台主機的 6001 Port
        proxy_pass http://localhost:6001/;
        
        # 解決 wss 400 的問題
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;

        # 把 IP、Protocol 等 header 都一起送給反向代理的 server
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
    }
}

 

sudo service nginx reload
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

以往安裝 MySQL 在本機時,通常都會順便安裝 MySQL client 端程式,透過 mysqldump 建立備份很簡單。但如果是使用 docker 建立 MySQL,就無法直接在容器外備份,需要改用透過 docker 對內部下指令的方式處理。

# backup test_db 到 test_db_backup.sql
docker exec [CONTAINER] //usr/bin/mysqldump -u [USER] --password=[PASSWORD] --routines --triggers test_db > /home/user/test_db_backup.sql

請留意路徑開始的「//」一定要用雙斜線,[USER] 與 [PASSWORD] 請自行代入資料庫的使用者名稱與密碼,[CONTAINER] 請自行代入 container name

另外除了備份 sql 檔案外,我還想要自動將備份的資料匯入到新資料庫中,這時候就要另外處理建立新資料庫,以及匯入的手續;這邊我把它寫成一個 shell script,並把資料庫改成變數傳入的方式,方便直接使用

#!/bin/sh

# backup db 資料到 db_backup.sql
docker exec [CONTAINER] //usr/bin/mysqldump -u [USER] --password=[PASSWORD] --routines --triggers $1 > /home/user/$1_backup.sql

# init 刪除原 db_backup 資料庫,重新建立一個空的 db_backup 資料庫
echo "DROP DATABASE IF EXISTS $1_backup; CREATE DATABASE $1_backup CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" > /home/user/$1_init.sql | cat /home/user/$1_init.sql | docker exec -i [CONTAINER] //usr/bin/mysql -u [USER] --password=[PASSWORD]

# restore 匯入備份的資料到 db_backup 資料庫
cat /home/user/$1_backup.sql | docker exec -i [CONTAINER] //usr/bin/mysql -u [USER] --password=[PASSWORD] $1_backup

另存成 backup_db.sh,修改可執行權限後,只要執行

./backup_db.sh test_db

就會自動備份 test_db 到 test_db_backup 中囉

danielhuang030 發表在 痞客邦 留言(0) 人氣()

Info

  • 階層關係:Business ID > Ad Accounts > Campaigns > Ad Sets > Ads
  • 基本上所有操作從 Ad Account 開始,相當於 Google Ads 的 Profile
  • 只要有 id 透過 new 的方式就可以取得對象物件
// 取得 Ad Account
$adAccountId = '193443xxxxxx07';
$adAccount = new \FacebookAds\Object\AdAccount(sprintf('act_%s', $adAccountId ));
  • Insights 相當於 Google Ads 的 Report,可以取得指定時間區間的數據
  • 透過 fields 指定顯示的欄位,透過 params 設定過濾條件
// 取得 Ad Account Insights
$fields = [
    'account_name',
];
$params = [
    'time_range' => (object)[
        'since' => '2019-02-20',
        'until' => '2019-02-22',
    ],
];
$insightsCursor = $adAccount->getInsights($fields, $params);
$results = [];
foreach ($insightsCursor as $insight) {
    $results[] = $insight->getData();
}
dd($results);
  • fields 可以使用複合 Insights 的方式取得統計後的數據,不過如果要使用時間範圍條件時,需要特別寫在 fields 而不是 params這一點文件完全沒提,依照邏輯應該是寫在 params 所以我試了很多種寫法;後來網路也找了很久才發現解法,真~的很OX
$results = [];
$campaignFields = [
    'id',
    'name',
    'status',
    sprintf('insights.time_range(%s){%s}', json_encode([
        'since' => '2019-02-20',
        'until' => '2019-02-21',
    ]), implode(',', [
        'cpc',
        'spend',
    ])),
];
$campaignCursor = $adAccount->getCampaigns($campaignFields);
foreach ($campaignCursor as $campaign) {
    $results[] = $campaign->getData();
}
dd($results);

Get Results

danielhuang030 發表在 痞客邦 留言(0) 人氣()

Laravel Medialibrary

Info

Installation

composer require spatie/laravel-medialibrary:^7.0.0
  • db migration,會建立 media table
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="migrations"
php artisan migrate
  • 產生設定檔
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="config"

Setting

  • 預設 disk_name 是 public,這個設定對應 config/filesystems.php;建議另外建立一個設定檔,範例是用 media

    'disks' => [
        // ...
 
        'media' => [
            'driver' => 'local',
            'root' => public_path('media'),
            'url' => env('APP_URL') . '/media',
            'visibility' => 'public',
        ],
 
        // ...
    ],
return [
 
    /*
     * The disk on which to store added files and derived images by default. Choose
     * one or more of the disks you've configured in config/filesystems.php.
     */
    'disk_name' => 'media',
 
    // ...
];

Usage

  • 在需要媒體檔案功能的 eloquent 中實作並使用 Trait
namespace App;
 
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia\HasMedia;
use Spatie\MediaLibrary\HasMedia\HasMediaTrait;
 
class Test extends Model implements HasMedia
{
    use HasMediaTrait;
 
    // ...
}
  • 設定媒體檔案,會自動把檔案存到之前設定的路徑中,並儲存檔案相關訊息至 table media
// 取得物件
$item = Test::find(1);
// 設定媒體檔案,可以使用絕對路徑,網址連結或是表單上傳的檔案物件
$item->addMedia($pathToFile)
   // 指定分類 test,或不填使用預設
   ->toMediaCollection('test');
  • 取得媒體檔案
$item = Test::find(1);
$media = $item->getMedia();

Converting

  • 這個套件包含同作者製作的另一個影像處理套件 spatie/image,詳細文件可以參考這裡;所以可以很容易在處理媒體檔案的同時,做一些簡單的處理(調整大小、模糊化、馬賽克、灰階等)
  • 透過 override eloquent registerMediaConversions() 實現
class Test extends Model implements HasMedia
{
    // ...
 
    /**
     * register media conversions
     *
     * @param \Spatie\MediaLibrary\Models\Media $media
     */
    public function registerMediaConversions(\Spatie\MediaLibrary\Models\Media $media = null)
    {
        // 建立 200x100 的縮圖
        $this->addMediaConversion('thumb')
              ->width(200)
              ->height(120)
              // 只處理 test collection
              ->performOnCollections('test')
              // 預設會使用 queue 處理,可以利用這個函式直接處理不需透過 job
              ->nonQueued();
 
        // 建立灰階圖片
        $this->addMediaConversion('greyscale')
              ->greyscale()
              ->nonQueued();
 
    }
 
    // ...
  • 之後如果有新增轉換格式,也可以透過 cmd 讓之前的檔案重新產生,也可以利用參數指定條件
php artisan medialibrary:regenerate
  • 還有一個比較特別的地方,可以擷取影片的內容縮圖;但是必須依賴 PHP-FFMpeg/PHP-FFMpeg 套件,系統也必須安裝 FFMpeg;實測可以成功
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

PageSpeed Insights

PageSpeed Modules

  • 提供 Apache 與 Nginx 二種目前最受歡迎的 HTTP Server,另外下載時還要注意機器是 32-bit/64-bit

Installation

  • ubuntu 可以透過 deb 快速安裝,之後重啟 HTTP Server 預設就會啟用;以下以 Apache Debian/Ubuntu 64-bit 為範例
# 下載 deb
wget https://dl-ssl.google.com/dl/linux/direct/mod-pagespeed-beta_current_amd64.deb

# 安裝
sudo dpkg -i mod-pagespeed-stable*.deb

# 重啟 apache2
sudo service apache2 restart

其他可以提高分數的方法

清除前幾行內容中的禁止轉譯 JavaScript 和 CSS

  • 移動 JavaScript 的位置從 head 到 body
  • 要注意一些 lib(e.g. jQuery) 還是要在有引用到的 JavaScript 之前

使用瀏覽器快取功能

sudo a2enmod expires
sudo service apache2 restart
  • 修改網站的 .htaccess,加入 mod_expires 設定

    ExpiresActive on
    ExpiresDefault A86400
    ExpiresByType image/x-icon A7776000
    ExpiresByType application/x-javascript A604800
    ExpiresByType application/javascript A604800
    ExpiresByType text/css A604800
    ExpiresByType image/gif A2592000
    ExpiresByType image/png A2592000
    ExpiresByType image/jpeg A2592000
    ExpiresByType text/plain A86400
    ExpiresByType application/x-shockwave-flash A2592000
    ExpiresByType video/x-flv A2592000
    ExpiresByType application/pdf A2592000
    ExpiresByType text/html A3600
    ExpiresByType application/x-font-woffl A7776000
    ExpiresByType application/vnd.ms-fontobject A7776000
    ExpiresByType image/svg+xml A7776000
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

ag-Grid

Info

  • 可以實現許多關於表格的多種功能,包括排序、搜尋,甚至是匯出 CSV 檔案
  • 使用目前流行的 MVC 的概念,表格顯示使用程式參數設定,顯示時會自動轉成 HTML 格式;有多種主題可以選擇,但如果要客製化顯示可能需要花比較多時間釐清設定方式。內容則是透過 JSON 格式注入
  • 支援多種主流 JavaScript Framework,當然純 JS 也完全沒有問題
  • Enterprise 版本而且所費不貲,但似乎也可以直接使用只會在 console 有註解顯示而已?

Usage

  • 基本使用可以參考官方文件說明
  • 如果需要 AJAX 取得資料,fetch 需要新增設定 credentials 參數才能取得 session
// fetch data
fetch("https://www.example.com", {
  // get same session
  credentials: "same-origin"
}).then(function(response) {
  return response.json();
}).then(function(json) {
  gridOptions.api.setRowData(json.data);
});
// 欄位設定新增 headerCheckboxSelectionFilteredOnly 屬性
var columnDefs = [
  {
    headerName: "Name",
    field: "name",
    checkboxSelection: true,
    headerCheckboxSelection: true,
    headerCheckboxSelectionFilteredOnly: true
  },
  // ...
];
// 設定 gridOptions 屬性
var gridOptions = {
  columnDefs: columnDefs,
 
  // ...
 
  // 開啟 deltaRowDataMode
  deltaRowDataMode: true,
 
  // 設定資料的唯一值,此例是 id
  getRowNodeId: function(data) {
    return data.id;
  }
};
  • 顯示 loading 特效
gridOptions.api.showLoadingOverlay();
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

Google AdWords API

Get Started

Service

BiddingStrategyService

CampaignService

[
    'ConversionOptimizerEligibility',
    'FrequencyCap',
    'NetworkSetting',
    'BiddingStrategyConfiguration',
    'ForwardCompatibilityMap',
    'VanityPharma',
    'Budget',
 
    // 使用原資料修改後更新時這個欄位會有問題,暫時以忽略這個欄位的方式避開錯誤
    'AdServingOptimizationStatus',
];
[
    'BudgetId',
    'BudgetName',
    'Amount',
    'DeliveryMethod',
    'BudgetReferenceCount',
    'IsBudgetExplicitlyShared',
    'BudgetStatus',
];
[
    'BiddingStrategyId',
    'BiddingStrategyName',
    'BiddingStrategyType',
];
[
    'TargetCpa',
    'TargetCpaMaxCpcBidCeiling',
];

BudgetService

// 忽略的欄位名稱
[
    'Id',
    'Name',
    'ReferenceCount',
    'IsExplicitlyShared',
    'Status',
];
 
// 正確的欄位名稱
[
    'BudgetId',
    'BudgetName',
    'BudgetReferenceCount',
    'IsBudgetExplicitlyShared',
    'BudgetStatus',
];

AdGroupCriterionService

ManagedCustomerRepository

ConversionTrackerService

Report

Choosing the right report

取得所有可用的 report definition

$reportDefinitionService = $this->adWordsServices->get($this->adWordsSession, ReportDefinitionService::class);
$reportDefinitionFields = $reportDefinitionService->getReportFields($this->table);

Campaign Performance Report

[
    'CampaignId',
    'ConversionTrackerId',
    'ConversionTypeName',
    'AllConversions',
];

Adgroup Performance Report

Codes and Formats

Common Errors

RATE_EXCEEDED

TOO_MANY_PREDICATE_VALUES

文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

WINDOWS

  • Ctrl+, 開啟使用者設定
  • Ctrl+P 開啟快速輸入命令列
  • Ctrl+K, Ctrl+S 開啟鍵盤快速鍵設定
    • editor.action.transformToUppercase 全部轉大寫設定
    • editor.action.transformToLowercase 全部轉小寫設定

config

{
    // 刪除檔案時需要確認
    "explorer.confirmDelete": true,

    // PHP 在輸入時檢查
    "php.validate.run": "onType",

    // PHP 的可執行檔位置,這邊是 Windows 的設定範例
    "php.validate.executablePath": "D:\\php\\php.exe",

    // 關閉內建的自動完成(以 PHP Intelephense 的自動完成為準)
    "php.suggest.basic": false,

    // 儲存的同時自動刪除程式碼後無意義的空白
    "files.trimTrailingWhitespace": true,

    // 連點滑鼠右鍵時,包含 PHP 的變數前置 $ 符號(原正規式去除 "$")
    "editor.wordSeparators": "`~!@#%^&*()-=+[{]}\\|;:'\",.<>/?",

    // 開啟自動換行功能
    "editor.wordWrap": "on",

    // 設定字型大小
    "editor.fontSize": 16,

    // 關閉右側 minimap
    "editor.minimap.enabled": false,

    // 關閉預覽模式,檔案會以新分頁開啟(每一支檔案獨立分頁)
    "workbench.editor.enablePreview": false,

    // 設定檔案結尾方式
    "files.eol": "\n",

    // 關閉 PHP Intelephense 的 format(以 phpfmt 為準)
    "intelephense.format.enable": false,

    // phpfmt php 執行檔位置
    // "phpfmt.php_bin": "D:\\php\\php.exe",

    // 開啟變數等號與陣列箭頭的自動對齊
    // "phpfmt.enable_auto_align": true,

    // 設定 PHP 使用的預設 Formatter phpfmt,開啟儲存前先 format
    "[php]": {
        // "editor.defaultFormatter": "kokororin.vscode-phpfmt",
        "editor.defaultFormatter": "junstyle.php-cs-fixer",
        "editor.formatOnSave": true,
    },

     // php-cs-fixer
    "php-cs-fixer.executablePath": "php-cs-fixer",
    "php-cs-fixer.executablePathWindows": "",   //eg: php-cs-fixer.bat
    "php-cs-fixer.onsave": true,
    "php-cs-fixer.rules": "@PSR2",
    "php-cs-fixer.config": ".php_cs;.php_cs.dist",
    "php-cs-fixer.allowRisky": false,
    "php-cs-fixer.pathMode": "override",
    "php-cs-fixer.exclude": [],
    "php-cs-fixer.autoFixByBracket": true,
    "php-cs-fixer.autoFixBySemicolon": false,
    "php-cs-fixer.formatHtml": false,
    "php-cs-fixer.documentFormattingProvider": true

    // getter setter 註解間的空格數量
    "phpGettersSetters.spacesAfterParam": 1,
    "phpGettersSetters.spacesAfterParamVar": 1,
    "phpGettersSetters.spacesAfterReturn": 1,
}

plugins

有人在 git 上回報,似乎蠻多人都有遇到Go to definition no definition found 的問題

網友建議用另一套 PHP Intelephense

文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

DEMO PAGE

算算從家裡通勤到台北工作已經 10 年,台鐵已經成為我上下班時不可或缺的交通工具;所以對我來說時刻與票價計算至關重要。去年發現政府資料開放平臺以及公共運輸整合資訊流通服務平臺提供的資料相當完整,只要結合行事曆即可產生簡單的票價計算可供比較;時刻方面雖然有提供 API 但準度不高,即便如此還是可以當作一個簡單的參考。

程式碼都是由 JavaScript 撰寫,資料來源就是之前提到的開放資料,希望能給在外地工作通勤的上班族一些幫助~
P.S 目前 ptx 在 Android https 的憑證驗證有問題,所以在手機上無法取得資料...(囧)必須等 Mozilla 與 Google 把政府的憑證加入安全名單, 或是自己手動安裝憑證,好麻煩啊!
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

jQuery DataTables

Info

  • 簡單實現表單排序、分頁、搜尋功能
  • 有很多實用的官方 Extensions
  • 還有不少第三方製作的 Plugins

Formatted numbers

Sum

FixedHeader/FixedColumns

dom

Other

自訂排序

排序順序

{ "orderSequence": ["asc", "desc"] }
{ "orderSequence": ["desc", "asc"], "targets": "_all" }
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

Close

您尚未登入,將以訪客身份留言。亦可以上方服務帳號登入留言

請輸入暱稱 ( 最多顯示 6 個中文字元 )

請輸入標題 ( 最多顯示 9 個中文字元 )

請輸入內容 ( 最多 140 個中文字元 )

reload

請輸入左方認證碼:

看不懂,換張圖

請輸入驗證碼