<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>網頁後端 &#8211; 記下來</title>
	<atom:link href="https://noter.tw/code/back-end/feed/" rel="self" type="application/rss+xml" />
	<link>https://noter.tw</link>
	<description>一路上踩到的坑、遇到的問題，一點一滴記下來，希望能幫助到需要的人~</description>
	<lastBuildDate>Thu, 25 Apr 2024 08:54:06 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6.3</generator>

<image>
	<url>https://noter.tw/wp-content/uploads/cropped-old-1130742_1920-1-32x32.jpg</url>
	<title>網頁後端 &#8211; 記下來</title>
	<link>https://noter.tw</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>PHPMailer + Gmail OAuth2 寄信</title>
		<link>https://noter.tw/12780/phpmailer-gmail-oauth2-%e5%af%84%e4%bf%a1/</link>
					<comments>https://noter.tw/12780/phpmailer-gmail-oauth2-%e5%af%84%e4%bf%a1/#respond</comments>
		
		<dc:creator><![CDATA[黃小蛙]]></dc:creator>
		<pubDate>Thu, 25 Apr 2024 08:08:36 +0000</pubDate>
				<category><![CDATA[程式開發]]></category>
		<category><![CDATA[網頁後端]]></category>
		<category><![CDATA[技術相關]]></category>
		<category><![CDATA[XOAUTH2]]></category>
		<category><![CDATA[OAuth]]></category>
		<category><![CDATA[建立 OAuth 用戶端 ID 憑證]]></category>
		<category><![CDATA[OAuth token]]></category>
		<category><![CDATA[Google Workspace]]></category>
		<category><![CDATA[PHP Gmail 寄信]]></category>
		<category><![CDATA[PHP 發送郵件]]></category>
		<category><![CDATA[get_oauth_token.php]]></category>
		<category><![CDATA[已授權的重新導向 URI]]></category>
		<category><![CDATA[oauth2 refresh token]]></category>
		<category><![CDATA[PHPMailer]]></category>
		<category><![CDATA[OAuth 用戶端 ID]]></category>
		<category><![CDATA[Gmail API]]></category>
		<category><![CDATA[Google Cloud 憑證]]></category>
		<category><![CDATA[PHPMailer Gmail OAuth2]]></category>
		<category><![CDATA[OAuth2 憑證]]></category>
		<guid isPermaLink="false">https://noter.tw/?p=12780</guid>

					<description><![CDATA[<p>小蛙有個維護的系統使用 PHPMailer 透過 Gmail 帳號密碼發送郵件，近期收到 Google 寄來的通知「自 2024 年 9 月 30 日起，應用程式必須採用 OAuth 才能存取 Google Workspace 帳戶。系統將不再支援使用密碼 (應用程式密碼除外) 的存取行為。」為了避免哪一天突然通通都只支援 OAuth，趁現在有一點時間趕快來改一下。</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/12780/phpmailer-gmail-oauth2-%e5%af%84%e4%bf%a1/" data-wpel-link="internal">PHPMailer + Gmail OAuth2 寄信</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>小蛙有個維護的系統使用 PHPMailer 透過 Gmail 帳號密碼發送郵件，近期收到 Google 寄來的通知「自 2024 年 9 月 30 日起，應用程式必須採用 OAuth 才能存取 Google Workspace 帳戶。系統將不再支援使用密碼 (應用程式密碼除外) 的存取行為。」為了避免哪一天突然通通都只支援 OAuth，趁現在有一點時間趕快來改一下。</p>



<span id="more-12780"></span>



<p>PHPMailer + Gmail OAuth 發信大概有幾個步驟：</p>



<ol class="my-li bg-darkblue wp-block-list">
<li><strong>安裝必要套件</strong>：使用 composer 在網站上安裝 phpmailer/phpmailer 及 league/oauth2-google 套件。</li>



<li><strong>建立 OAuth 2.0 用戶端 ID 憑證</strong>：到 Goolge Cloud 建立一個專案，並開啟 Gmail API 與建立一個 OAuth 2.0 用戶端 ID 憑證。</li>



<li><strong>取得 Refresh Token</strong>：設定 <code>get_oauth_token.php</code> 通過 OAuth 驗證，取得 Refresh Token。</li>



<li><strong>使用 PHPMailer 寄信</strong>：使用 PHPMailer 搭配前幾步得到的資訊寄送郵件。</li>
</ol>



<h2 class="wp-block-heading para">安裝必要套件</h2>



<p>需使用 composer 安裝必要套件，沒有 composer 的話可參考<a href="https://getcomposer.org/doc/00-intro.md" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">這邊<span class="wpel-icon wpel-image wpel-icon-6"></span></a>。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">composer require phpmailer/phpmailer
composer require league/oauth2-google</pre>



<h2 class="wp-block-heading para">建立 OAuth 2.0 用戶端 ID 憑證</h2>



<h3 class="wp-block-heading para">啟用 Gmail API</h3>



<p>進入到 <a href="https://console.cloud.google.com/apis/dashboard" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">Google Cloud<span class="wpel-icon wpel-image wpel-icon-6"></span></a>，點擊左上方專案下拉選單 -> 新增專案 -> 輸入專案名稱，只要自己能辨識的即可，下面若有機構、位置，也都必須填寫上去</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczP4fogGZPjU75mdxaeI0tKW2eGRS6FBUuaLQDUBqfgiO7knRvicztaTrMIUvqtpjyHL1b3_UA2wW586_u2pjqPXf2i5JVbEQwBDqsgfF_49ZwFj4gnTYZxNYJ6CP86gNEcwB1UuMCIhRLv6_xQfPFlq=w666-h715-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 1" style="width:500px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>專案建立完成後進入該專案，左側選擇「已啟用的 API 和服務」，點擊「啟用 API 和服務」</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczMKjfxWGckFT4SDqI9q5OGQi6NYeo_gpu-MQduZVVo3A5ArRza7b9UuIB4DF8LMkVhGYIeyC8oGBTCOQcq0vh4m4SQIzI84ARFBNMbgCIoKTGo_FG8adK4gWlOjolBpIpv3Z4v0UDeI7X8-vnvo0S64=w714-h390-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 2" style="width:500px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>找到並啟用 Gmail API</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczN60kf2CLZeKdaaKKq_T_dfsTQ51TsKgzgjJoK5oPxoAi69KAAQuqX3GWE5u5nNGBe7zP7V3EKceOYUe2cHXtLpFjLAbQgOm9lJQ1FVRBInF3Up-pmebuYjoRS37QywSXmEJk1rrieIxFPiD-oemF-X=w594-h401-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 3" style="width:400px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<h3 class="wp-block-heading para">建立 OAuth 用戶端 ID 憑證</h3>



<p>成功啟用 Gmail API 後，左側進入「憑證」選單 -> 點擊「建立憑證」 -> OAuth 用戶端 ID</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczPJKZpCSzpeLLRDHvkcwdJD2f82V1YY0UX5OZE9ykHd8uya60uNODJxm49OV5r5Qmv3F1JLR1DFHrUrGDD2fMKWaHXiF2miu0JIAHax-roCaD5dAJI4A16PN-RdZG0xIlmNhSD3LIpm8fLS3FNqSA9x=w935-h456-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 4" style="width:600px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>第一次建立需要先設定同意畫面，點擊「設定同意畫面」</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczOGpbByPCfX_QYNdc3Tty8-pY4dMxWJQdOxwQobKf0GaWAvTLjax_su14ODzwzsrdZ2RsU8t8OwBr2b4TWiI8OQ7ZStwA_Av-K3l8dkgTFGtlVaWcV1sravgQpNQQcVt2MZwamni3tLnDlWAd54SrMe=w690-h276-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 5" style="width:500px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>我們的目的是完成至少一次 OAuth 驗證取得 Refresh Token，這邊選「內部」就可以了</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczPGy3-iYpG5LY2fG9QKVqTJAah6GW-_2JCtjqsHeeYV9y2dHxGjF_aW7IR-lFGfI3cnEJyLXg8h4p6x_AvY5CRhrizpFWg9Dc2aCUp-MDQ9_S6rgUDoKVxDP8hCWRUDNu29lPxlPo87aMlhY8pTL1gj=w679-h581-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 6" style="width:400px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>應用程式名稱、使用者支援電子郵件及開發人員聯絡資訊為必填項目，其餘部份視情況填寫，填寫完成後點擊「儲存並繼續」。</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-1 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow"><div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczN61095TO7WUG8Jydw_-ZEoOhID5Y4tzbiDnmw_EIlI3ueo2-T-LMeWGh0oEkOosg26OKJulTg1vTsPF-uCZD-5oXDv_iPtwv4-vlFaaULC10U_zRvGVVxjAtYVKQYxdm-QAunv4opzFP9zcAP3zcHN=w679-h798-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 7" style="object-fit:cover" title="PHPMailer + Gmail OAuth2 寄信"></figure></div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow"><div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczNQiuqmUzaJJl-XySD31aSUGYPYyVDAVZltwi4U-9m2cpKP-4gKABuJzPwt1Dg2HfeIHFasME78Z8_TRyyXzwcq_gwbpZpvd7UNRph2x2ekx6o4zuoMD1sGyutyLuIpulDbyxdU7OqLBtaW_Cl8opWg=w677-h862-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 8" title="PHPMailer + Gmail OAuth2 寄信"></figure></div></div>
</div>



<p>第二步範圍部份直接留空，點擊「儲存並繼續」</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-2 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow"><div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczMBKau2JTNrXXjqDFkP6vISLfQa_hyHJQZRr_w9v9na-CXiiEKI5e8TO7t0C3wKt5v-8aYVfi4YgRvXzNvvJ4QkjXPVHZBtmBlt4H3FkXCyXPGKRSNUNObiptfhmJ1UEhYPT5loGB9gTJ6wZKmZvJNA=w676-h552-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 9" title="PHPMailer + Gmail OAuth2 寄信"></figure></div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow"><div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczPL1myaeyJMZsXqKb_MX2HErqMs1VuvwGiBzmkDAyYLb4BoaNkDvV1DS72TAcrkCDLkkAsGJSavFnwjg_t-cq28V3ezmndNqK-Z2OqmDslV5_YMKhD8Kq8jk3Om8akIA3lSIImxgLQfyE3e28hJ5JIh=w676-h537-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 10" title="PHPMailer + Gmail OAuth2 寄信"></figure></div></div>
</div>



<p>OAuth 同意畫面建立完成</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-3 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow"><div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczPNN115kjpIKr-OPaknHyw3-TG0PVuuC9RCkdtZP5YgXMIQ8tq4-IpdYERZJ4wm0X8CFzz4nmvnSuPMhuXXuvLHu-qoQbJ7VAuR-Nu28p6jfmQmgkYYzJm6PDPPONbVzFuUCR6vR745pdeEq9fxllq2=w675-h617-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 11" title="PHPMailer + Gmail OAuth2 寄信"></figure></div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow"><div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczP6GUlp18EkN6JAvl90LMqTV67IcvEvLRLdIZN5eL-a97ABLE9FficqfhZqCGq8hrSjQBGRmyDuFp2FP7OSTC4iwXY5Eq_SPh39XTBoGN0NH2paLIG5b8UsF4cawd3WMvmaiEUSKJvyAX3eAolyW6C7=w676-h569-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 12" style="width:416px;height:auto" title="PHPMailer + Gmail OAuth2 寄信"></figure></div></div>
</div>



<p>再回到「憑證」->「建立憑證」->「OAuth 用戶端 ID」</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczPJKZpCSzpeLLRDHvkcwdJD2f82V1YY0UX5OZE9ykHd8uya60uNODJxm49OV5r5Qmv3F1JLR1DFHrUrGDD2fMKWaHXiF2miu0JIAHax-roCaD5dAJI4A16PN-RdZG0xIlmNhSD3LIpm8fLS3FNqSA9x=w935-h456-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 13" style="width:600px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>應用程式類型選擇「網頁應用程式」，名稱為必填，可以跟小蛙一樣輸入 PHPMailer，只要能辨識即可。「已授權的重新導向 URI」填入網站下 phpmailer 套件下的 <code>get_oauth_token.php</code> 路徑 (也可以把它拉出來到外層比較好處理的地方)，小蛙這邊的例子是 </p>



<pre class="wp-block-preformatted">https://xx.xxx/vendor/phpmailer/phpmailer/get_oauth_token.php</pre>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczOvK99odz0V_ok4Ie8wEihuHHv3Xd3nohjEQ1SmNSN1I50Ee6aQvGsCmfK7gHRbfmJ5ZcSVeeErcTpRocoGuWztkqtHUyZt1oilrTZdcgLYnkeM8e4ZMt7Am8nv05r2prtZfGseGj2stkKXgvBz7dgz=w681-h993-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 14" style="width:503px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>建立完成後會得到<strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">用戶端編號</mark></strong>及<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color"><strong>用戶端密鑰</strong></mark>，先存起來等一下馬上會用到。</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczPcJ7LUPlQ7amLYg7ihWiFq4zwGti6hcZPBNouXbgkQ_tRXUQmQ9CrD9mPV_-LsRN2wybLy7tyxGnXAI0ndlSzMC1TGMR3QuT8TNQYwXeBn6HTxwvEE_J_U_vmVtEymVD4jh9EnFf4UYMtweBlLZ2K1=w570-h480-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 15" style="width:400px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<h2 class="wp-block-heading para">取得 Refresh Token</h2>



<p>找到 PHPMailer 下的 <code>get_oauth_token.php</code> (預設應該會在網頁目錄下的 <code>vender/phpmailer/phpmailer/</code>)，PHPMailer 原始附帶的檔案會一直卡在 Provider missing 或 Invalid state，沒辦法正確取得 Refresh Token，因此小蛙對 <code>get_oauth_token.php</code> 做了一些修改，確保可以拿到 token。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;?php
/**
 * PHPMailer - PHP email creation and transport class.
 * PHP Version 5.5
 * @package PHPMailer
 * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
 * @author Marcus Bointon (Synchro/coolbru) &lt;phpmailer@synchromedia.co.uk>
 * @author Jim Jagielski (jimjag) &lt;jimjag@gmail.com>
 * @author Andy Prevost (codeworxtech) &lt;codeworxtech@users.sourceforge.net>
 * @author Brent R. Matzelle (original founder)
 * @copyright 2012 - 2020 Marcus Bointon
 * @copyright 2010 - 2012 Jim Jagielski
 * @copyright 2004 - 2009 Andy Prevost
 * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
 * @note This program is distributed in the hope that it will be useful - WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * Get an OAuth2 token from an OAuth2 provider.
 * * Install this script on your server so that it's accessible
 * as [https/http]://&lt;yourdomain>/&lt;folder>/get_oauth_token.php
 * e.g.: http://localhost/phpmailer/get_oauth_token.php
 * * Ensure dependencies are installed with 'composer install'
 * * Set up an app in your Google/Yahoo/Microsoft account
 * * Set the script address as the app's redirect URL
 * If no refresh token is obtained when running this file,
 * revoke access to your app and run the script again.
 */

namespace PHPMailer\PHPMailer;

// 用來看錯誤訊息以及刪除 opcache
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
@opcache_reset();
	
/**
 * Aliases for League Provider Classes
 * Make sure you have added these to your composer.json and run `composer install`
 * Plenty to choose from here:
 * @see https://oauth2-client.thephpleague.com/providers/thirdparty/
 */
//@see https://github.com/thephpleague/oauth2-google
use League\OAuth2\Client\Provider\Google;

// 根據自己的 vendor 路徑進行調整
require '../../autoload.php';

session_start();

// 改成剛剛在 Google Cloud 上設定的值
$providerName = 'Google';
// 用戶端編號
$clientId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com';
// 用戶端密碼
$clientSecret = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
// 已授權的重新導向 URI
$redirectUri = 'https://xxx.xxxx.xxx/get_oauth_token.php';

$_SESSION['provider'] = $providerName;
$_SESSION['clientId'] = $clientId;
$_SESSION['clientSecret'] = $clientSecret;

$params = [
    'clientId' => $clientId,
    'clientSecret' => $clientSecret,
    'redirectUri' => $redirectUri,
    'accessType' => 'offline'
];

$options = [];
$provider = null;

switch ($providerName) {
    case 'Google':
        $provider = new Google($params);
        $options = [
            'scope' => [
                'https://mail.google.com/'
            ]
        ];
        break;
}

if (null === $provider) {
    exit('Provider missing');
}

if (!isset($_GET['code'])) {
    //If we don't have an authorization code then get one
    $authUrl = $provider->getAuthorizationUrl($options);
    $_SESSION['oauth2state'] = $provider->getState();
    header('Location: ' . $authUrl);
    exit;
    //Check given state against previously stored one to mitigate CSRF attack
	// 這段不註解掉的話，會一直卡在 Invalid state，沒辦法拿到正確的 token
/*} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
	error_log("3333");
	error_log("state 3333 " . $_SESSION['oauth2state']);
    //unset($_SESSION['oauth2state']);
    //unset($_SESSION['provider']);
    exit('Invalid state');*/
} else {
    //unset($_SESSION['provider']);
    //Try to get an access token (using the authorization code grant)
    $token = $provider->getAccessToken(
        'authorization_code',
        [
            'code' => $_GET['code']
        ]
    );
    //Use this to interact with an API on the users behalf
    //Use this to get a new access token if the old one expires
    echo 'Refresh Token: ', htmlspecialchars($token->getRefreshToken());
}</pre>



<p>將內容貼上後，開啟瀏覽器連到該網址 (就是<strong>已授權的重新導向 URI)</strong> 開始進入 OAuth 登入流程，選擇要登入的帳號</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczNd2HKRgMmOhBQaMmcC9pgUefAAsic1hkMqQzNHntO-1WCADxGh2gI74MXUtCztrElVXrZnqGeFwdlhg_Wu_VqUPq8esg9tm6kbcFcFJ6ixXkpRNfY7_t6sFZkMkzJBqqt9NtKBjoHpawuXktXa34LZ=w1058-h401-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 16" style="width:700px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>確認登入</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczNyIy5svb1IInWm1pj7vJurWmbOEW6vM4LNL0JxtFFqilMaB_mu_PT3WV-MMYfXhKIDG99pTDOlE9fOKxFH8Z4MUhbM-E-bWcSaOt5cJ3hoNDTeWIut3PJ3g-TgfLLJZNkOiyQjGLX6-Yr4VXKBW7sh=w1063-h490-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 17" style="width:700px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>信任應用程式</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczOW1-cRB-uxqhR9v7znPOoAw006Lx3tuBqFpba4WSijsidXKh0hKuWr1kvTz7KYfo0n38zK11rLkHPdvRNdOIqcpKiQcYfUz3zLxmJomAcC5j_gg2zUM8-yvCaGCw4EWqImteEhvYos6aLjoRAoojr2=w1057-h699-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 18" style="width:700px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>最後會印出 Refresh Token: xxxxxxxxxx，這個 token 一定要存好，少了這個 PHPMailer 就沒辦法透過 OAuth 驗證寄信喔，原本以為 <code>get_oauth_token.php</code> 每次執行應該 &#8220;有機會&#8221; 去換一個可使用的 Token 回來，不過再執行一次會發現 Refresh Token: 變成空白。</p>


<div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczMAEzc9EkM0XaVMs8px7YKL3EXAUJ2L_uJ8YlSTBBnEJAP8BH4JqC2s_b5_Rm1LNffiyRiTBmehd0jhvLbPhpAslSkq-8Lx1BfxwSTfg1jLVcAgOcadwiOl_V0YmZzKy86GplsW1NK61FBz9sCKGyCH=w861-h72-s-rw-gm" alt="AP1GczMAEzc9EkM0XaVMs8px7YKL3EXAUJ2L uJ8YlSTBBnEJAP8BH4JqC2s b5 Rm1LNffiyRiTBmehd0jhvLbPhpAslSkq 8Lx1BfxwSTfg1jLVcAgOcadwiOl V0YmZzKy86GplsW1NK61FBz9sCKGyCH=w861 h72 s rw gm PHPMailer + Gmail OAuth2 寄信" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<p>到這邊就成功取得可以使用的 Token 了，回到 PHPMailer 寄信程式吧！</p>



<h2 class="wp-block-heading para">使用 PHPMailer 寄信</h2>



<p>複製下面的檔案，視自身情況修改幾個地方：</p>



<ol class="my-li bg-darkblue wp-block-list">
<li><strong>require(&#8216;./vendor/autoload.php&#8217;);</strong>：將位置指向正確的路徑。</li>



<li><strong>$client_id</strong>：前面步驟取得的用戶端編號。</li>



<li><strong>$client_secret</strong>：前面步驟取得的用戶端密碼。</li>



<li><strong>$refresh_token</strong>：前面步驟取得的 Token。</li>



<li><strong>$email</strong>：前面步驟通過 OAuth 的信箱。</li>



<li>信件相關之寄件人、收件人、主旨、內容 &#8230; 等。</li>
</ol>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;?php

// 用來看錯誤訊息以及刪除 opcache
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
@opcache_reset();

// 根據自己的路徑修改 composer 的載入位置
require('./vendor/autoload.php');

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
use League\OAuth2\Client\Provider\Google;

// 用戶端編號
$client_id 	= 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com';
// 用戶端密碼
$client_secret 	= 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
// 從前一個步驟取得的 Token
$refresh_token  = '1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxg';
// OAuth 的信箱
$email		= 'xxxxxxx@xxxxxxx';

$mail = new PHPMailer(true);

$provider = new Google(
	[
		'clientId' => $client_id,
		'clientSecret' => $client_secret,
	]
);

$mail->setOAuth(
	new OAuth(
		[
			'provider'     => $provider,
			'clientId'     => $client_id,
			'clientSecret' => $client_secret,
			'refreshToken' => $refresh_token,
			'userName'     => $email,
		]
	)
);

$mail->isSMTP();

$mail->SMTPDebug 	= 3;				// 如果需要顯示debug內容,測完確定沒問題請改成 0
$mail->SMTPAuth 	= true; 
$mail->SMTPSecure 	= 'tls';
$mail->AuthType 	= 'XOAUTH2';
$mail->Host 		= "smtp.gmail.com";
$mail->Port 		= 587;
$mail->CharSet 		= "utf-8";

$mail->setFrom('寄件人信箱', '寄件人名稱');    // 寄件人
$mail->addAddress('收件人信箱', '收件人名稱'); // 收件人
$mail->isHTML(true);                         // 寄送 HTML 格式郵件

$mail->Subject = "郵件主旨";                  // 主旨
$mail->Body    = "&lt;Strong>測試&lt;/Strong>";     // 信件內容
$mail->AltBody = "測試";                      // 無法顯示 HTML 時的內容

$mail->send();

?></pre>



<p>一切就緒後連到該寄信的 PHP 頁面，就可以成功把信寄出囉！</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AP1GczMLz2nu2nJ4oC9XUvFKUNbJNFysGsEQYN9LUHzuBmiEiyxnq5avpPb8-nMINhWVug9WD-guXHdUyfrte1yUxdbLU9O4rRn0fxZZ59U1vF0-j_1XofQvqYgfIHzmGwr-hf9y1ccy9TBervTm_5lZ7Y6w=w630-h363-s-rw-gm" alt="PHPMailer + Gmail OAuth 寄信 19" style="width:400px" title="PHPMailer + Gmail OAuth2 寄信"></figure></div>


<pre class="wp-block-preformatted">延伸閱讀：<a href="https://noter.tw/10744/google-%e8%a9%a6%e7%ae%97%e8%a1%a8%e6%89%b9%e9%87%8f%e7%99%bc%e9%80%81-email-yamm/" data-wpel-link="internal">Google 試算表批量發送 Email (免費版每日 50 封)</a></pre>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/12780/phpmailer-gmail-oauth2-%e5%af%84%e4%bf%a1/" data-wpel-link="internal">PHPMailer + Gmail OAuth2 寄信</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://noter.tw/12780/phpmailer-gmail-oauth2-%e5%af%84%e4%bf%a1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>無法開啟 PHP ZipArchive 下載的 zip 檔</title>
		<link>https://noter.tw/10668/%e7%84%a1%e6%b3%95%e9%96%8b%e5%95%9f-php-ziparchive-%e4%b8%8b%e8%bc%89%e7%9a%84-zip-%e6%aa%94/</link>
					<comments>https://noter.tw/10668/%e7%84%a1%e6%b3%95%e9%96%8b%e5%95%9f-php-ziparchive-%e4%b8%8b%e8%bc%89%e7%9a%84-zip-%e6%aa%94/#respond</comments>
		
		<dc:creator><![CDATA[黃小蛙]]></dc:creator>
		<pubDate>Fri, 16 Dec 2022 13:19:02 +0000</pubDate>
				<category><![CDATA[網頁後端]]></category>
		<category><![CDATA[網頁前端]]></category>
		<category><![CDATA[技術相關]]></category>
		<category><![CDATA[ZipArchive]]></category>
		<category><![CDATA[PHP zip]]></category>
		<category><![CDATA[PHP 壓縮檔案]]></category>
		<category><![CDATA[ob_clean()]]></category>
		<category><![CDATA[網頁直接下載檔案]]></category>
		<category><![CDATA[php]]></category>
		<guid isPermaLink="false">https://noter.tw/?p=10668</guid>

					<description><![CDATA[<p>PHP 內有一個用來操控壓縮檔的 ZipArchive 模組，今天收到問題回報說下載的 zip 檔沒辦法開啟，當下直覺認為是對方操作錯誤，給幾個同事測試之後，有一個同事同樣無法開啟，竟然是因為檔頭多了一些空白造成，這篇記錄此問題的解法。</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/10668/%e7%84%a1%e6%b3%95%e9%96%8b%e5%95%9f-php-ziparchive-%e4%b8%8b%e8%bc%89%e7%9a%84-zip-%e6%aa%94/" data-wpel-link="internal">無法開啟 PHP ZipArchive 下載的 zip 檔</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>PHP 內有一個用來操控壓縮檔的 ZipArchive 模組，今天收到問題回報說下載的 zip 檔沒辦法開啟，當下直覺認為是對方操作錯誤，給幾個同事測試之後，有一個同事同樣無法開啟，竟然是因為檔頭多了一些空白造成，這篇記錄此問題的解法。</p>



<span id="more-10668"></span>



<h2 class="para wp-block-heading">PHP 壓縮檔案</h2>



<p>這邊是留給自己的 ZipArchive 用法的 memo，確定載入此模組後，只要使用以下方法就可以輕鬆把檔案壓縮成 zip</p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 建立 ZipArchive 物件
$zip = new ZipArchive();
// 在指定路徑中建立一個 zip 檔
$res = $zip->open('tmp_callback/123.zip', ZipArchive::CREATE);
// 把 tmp_callback/file1 新增至 zip 內，並改名為 00000001.txt
$zip->addFile('tmp_callback/file1', '00000001.txt');
// 關閉 zip 物件
$zip->close();</pre>



<h2 class="para wp-block-heading">透過網頁下載 zip 檔</h2>



<p>透過以下的語法可以讓瀏覽器直接下載 zip 檔，一方面在之前做權限控管以及避免洩漏檔案真實路徑</p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$path = "/var/www/html/uploads/";
$filename = '03082358.zip';
	
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$filename."\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize($path . $filename));
@readfile($file);</pre>



<h2 class="para wp-block-heading">無法開啟壓縮檔的原因</h2>



<p>直到有使用者反應壓縮檔無法開啟，透過 <a href="https://unzip-online.com/en/zip" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">unzip-online.com<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 線上解壓縮也失敗</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img fetchpriority="high" decoding="async" src="https://lh3.googleusercontent.com/pw/AL9nZEUmC2qlg12i3weQMfhMjRT7Fs36NTNVuXgAmZ14RgARuzc-f3A5P6-R6RVcbFWjuV5zg2buO7UDMNQzmuJq7Yjz6p6uhzi36eDw9VLmBYSYsFfAzZnhLX-h94Sj4Ha-4Vu11rgEEmMHazPjEb01fLom=w940-h463-no?authuser=1" alt="AL9nZEUmC2qlg12i3weQMfhMjRT7Fs36NTNVuXgAmZ14RgARuzc f3A5P6 R6RVcbFWjuV5zg2buO7UDMNQzmuJq7Yjz6p6uhzi36eDw9VLmBYSYsFfAzZnhLX h94Sj4Ha 4Vu11rgEEmMHazPjEb01fLom=w940 h463 no?authuser=1 無法開啟 PHP ZipArchive 下載的 zip 檔" width="470" height="232" title="無法開啟 PHP ZipArchive 下載的 zip 檔"></figure></div>


<p>使用 notepad++ 開啟後才發現 zip 檔開頭有三行空白，可是有些電腦可以正常開啟有些電腦卻不行</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AL9nZEVkfKvVTFLvsECju82DL81lA0cgTr9UbDAzMFQKa0EYoBTd7BkCwQVNsgf7pAeJbFT5wvfG7ovzLSIsHevoDTCTe6caIDyIJmvsoi6cgQzor9JIL0mvkwUAsczIPFE7Eq6i99yno-QHzmq8WTTKaliH=w208-h119-no?authuser=1" alt="AL9nZEVkfKvVTFLvsECju82DL81lA0cgTr9UbDAzMFQKa0EYoBTd7BkCwQVNsgf7pAeJbFT5wvfG7ovzLSIsHevoDTCTe6caIDyIJmvsoi6cgQzor9JIL0mvkwUAsczIPFE7Eq6i99yno QHzmq8WTTKaliH=w208 h119 no?authuser=1 無法開啟 PHP ZipArchive 下載的 zip 檔" width="156" height="89" title="無法開啟 PHP ZipArchive 下載的 zip 檔"></figure></div>


<p>只要把那三行空白刪除，就可以正常解壓縮了 &#8230;</p>



<h2 class="para wp-block-heading">無法開啟壓縮檔的解決方法</h2>



<p>一開始以為是 ZipArchive 模組的問題，做了各種嘗試後發現，產出來的 zip 是正常的，並沒有上面那三行空白，那問題就是出在下載這段了。</p>



<p>試了很多方法發現只要有<code>&lt;?php include_once "xxx.php" ?&gt;</code>就會有三行空白的狀況，一開始笨笨的試著把所有用到的 include 都拔進來放，後來覺得這樣實在是又累又蠢，最後終於找到簡單解法 &#8230; 只要在<code>readfile</code>之前呼叫<code>ob_clean();</code>就可以避免這個問題 &#8230; </p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 以上省略
ob_clean();
@readfile($file);</pre>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/10668/%e7%84%a1%e6%b3%95%e9%96%8b%e5%95%9f-php-ziparchive-%e4%b8%8b%e8%bc%89%e7%9a%84-zip-%e6%aa%94/" data-wpel-link="internal">無法開啟 PHP ZipArchive 下載的 zip 檔</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://noter.tw/10668/%e7%84%a1%e6%b3%95%e9%96%8b%e5%95%9f-php-ziparchive-%e4%b8%8b%e8%bc%89%e7%9a%84-zip-%e6%aa%94/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹</title>
		<link>https://noter.tw/8942/google-apis-3-service-account-credentials/</link>
					<comments>https://noter.tw/8942/google-apis-3-service-account-credentials/#respond</comments>
		
		<dc:creator><![CDATA[黃小蛙]]></dc:creator>
		<pubDate>Tue, 13 Jul 2021 07:37:41 +0000</pubDate>
				<category><![CDATA[技術相關]]></category>
		<category><![CDATA[網頁後端]]></category>
		<category><![CDATA[程式開發]]></category>
		<category><![CDATA[OAuth client ID]]></category>
		<category><![CDATA[API key]]></category>
		<category><![CDATA[Service account]]></category>
		<category><![CDATA[教學]]></category>
		<category><![CDATA[Google 認證]]></category>
		<category><![CDATA[建立 Service Account]]></category>
		<category><![CDATA[分享]]></category>
		<category><![CDATA[APIs & Services]]></category>
		<category><![CDATA[記下來]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[Google APIs]]></category>
		<category><![CDATA[Google APIs Credentials]]></category>
		<category><![CDATA[Credentials 介紹]]></category>
		<category><![CDATA[說明]]></category>
		<guid isPermaLink="false">https://noter.tw/?p=8942</guid>

					<description><![CDATA[<p>總算沒偷懶已經來到第三篇了，還不清楚怎麼回事的朋友可以看前面兩篇 PHP Client 操作 Google APIs (1) 開啟與測試 API 及 PHP Client 操作 Google APIs&#46;&#46;&#46;</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/8942/google-apis-3-service-account-credentials/" data-wpel-link="internal">PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>總算沒偷懶已經來到第三篇了，還不清楚怎麼回事的朋友可以看前面兩篇 <a style="font-size: revert;" rel="noreferrer noopener" href="https://noter.tw/8940/google-apis-1/" target="_blank" data-wpel-link="internal">PHP Client 操作 Google APIs (1) 開啟與測試 API</a> 及 <a style="font-size: revert;" rel="noreferrer noopener" href="https://noter.tw/8944/google-apis-2/" target="_blank" data-wpel-link="internal">PHP Client 操作 Google APIs (2) 安裝 Google API Client for PHP</a>，這篇記錄如何建立 Google APIs Credentials 進行認證，並說明各認證方式的差別。</p>



<span id="more-8942"></span>



<p class="withcode">這系列小蛙以 Google Search Console API 當作例子，其他的 Google APIs 大致上使用的流程類似，差別可能在於用到的物件不同 (Google 分析就要改成使用 <code>Google_Service_Analytics</code> 物件)、要設定的 Scope 不同 (<code>Google_Service_Analytics::ANALYTICS_READONLY</code>，這些在 Google APIs 文件上都會有)，可以使用的認證方式不同 (有的限定只能 OAuth) 等等。</p>



<h2 class="para wp-block-heading">認證方式 (Google APIs Credentials)</h2>



<p class="withcode pink-solid">Google 在 <a href="https://console.cloud.google.com/" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">API Console<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 裡提供了 3 種認證方式，分別是：<code>API Key</code>、<code>OAuth client ID</code> 及 <code>Service account</code>。如果不清楚該使用哪一種的話，可以點最下面的 Help me choose。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLWsr_HcF9xUg_K2uLOc0AIx60-NcmcdxJCRRuW8QHXPQTEheM7Ct1EEhDMCbhmmiAAbaMAOnIGqfSOea6Y2wlJt7avO-8tLs6B3er1BJlO5z5IcZhlknQeIAZXTnmUegsn7zlimWzmNVJnDLT8KW0yHwA=w448-h282-no?authuser=3" alt="Google APIs Credentials" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<h3 class="para wp-block-heading">API Key</h3>



<p>API Key 是最簡單也最單純的，如果使用到 Google APIs 的功能是支援 API Key 存取的話，這是一個非常簡單的好選項，只要設定完成後，會得到一組 API Key，接著設定在 API Client 內就可以使用了。</p>



<h3 class="para wp-block-heading">OAuth client ID</h3>



<p>透過自己的 Gmail 帳號登入後並授權後才可以使用，用來存取自己帳號相關的資料，例如：行事曆、Search Console 資料、Google Analytics 資料 &#8230; 等。第一篇測試 APIs Explorer 時跳出的視窗就是使用 OAuth 2.0 登入的畫面。</p>



<div class="wp-block-image"><figure class="aligncenter size-large is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLViqpex5Vq1tiC-5zHF84HkfewOulCuvoms8WvDbmNC4WNlWfxr_sW0PPedt4Mu8xhel_4oidPPm9R2hXS72UjvzhglZyQkLW6Bxwets6MXnrEZDseuzGfqeJGoHbzMTB8bGlqWvCzMQrJUotyxb5P4Fg=w601-h660-no?authuser=3" alt="OAuth 2.0 Login Screen" width="451" height="495" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<h3 class="para wp-block-heading">Service account</h3>



<p>主要在 Server 端的應用，有些 APIs 需要透過 OAuth 取得帳號相關資料，但是如果要讓它自動執行或是做成機器人 (robot) 的話，沒辦法每次跳出視窗讓你去按登入授權，因此這種狀況選擇 Service account 就對了。</p>



<h2 class="para wp-block-heading">建立 Service Account </h2>



<p>繼續到 GCP 左側選單，點擊 APIs &amp; Services -&gt; Credentials</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLVwRX1gGcTjFQpwDX1KvCbZGYS1ZuoR21ueYlqk2HL6NoqldMtZEF6EccUjnWQq4S7gRIQdV2Me8A0AGDIOnTwlUmBvymjHDai5_Wki8njdr-FeL-8iwFtWR5GnhJ-VcO2FW9RzdUBgQbEfkyFi8DrsxQ=w452-h508-no?authuser=3" alt="Google APIs Credentials 1" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<p>點擊上方 CREATE CREDENTIALS 後選擇 Service account</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLWsr_HcF9xUg_K2uLOc0AIx60-NcmcdxJCRRuW8QHXPQTEheM7Ct1EEhDMCbhmmiAAbaMAOnIGqfSOea6Y2wlJt7avO-8tLs6B3er1BJlO5z5IcZhlknQeIAZXTnmUegsn7zlimWzmNVJnDLT8KW0yHwA=w448-h282-no?authuser=3" alt="Google APIs Credentials 2" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<p>輸入帳號名稱 (Service account name) 後會自動產生 (Service account ID)，輸入描述避免太多 Credentials 的時候可以弄清楚哪個是哪個，輸入完成後點擊 CREATE AND CONTINUE</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLWPU0h72ZGCeUnCbvMw5BR9nzGpOVZNZk4hQb8HvNr_ch_quwpujdXI0EigqkXMLDEPe3RsOtocWpxk5UJLiNGcamxDjVoJpIW4NuS_ihuV14IyFoJ81n9KTYh5jCAwWZYANJbEKXzRg69BViOjj2CBbw=w540-h430-no?authuser=3" alt="Google APIs Credentials 3" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<p>這步驟是選用的，小蛙直接跳過</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLUtquEXwP7kq6E9-rw9Xm0c6q--rr8vhNmyOCDmRNUppTPV6oWsS_swrgUtqSVkRF_hVzvkIQLKvCzf2rct__mlHvYA0uxnbWowWoN6pKsteIPv5KAqhgN_Ti-3ZHqqEbZ04ICSh5AAl1MiymmxdZ61Ng=w523-h363-no?authuser=3" alt="Google APIs Credentials 4" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<p>這步驟也是選用的，直接跳過</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLX_uiCo_D0ASkeoSQO5zEM3K4_Y5BXmgxxONvPiGeKcT9Vb7GuzWNm8legvtzE6uVTgUt_BR-L6CgKMvIoUpQsqSYXK8QlvMULhqIkCSr1kSn96-Wnx-utTx3Gy6UE7DHvkNmOLfxiTiqphM-DKvrMwGQ=w527-h317-no?authuser=3" alt="Google APIs Credentials 5" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<p>回到主畫面後可以看到剛剛新增的帳號出現了，點選右邊的鉛筆進入修改畫面</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLU_OhP-_kwBeNqHbmhuo0Dn0uK_yH_ARM1RnSV3j1Ai2wJaCFfwRxWcnGFfdFoYsvyEV3dkxU_Fx3UA57Kt2Kgrhq8qW6YpniaDCFHfdaIMXM38Z8vSbFdwrlkDT2MUPV44j-31c5KHNtKoD4mrz9zR2g=w798-h172-no?authuser=3" alt="Create Credentials 6" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<p>上方切換到 KEYS 頁籤 (1)，點擊下方 ADD KEY (2)，選擇 Create new key (3)</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLWS7pdvaQdNoJdCL4vHP1EBApz1KUFuhKUu9UF02boWTzM7l-NJbcKo0xy_P_DRUpk5tKulBfpqF7c96nfMWNt7gdzmZCsCyAe8WSOfkV2HPD0n8NX6CXSmNneQsZIK8DDlj61Cy0bveB3POdOsWAnbRg=w488-h460-no?authuser=3" alt="Create Credentials 7" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<p>選擇 Key type，Google 建議使用 JSON 格式 (1) 的就好，點擊右下角的 CREATE (2)</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLV-HbT8k_2SZOSa_lWjr-tFn9n4e6oQEK4R1u_xZw36u-Cm8XxxNrB1o9fN0_jawmUByCaTkOn-rnpemUIXG8xYVoBptRx0sL5Qd_YD03Qrf-TqrFngKejWBxzL7VBnn2O87QXoThjAWmIzFXdIvXHCgw=w530-h328-no?authuser=3" alt="Create Credentials 8" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<p>接著會開始下載 JSON，這個 JSON 一定要保存好喔，如果遺失了是沒辦法再下載的，只能把原本的刪除後再產生一個，一定要注意。</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLVto-2A2QceFHDryLAVFYIBxDiI8exjglSIhl03n8v7qDynqYwM1q6x0NHsrnVhPaawZ3W7AKdGSdiCchb_RBgVFZZMbQe_GKcfso0Cjz7yTuaFGDJmlrlRARv3u_xKx_yk5u-Sq1ckoKq5UpQ1OlHCkQ=w677-h131-no?authuser=3" alt="Create Credentials 9" title="PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹"></figure></div>



<p>到這邊 Service Account 就建立完成了，下一篇將會說明如何透過 PHP Client 操作 Google Search Console APIs 來取得相關數據。</p>



<p id="block-c58a739f-b4e1-42ae-8530-5e20da347253"><strong>Google 系列文章：</strong></p>



<ul class="wp-block-list" id="block-2b1f3c8f-95c7-4eff-8105-53dea9d75e5e"><li><a href="https://noter.tw/8940/google-apis-1-api-explorer/" data-wpel-link="internal">PHP Client 操作 Google APIs (1) 開啟與測試 API</a></li><li><a href="https://noter.tw/8944/google-apis-2-google-api-client-for-php/" data-wpel-link="internal">PHP Client 操作 Google APIs (2) 安裝 Google API Client for PHP</a></li><li><a href="https://noter.tw/8942/google-apis-3-service-account-credentials/" data-wpel-link="internal">PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹</a></li><li><a href="https://noter.tw/9035/google-apis-4-google-search-console-apis/" data-wpel-link="internal">PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs</a></li><li><a href="https://noter.tw/8604/google-formranger/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">Google 表單應用：設定選項次數上限</a></li><li><a href="https://noter.tw/9049/copy-a-google-form/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">複製 Google 表單 (Copy A Google Form)</a></li><li><a href="https://noter.tw/8358/google-adsense-tax-w8ben/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">Google Adsense: 向 Google 提交稅務資料 | 填寫 W-8BEN 稅務表單</a></li><li><a href="https://noter.tw/4946/google-%e9%9b%b2%e7%ab%af%e5%a4%96%e5%b8%b6%e6%9c%8d%e5%8b%99-takeout%ef%bc%9a%e8%bc%95%e9%ac%86%e6%89%93%e5%8c%85%e9%9b%b2%e7%ab%af%e7%a1%ac%e7%a2%9f%e3%80%81%e7%9b%b8%e7%b0%bf-%e7%ad%89/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">Google 雲端外帶服務 (Takeout)：輕鬆打包雲端硬碟、相簿 … 等資料</a></li><li><a href="https://noter.tw/4394/google-%e7%9b%b8%e7%b0%bf%e5%8f%96%e5%be%97%e7%9c%9f%e5%af%a6%e5%9c%96%e7%89%87%e4%bd%8d%e7%bd%ae%ef%bc%88%e6%8f%92%e5%85%a5%e5%9c%96%e7%89%87%e5%88%b0%e6%96%87%e7%ab%a0%ef%bc%89/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">Google 相簿取得真實圖片位置（插入圖片到文章）</a></li><li><a href="https://noter.tw/4267/gdirve-%e8%ae%93%e4%bd%a0%e5%9c%a8-linux-%e6%96%87%e5%ad%97%e4%bb%8b%e9%9d%a2%e4%b9%9f%e8%83%bd%e5%a5%bd%e5%a5%bd%e4%bd%bf%e7%94%a8-google-drive/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">gdirve 讓你在 Linux 文字介面也能好好使用 Google Drive</a></li><li><a href="https://noter.tw/4302/microsoft-office-word-%e7%84%a1%e6%b3%95%e9%96%8b%e5%95%9f-google-drive-%e8%b6%85%e9%80%a3%e7%b5%90/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">Microsoft Office Word 無法開啟 Google Drive 超連結</a></li></ul>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/8942/google-apis-3-service-account-credentials/" data-wpel-link="internal">PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://noter.tw/8942/google-apis-3-service-account-credentials/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs</title>
		<link>https://noter.tw/9035/google-apis-4-google-search-console-apis/</link>
					<comments>https://noter.tw/9035/google-apis-4-google-search-console-apis/#comments</comments>
		
		<dc:creator><![CDATA[黃小蛙]]></dc:creator>
		<pubDate>Tue, 13 Jul 2021 07:30:16 +0000</pubDate>
				<category><![CDATA[其他]]></category>
		<category><![CDATA[網頁後端]]></category>
		<category><![CDATA[技術相關]]></category>
		<category><![CDATA[分享]]></category>
		<category><![CDATA[記下來]]></category>
		<category><![CDATA[Google APIs]]></category>
		<category><![CDATA[Google Cloud Platform]]></category>
		<category><![CDATA[Goolge Search Console APIs]]></category>
		<category><![CDATA[開發]]></category>
		<category><![CDATA[教學]]></category>
		<guid isPermaLink="false">https://noter.tw/?p=9035</guid>

					<description><![CDATA[<p>這系列的最後一篇，原本以為已經寫完了，今天發現這篇躺在草稿裡 &#8230; 還不清楚怎麼回事的朋友可以看前面幾篇 PHP Client 操作 Google APIs (1) 開啟與測試 API 、 &#46;&#46;&#46;</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/9035/google-apis-4-google-search-console-apis/" data-wpel-link="internal">PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>這系列的最後一篇，原本以為已經寫完了，今天發現這篇躺在草稿裡 &#8230; 還不清楚怎麼回事的朋友可以看前面幾篇 <a style="font-size: revert;" rel="noreferrer noopener" href="https://noter.tw/8940/google-apis-1/" target="_blank" data-wpel-link="internal">PHP Client 操作 Google APIs (1) 開啟與測試 API</a> 、 <a style="font-size: revert;" rel="noreferrer noopener" href="https://noter.tw/8944/google-apis-2/" target="_blank" data-wpel-link="internal">PHP Client 操作 Google APIs (2) 安裝 Google API Client for PHP</a> 及 <a href="https://noter.tw/8942/google-apis-3-service-account-credentials/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹</a>，這篇記錄如何使用 Google Search Console APIs。 </p>



<span id="more-9035"></span>



<p>我們在前幾篇裡面瞭解了 Google APIs、安裝 Client for PHP 以及申請了 Google APIs Credentials 裡的 Service Account 並下載了 JSON (一定要好好保存，遺失了只能刪除再產生一次，沒辦法再次下載喔)，開始使用！</p>



<h2 class="para wp-block-heading">Google APIs 大致流程</h2>



<p>使用 Google APIs 大致上的流程都差不多</p>



<ul class="my-li bg-darkblue wp-block-list"><li>載入 Google APIs Client for PHP (或其他語言版本)</li><li>加入 Service Account</li><li>建立並初始化物件</li><li>設定篩選條件</li><li>取得並解析結果</li></ul>



<p>只要把握這幾個原則，之後若要使用 Google APIs 就不會再霧沙沙了 (但 API 還是要查，每個 API 的 Scope 跟條件設定上會有很大的不同)</p>



<h2 class="para wp-block-heading">載入 Google APIs Client for PHP</h2>



<p>這個在前面  <a style="font-size: revert;" rel="noreferrer noopener" href="https://noter.tw/8944/google-apis-2/" target="_blank" data-wpel-link="internal">PHP Client 操作 Google APIs (2) 安裝 Google API Client for PHP</a>  的時候已經講過了，在程式碼裡面很簡單，開頭放上這行就可以了，為了更方便 debug，可以多設定兩行條件，只要使用上出現錯誤，就會直接在網頁上呈現</p>



<pre class="wp-block-preformatted withcode">// 顯示錯誤
ini_set('display_errors', '1');
// 所有錯誤都顯示
error_reporting(E_ALL);
// 載入 Google Client
require_once '../googleapiclient/vendor/autoload.php';</pre>



<h2 class="para wp-block-heading">加入 Service Account</h2>



<p>小蛙前面提到這次要做的事情是讓程式不經人工自動執行，而 Search Console 能使用的方式只有 OAuth 與 Service Account (忘記差別的這邊請 <a href="https://noter.tw/8942/google-apis-3-service-account-credentials/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹</a>)，要不需人工介入只有 Service Account 了，而要讓 Service Account 存取 Search Console 資料的話，就必須將該帳號加入 Search Console 裡面囉！</p>



<p>首先進入 Search Console 要使用的資源，左邊選單下面找到「設定」</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLXzIBu7ISSOmPScFy7qVA96IJdZN6hn_2sC8u8CAQo6ilW2TSsoE7VnZcq8sj1gRGhJqQ6wc_wjaC6MNwP3aY-rbsd89loU6C4h5Abb520EUYBS_0TD5NMys3eYYxweQuQwqvchEjMtFMJr9_65SCb-QA=w326-h243-no?authuser=2" alt="AM JKLXzIBu7ISSOmPScFy7qVA96IJdZN6hn 2sC8u8CAQo6ilW2TSsoE7VnZcq8sj1gRGhJqQ6wc wjaC6MNwP3aY rbsd89loU6C4h5Abb520EUYBS 0TD5NMys3eYYxweQuQwqvchEjMtFMJr9 65SCb QA=w326 h243 no?authuser=2 PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs" title="PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs"></figure></div>


<p>點擊「使用者與權限」</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLXZUyX8xf-BHak-20EBkiO2TFTuTXL_INR2QUGFYk7pHLNsIPtwfApwqJ1xcShRkvqj2low9Iuh4ksHN79DSkUqUjylMiyEd1MMK0DO5P2U1X-ZfMa9lKLLv8EyrMp1-j3pXSYrSGxt8gh5MKlyxeN7ww=w817-h394-no?authuser=2" alt="AM JKLXZUyX8xf BHak 20EBkiO2TFTuTXL INR2QUGFYk7pHLNsIPtwfApwqJ1xcShRkvqj2low9Iuh4ksHN79DSkUqUjylMiyEd1MMK0DO5P2U1X ZfMa9lKLLv8EyrMp1 j3pXSYrSGxt8gh5MKlyxeN7ww=w817 h394 no?authuser=2 PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs" width="613" height="296" title="PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs"></figure></div>


<p>點擊「新增使用者」並把從 GCP Credentials 那邊得到的 Service Account 貼上，權限選擇「完整」</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLXEF6Sy2MrLHsvzAKbOuqRzWdPB-kwXigP9LAgaaBdiHnlavX2vwEvWRLMW6NS4nJi_mkQlhcD0ldCVhN4KoVx_D_azli_64i9YbL1nDF7HEzeBvMSG5mS0gdHUv_YeADHYPcEgZqaWH6uKZFM4gwAj5A=w746-h385-no?authuser=2" alt="AM JKLXEF6Sy2MrLHsvzAKbOuqRzWdPB kwXigP9LAgaaBdiHnlavX2vwEvWRLMW6NS4nJi mkQlhcD0ldCVhN4KoVx D azli 64i9YbL1nDF7HEzeBvMSG5mS0gdHUv YeADHYPcEgZqaWH6uKZFM4gwAj5A=w746 h385 no?authuser=2 PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs" width="560" height="289" title="PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs"></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-large"><img decoding="async" src="https://lh3.googleusercontent.com/pw/AM-JKLVdteLUJJMU-tjYJzk3UQDQIhDk0n7Q36DgbhQKKbu3n57YYr2H1aPYTP0krgU5rBXxSGz8_z40ZmiJEOCaGJKCK-XhvCFhYdVza6Zs3z1Y2T5P7DAshqm-rDHu4jGLKaBkisrRs7fcjXUvlPo5Lh56mw=w1004-h385-no?authuser=2" alt="AM JKLVdteLUJJMU tjYJzk3UQDQIhDk0n7Q36DgbhQKKbu3n57YYr2H1aPYTP0krgU5rBXxSGz8 z40ZmiJEOCaGJKCK XhvCFhYdVza6Zs3z1Y2T5P7DAshqm rDHu4jGLKaBkisrRs7fcjXUvlPo5Lh56mw=w1004 h385 no?authuser=2 PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs" title="PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs"></figure></div>


<h2 class="para wp-block-heading">建立並初始化物件</h2>



<p>回到 PHP 的部份加入金鑰及設定 Scope</p>



<pre class="wp-block-preformatted withcode">// 設定金鑰
putenv('GOOGLE_APPLICATION_CREDENTIALS=存放金鑰的路徑/server_secrets.json');
session_start();

// 建立基本 Google Client 
$client = new Google_Client();
// 載入預設金鑰位置
$client-&gt;useApplicationDefaultCredentials();
// 設定 Scope
$client-&gt;addScope([Google_Service_Webmasters::WEBMASTERS_READONLY, Google_Service_Webmasters::WEBMASTERS]);
$client-&gt;setAccessType('offline');
// 建立基本 Google Service Webmasters 物件
$master = new Google_Service_Webmasters($client);</pre>



<h2 class="para wp-block-heading">設定篩選條件</h2>



<p>建立完要用的 Google Service Webmasters 物件之後，對！以前 Google Search Console 叫做 Google Webmaster，雖然我們說要操作 Search Console 但建立的物件跟設定的 Scope 可以看出都還是 Webmasters。</p>



<pre class="wp-block-preformatted withcode">$search = new Google_Service_Webmasters_SearchAnalyticsQueryRequest();
$search-&gt;setStartDate( '2021-07-06' );
$search-&gt;setEndDate( '2021-08-06' );
$search-&gt;setDimensions( ['QUERY'] );
$search-&gt;setAggregationType( 'BY_PROPERTY' );
$search-&gt;setRowLimit(10);</pre>



<p>至於有哪些條件或是要怎麼操作取得自己要的內容，就自己到 <a href="https://developers.google.com/webmaster-tools/search-console-api-original/v3/searchanalytics/query" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">API Explorer<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 測試，或到 <a href="https://noter.tw/8940/google-apis-1/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">PHP Client 操作 Google APIs (1) 開啟與測試 API</a> 複習吧！</p>



<h2 class="para wp-block-heading">取得並解析結果</h2>



<p>最後把他們全部串起來～丟回的結果可以先用 print_r 印出來看看，或是直接到 <a href="https://developers.google.com/webmaster-tools/search-console-api-original/v3/searchanalytics/query" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">這邊<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 看回傳的結果 ~</p>



<pre class="wp-block-preformatted withcode">$data = $master-&gt;searchanalytics-&gt;query('https://noter.tw/', $search);

$result = [];
$rows = $data-&gt;rows;
foreach ($rows as $row){
    // 剖析資料
}

</pre>



<p>到這邊基本上就大功告成了，上面的功能是取得網站上特定區間的關鍵字資訊 ~ 需要其他資料的話，釣竿已經在手了，就要靠自己囉！Good Luck ~ </p>



<p id="block-c58a739f-b4e1-42ae-8530-5e20da347253"><strong>Google 系列文章：</strong></p>



<ul class="wp-block-list" id="block-2b1f3c8f-95c7-4eff-8105-53dea9d75e5e"><li><a href="https://noter.tw/8940/google-apis-1-api-explorer/" data-wpel-link="internal">PHP Client 操作 Google APIs (1) 開啟與測試 API</a></li><li><a href="https://noter.tw/8944/google-apis-2-google-api-client-for-php/" data-wpel-link="internal">PHP Client 操作 Google APIs (2) 安裝 Google API Client for PHP</a></li><li><a href="https://noter.tw/8942/google-apis-3-service-account-credentials/" data-wpel-link="internal">PHP Client 操作 Google APIs (3) Google APIs Credentials 介紹</a></li><li><a href="https://noter.tw/9035/google-apis-4-google-search-console-apis/" data-wpel-link="internal">PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs</a></li><li><a href="https://noter.tw/8604/google-formranger/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">Google 表單應用：設定選項次數上限</a></li><li><a href="https://noter.tw/9049/copy-a-google-form/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">複製 Google 表單 (Copy A Google Form)</a></li><li><a href="https://noter.tw/8358/google-adsense-tax-w8ben/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">Google Adsense: 向 Google 提交稅務資料 | 填寫 W-8BEN 稅務表單</a></li><li><a href="https://noter.tw/4946/google-%e9%9b%b2%e7%ab%af%e5%a4%96%e5%b8%b6%e6%9c%8d%e5%8b%99-takeout%ef%bc%9a%e8%bc%95%e9%ac%86%e6%89%93%e5%8c%85%e9%9b%b2%e7%ab%af%e7%a1%ac%e7%a2%9f%e3%80%81%e7%9b%b8%e7%b0%bf-%e7%ad%89/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">Google 雲端外帶服務 (Takeout)：輕鬆打包雲端硬碟、相簿 … 等資料</a></li><li><a href="https://noter.tw/4394/google-%e7%9b%b8%e7%b0%bf%e5%8f%96%e5%be%97%e7%9c%9f%e5%af%a6%e5%9c%96%e7%89%87%e4%bd%8d%e7%bd%ae%ef%bc%88%e6%8f%92%e5%85%a5%e5%9c%96%e7%89%87%e5%88%b0%e6%96%87%e7%ab%a0%ef%bc%89/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">Google 相簿取得真實圖片位置（插入圖片到文章）</a></li><li><a href="https://noter.tw/4267/gdirve-%e8%ae%93%e4%bd%a0%e5%9c%a8-linux-%e6%96%87%e5%ad%97%e4%bb%8b%e9%9d%a2%e4%b9%9f%e8%83%bd%e5%a5%bd%e5%a5%bd%e4%bd%bf%e7%94%a8-google-drive/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">gdirve 讓你在 Linux 文字介面也能好好使用 Google Drive</a></li><li><a href="https://noter.tw/4302/microsoft-office-word-%e7%84%a1%e6%b3%95%e9%96%8b%e5%95%9f-google-drive-%e8%b6%85%e9%80%a3%e7%b5%90/" target="_blank" rel="noreferrer noopener" data-wpel-link="internal">Microsoft Office Word 無法開啟 Google Drive 超連結</a></li></ul>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/9035/google-apis-4-google-search-console-apis/" data-wpel-link="internal">PHP Client 操作 Google APIs (4) 使用 Google Search Console APIs</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://noter.tw/9035/google-apis-4-google-search-console-apis/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Post 後上一頁 history.back() 發生文件已到期/認重新提交表單 ERR_CACHE_MISS</title>
		<link>https://noter.tw/8538/err_cache_miss/</link>
					<comments>https://noter.tw/8538/err_cache_miss/#comments</comments>
		
		<dc:creator><![CDATA[黃小蛙]]></dc:creator>
		<pubDate>Sat, 05 Jun 2021 06:41:57 +0000</pubDate>
				<category><![CDATA[網頁後端]]></category>
		<category><![CDATA[網頁前端]]></category>
		<category><![CDATA[post]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[JSP]]></category>
		<category><![CDATA[ERR_CACHE_MISS]]></category>
		<category><![CDATA[history.back()]]></category>
		<category><![CDATA[送出表單]]></category>
		<category><![CDATA[重新提交表單]]></category>
		<category><![CDATA[上一頁]]></category>
		<category><![CDATA[文件已過期]]></category>
		<category><![CDATA[重新導向]]></category>
		<category><![CDATA[Cache-Control]]></category>
		<guid isPermaLink="false">https://noter.tw/?p=8538</guid>

					<description><![CDATA[<p>原本可以正常使用的系統裡，經過資安調整之後出現了 ERR_CACHE_MISS 的問題，記錄一下解決的方法。 平常瀏覽網頁的時候也會遇到，當送出表單後，點選「上一頁」常可以看到文件已過期，或是重新傳送&#46;&#46;&#46;</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/8538/err_cache_miss/" data-wpel-link="internal">Post 後上一頁 history.back() 發生文件已到期/認重新提交表單 ERR_CACHE_MISS</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>原本可以正常使用的系統裡，經過資安調整之後出現了 ERR_CACHE_MISS 的問題，記錄一下解決的方法。</p>



<span id="more-8538"></span>



<p class="withcode">平常瀏覽網頁的時候也會遇到，當送出表單後，點選「上一頁」常可以看到文件已過期，或是重新傳送這些資料才可以正確顯示之類的錯誤訊息，記得以前會看到<code>ERR_CACHE_MISS</code>錯誤的字樣，小蛙這次遇到的只有顯示如下圖</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://lh3.googleusercontent.com/pw/ACtC-3eSvPTbFIORBjFcpEsPvn90wGzIaXNUCrp25i3s9QSent9yf6064q3zx3R1JpHozzATD2LhTwU1_d3al4waPQ5RX7JAmDlMN2Nf7eo-xfyuYanrDOQk5Aw-T6GKADAvVyk6SclvZGi7YVpD79o8o_lA7Q=w1314-h650-no?authuser=2" alt="ACtC 3eSvPTbFIORBjFcpEsPvn90wGzIaXNUCrp25i3s9QSent9yf6064q3zx3R1JpHozzATD2LhTwU1 d3al4waPQ5RX7JAmDlMN2Nf7eo xfyuYanrDOQk5Aw T6GKADAvVyk6SclvZGi7YVpD79o8o lA7Q=w1314 h650 no?authuser=2 Post 後上一頁 history.back() 發生文件已到期/認重新提交表單 ERR_CACHE_MISS" title="Post 後上一頁 history.back() 發生文件已到期/認重新提交表單 ERR_CACHE_MISS"><figcaption>Chrome 錯誤訊息：這個網頁需要使用你先前輸入的資料才能正確顯示。你可以重新傳送這些資料，不過這麼做會重複執行這個網頁先前執行過的任何動作。 (ERR_CACHE_MISS)</figcaption></figure>



<figure class="wp-block-image size-large"><img decoding="async" src="https://lh3.googleusercontent.com/pw/ACtC-3f_qjJrM_VT4J6DVmyBUA8UnC4f3AzMssF_Ctxux1nsRzdsxZXI0prLeuH-01YNoQ3WYgIpG-BzUYaeDt3unvCruWrxGTIOnzNOcr3eynoV4gRC84NUQCEhzWcUTyRtEdBwsgZQn02TmqZD1j9-lkS8Cg=w826-h494-no?authuser=2" alt="ACtC 3f qjJrM VT4J6DVmyBUA8UnC4f3AzMssF Ctxux1nsRzdsxZXI0prLeuH 01YNoQ3WYgIpG BzUYaeDt3unvCruWrxGTIOnzNOcr3eynoV4gRC84NUQCEhzWcUTyRtEdBwsgZQn02TmqZD1j9 lkS8Cg=w826 h494 no?authuser=2 Post 後上一頁 history.back() 發生文件已到期/認重新提交表單 ERR_CACHE_MISS" title="Post 後上一頁 history.back() 發生文件已到期/認重新提交表單 ERR_CACHE_MISS"><figcaption>Firefox 錯誤訊息：文件已過期 此文件已不存在。您所請求的文件已不存在於 Firefox 的快取當中。為了您的安全，Firefox 將不會自動重新請求敏感文件。請點下重試以重新向網站請求取得文件。(ERR_CACHE_MISS)</figcaption></figure>



<p class="withcode">小蛙接手處理的這個系統則是原本點選上一頁沒有問題，經過資安掃描修正後發生的，Google 及跟處理過該狀況的同事討論過之後找到相同的答案，如果在 header 中加入<code>Cache-Control: private</code>的設定，就可以解決掉這個問題！</p>



<h2 class="para wp-block-heading">PHP</h2>



<pre class="wp-block-preformatted withcode">// 在 session_start(); 後加入
header("Cache-Control:private");</pre>



<h2 class="para wp-block-heading">JSP</h2>



<pre class="wp-block-preformatted withcode">response.setHeader("Cache-Control", "private");</pre>



<p>參考資料：</p>



<ul class="my-li bg-darkblue wp-block-list"><li>Chrome浏览器对于POST页面执行history.back返回或表单数据丢失的解决办法<br>(連結失效 https://www.unixso.com/Php/post-header.html)</li><li><a href="https://blog.csdn.net/A9925/article/details/42027229" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">ERR_CACHE_MISS 搜尋回上一頁空白或錯誤<span class="wpel-icon wpel-image wpel-icon-6"></span></a></li><li><a href="https://www.cnblogs.com/houdj/p/9685571.html" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">ERR_CACHE_MISS 上一页提示确认重新提交表单<span class="wpel-icon wpel-image wpel-icon-6"></span></a></li></ul>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/8538/err_cache_miss/" data-wpel-link="internal">Post 後上一頁 history.back() 發生文件已到期/認重新提交表單 ERR_CACHE_MISS</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://noter.tw/8538/err_cache_miss/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>WordPress 利用鉤子(Hooks)清除頁面快取</title>
		<link>https://noter.tw/6888/wordpress-%e5%88%a9%e7%94%a8%e9%89%a4%e5%ad%90hooks%e6%b8%85%e9%99%a4%e9%a0%81%e9%9d%a2%e5%bf%ab%e5%8f%96/</link>
					<comments>https://noter.tw/6888/wordpress-%e5%88%a9%e7%94%a8%e9%89%a4%e5%ad%90hooks%e6%b8%85%e9%99%a4%e9%a0%81%e9%9d%a2%e5%bf%ab%e5%8f%96/#comments</comments>
		
		<dc:creator><![CDATA[黃小蛙]]></dc:creator>
		<pubDate>Sun, 29 Dec 2019 09:55:15 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[網頁後端]]></category>
		<category><![CDATA[鉤子]]></category>
		<category><![CDATA[清除快取]]></category>
		<category><![CDATA[更新文章]]></category>
		<category><![CDATA[觸發]]></category>
		<category><![CDATA[程式]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[add_action]]></category>
		<category><![CDATA[wp_head]]></category>
		<category><![CDATA[transition_post_status]]></category>
		<category><![CDATA[教學]]></category>
		<category><![CDATA[comment_post]]></category>
		<category><![CDATA[分享]]></category>
		<category><![CDATA[hooks]]></category>
		<guid isPermaLink="false">https://noter.tw/?p=6888</guid>

					<description><![CDATA[<p>記下來由於有使用快取的緣故，常常發文或有網友留言後頁面都不會更新，對於小蛙常常要測試修改一些東西的時候被快取誤導 Orz &#8230; 這篇記錄一下怎麼用 WordPress 內建的鉤子來解決這件事&#46;&#46;&#46;</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/6888/wordpress-%e5%88%a9%e7%94%a8%e9%89%a4%e5%ad%90hooks%e6%b8%85%e9%99%a4%e9%a0%81%e9%9d%a2%e5%bf%ab%e5%8f%96/" data-wpel-link="internal">WordPress 利用鉤子(Hooks)清除頁面快取</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>記下來由於有使用快取的緣故，常常發文或有網友留言後頁面都不會更新，對於小蛙常常要測試修改一些東西的時候被快取誤導 Orz &#8230; 這篇記錄一下怎麼用 WordPress 內建的鉤子來解決這件事情。</p>



<span id="more-6888"></span>



<p>有興趣了解更多的網友可以到 <a rel="noreferrer noopener nofollow external" aria-label="WordPress的Hook機制與原理 (在新分頁中開啟)" href="https://audilu.com/2011/10/10/wordpress-hook/" target="_blank" data-wpel-link="external" class="wpel-icon-right">WordPress的Hook機制與原理<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 看看，小蛙直接進主題。</p>



<h2 class="wp-block-heading">什麼是鉤子（hooks）</h2>



<p>WordPress 有一種叫做鉤子的機制，在某些情況或條件發生的時候，去「鉤」起一連串相關要處理的功能，<a rel="noreferrer noopener nofollow external" aria-label="List of all WordPress hooks (在新分頁中開啟)" href="https://adambrown.info/p/wp_hooks/hook" target="_blank" data-wpel-link="external" class="wpel-icon-right">List of all WordPress hooks<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 可以看到所有的鉤子列表。以上面的文章舉例，WordPress 載入網頁的時候會呼叫一個 wp_head() 來載入相對應 html head 的部份，如果我們希望在執行 wp_head 的時候額外多做一些事情，就可以在佈景主題下的 functions.php 加入</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">add_action('wp_head', 'extra_act');
function  extra_act () {
   ... 要做的事情放在這裡 ...
} </pre>



<p>透過 add_action 來告訴 WordPress 說，當你執行到 wp_head 的時候，我要多掛上一件叫做 extra_act 的事情麻煩你一起處理，在 Drupal 也同樣存在 Hooks 的概念。知道鉤子是幹嘛的之後，就可以開始進行我們要做的事情了。</p>



<h2 class="wp-block-heading">查找能用的鉤子</h2>



<p>從 <a rel="noreferrer noopener nofollow external" href="https://adambrown.info/p/wp_hooks/hook" target="_blank" data-wpel-link="external" class="wpel-icon-right">List of all WordPress hooks<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 可以查到可以用的鉤子，小蛙要的功能是當使用者留言之後，及小蛙的文章有變動的時候，把 pagespeed module 及 php 的快取清除，開始來找跟留言及文章變動有關的鉤子。</p>



<p class="withcode pink-solid">小蛙找到 <code>transition_post_status</code> 及 <code>comment_post</code> 這兩個鉤子，看起來符合需求，開始加工需要的功能</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">// 加入文章狀態改變的鉤子
add_action('transition_post_status', 'post_changed');
function post_changed($new_status, $old_status=null, $post=null) {
    if('publish' == $new_status || 'publish' == $old_status){
        // 清除快取，小蛙是用 shell_exec 執行系統指令
    }
}</pre>



<p>文章新狀態是 &#8220;發布&#8221; 的情況下，以及舊狀態是 &#8220;發布&#8221; 的情況下才做清除快取，帶入到實際狀況就是文章從所有狀態轉成 &#8220;發布&#8221;，表示新發文章（無論從垃圾桶叫回來、草稿發布 &#8230;）都會符合新狀態為 &#8220;發布&#8221; 的情況。而舊狀態為 &#8220;發布&#8221; 的情況則是原本已發布的網頁，刪除或是轉換回草稿，都會符合舊狀態為 &#8220;發布&#8221; 的條件。</p>



<p>訪客留言部份程式碼如下，只要有新的留言進來，就會執行清除快取，讓留言的使用者可以馬上看到剛剛的留言～</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">add_action('comment_post', 'comment_posted');
function comment_posted() {
     // 清除快取，小蛙是用 shell_exec 執行系統指令 
}</pre>



<p>到這邊就完成小蛙的需求囉！留個記錄給有需要的人～</p>



<p><strong>WordPress 5.0 系列：</strong></p>



<ul class="my-li bg-darkblue wp-block-list"><li><a href="https://noter.tw/446/wordpress-升級-5-0/" data-wpel-link="internal">WordPress 升級 5.0</a></li><li><a href="https://noter.tw/552/wordpress-5-0-區塊基本使用/" data-wpel-link="internal">WordPress 5.0 區塊基本使用</a></li><li><a href="https://noter.tw/569/wordpress-5-0-相關問題/" data-wpel-link="internal">WordPress 5.0 相關問題</a></li><li><a href="https://noter.tw/4262/%E5%BC%B7%E5%8C%96-wordpress-%E6%90%9C%E5%B0%8B%E5%8A%9F%E8%83%BD-search-filter-toolset-types/" data-wpel-link="internal">強化 WordPress 搜尋功能 – Search &amp; Filter + Toolset Types</a></li><li><a href="https://noter.tw/6888/wordpress-利用鉤子(hooks)清除頁面快取/" data-wpel-link="internal">WordPress 利用鉤子(Hooks)清除頁面快取</a></li></ul>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/6888/wordpress-%e5%88%a9%e7%94%a8%e9%89%a4%e5%ad%90hooks%e6%b8%85%e9%99%a4%e9%a0%81%e9%9d%a2%e5%bf%ab%e5%8f%96/" data-wpel-link="internal">WordPress 利用鉤子(Hooks)清除頁面快取</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://noter.tw/6888/wordpress-%e5%88%a9%e7%94%a8%e9%89%a4%e5%ad%90hooks%e6%b8%85%e9%99%a4%e9%a0%81%e9%9d%a2%e5%bf%ab%e5%8f%96/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Ubuntu 18.04 安裝 AWStats 來統計網站流量</title>
		<link>https://noter.tw/5967/ubuntu-18-04-%e5%ae%89%e8%a3%9d-awstats-%e4%be%86%e7%b5%b1%e8%a8%88%e7%b6%b2%e7%ab%99%e6%b5%81%e9%87%8f/</link>
					<comments>https://noter.tw/5967/ubuntu-18-04-%e5%ae%89%e8%a3%9d-awstats-%e4%be%86%e7%b5%b1%e8%a8%88%e7%b6%b2%e7%ab%99%e6%b5%81%e9%87%8f/#respond</comments>
		
		<dc:creator><![CDATA[黃小蛙]]></dc:creator>
		<pubDate>Sun, 20 Oct 2019 06:54:45 +0000</pubDate>
				<category><![CDATA[Web Server]]></category>
		<category><![CDATA[網頁後端]]></category>
		<category><![CDATA[網站流量]]></category>
		<category><![CDATA[網站分析]]></category>
		<category><![CDATA[流量統計]]></category>
		<category><![CDATA[cgi]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[教學]]></category>
		<category><![CDATA[2019]]></category>
		<category><![CDATA[分享]]></category>
		<category><![CDATA[AWStats]]></category>
		<category><![CDATA[網站資料統計]]></category>
		<guid isPermaLink="false">https://noter.tw/?p=5967</guid>

					<description><![CDATA[<p>最近接到一個任務，要用 AWStats 統計流量，之前大多使用 Google Analytics，這次要求用 AWStats，很久以前有用過，但從來沒有自己架設過，這篇留個紀錄。 安裝 AWStats&#46;&#46;&#46;</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/5967/ubuntu-18-04-%e5%ae%89%e8%a3%9d-awstats-%e4%be%86%e7%b5%b1%e8%a8%88%e7%b6%b2%e7%ab%99%e6%b5%81%e9%87%8f/" data-wpel-link="internal">Ubuntu 18.04 安裝 AWStats 來統計網站流量</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>最近接到一個任務，要用 AWStats 統計流量，之前大多使用 Google Analytics，這次要求用 AWStats，很久以前有用過，但從來沒有自己架設過，這篇留個紀錄。</p>



<span id="more-5967"></span>



<h2 class="para wp-block-heading" id="install">安裝 AWStats</h2>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">sudo apt-get install awstats libgeo-ipfree-perl libnet-ip-perl</pre>



<p>這邊應該沒有什麼問題，安裝就是了。</p>



<h2 class="para wp-block-heading" id="config">設定要統計的網站</h2>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code"># 把預設的 awstats.conf 複製一份出來改
sudo cp /etc/awstats/awstats.conf /etc/awstats/awstats.noter.tw.conf
# 編輯設定檔
sudo vim /etc/awstats/awstats.noter.tw.conf</pre>



<p>安裝完之後在 /etc/awstats/ 下面會有一個預設設定檔 awstats.conf，直接複製出來改成 awstats.接上你的網址.conf，然後進入編輯。</p>



<p>要修改的部份小蛙直接參考 <a rel="noreferrer noopener nofollow external" aria-label="Awstats流量統計@Alvin Chen Club (在新分頁中開啟)" href="http://www.alvinchen.club/2018/04/14/awstats-%E8%AE%93%E4%BD%A0%E7%9F%A5%E9%81%93%E7%B6%B2%E7%AB%99%E6%B5%81%E9%87%8F%E6%9C%89%E5%A4%9A%E5%B0%91/" target="_blank" data-wpel-link="external" class="wpel-icon-right">Awstats流量統計@Alvin Chen Club<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 的設定</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code"># 要分析的 log 路徑，大概在 50 行左右
LogFile="/var/log/apache2/access.log"

# 記錄類別，維持不變 W = weblog，大概在 62 行
LogType=W

# 記錄格式，這邊小蛙的環境是 Apache2，維持 1 就好，也可以自己設定
# 大概在 123 行
LogFormat=1

# Domain，大概在 154 行
SiteDomain="noter.tw"

# 其他可連到主機的名稱，大概在 169 行
HostAliases="localhost 127.0.0.1"

AllowFullYearView=3
LoadPlugin="tooltips"
LoadPlugin="graphgooglechartapi"
LoadPlugin="geoipfree"</pre>



<p>小蛙大概改了這些東西，好了之後存檔離開。</p>



<h2 class="para wp-block-heading" id="mv">搬移排程檔</h2>



<p>因為剛安裝的時候，會自動在 /etc/cron.d/ 下面建立一個 awstats，打開可以看到裡面已經設定了自動執行的排程，為了避免等一下我們設定統計資料的時候，這個排程造成干擾，因此先把它移動到其他地方。</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code"># 把 awstats 排程檔移動到其他地方放，e.g. /root 目錄下
sudo mv /etc/cron.d/awstats /root
# 把 awstats 裡面原本建立的檔案都先刪掉
sudo rm /var/lib/awstats/*</pre>



<h2 class="para wp-block-heading" id="manual">手動執行</h2>



<p>要知道設定檔能不能正確執行，就先手動跑一次囉！</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">sudo /usr/lib/cgi-bin/awstats.pl -config=noter.tw -update</pre>



<p>這一步小蛙跑超久超久，期間還一直被中斷 &#8230; 因為小蛙總共要跑 5 份統計資料，花了不少時間在這邊測試，後來發現很簡單，只要搭配這篇 <a href="https://noter.tw/3946/ssh-%e9%81%a0%e7%ab%af%e7%99%bb%e5%87%ba%e5%be%8c%e7%b9%bc%e7%ba%8c%e5%9f%b7%e8%a1%8c/" data-wpel-link="internal">SSH 遠端登出後繼續執行</a></p>



<p>感謝同事翻到這篇文章，並且了小蛙跑超久的解決方案</p>



<p class="pre left green">分析速度很慢的問題把 <strong>DNSLookup </strong>改成 <strong>0</strong> 應該就好了<br>官方文件有說 <br><strong>With DNSLookup enabled, log analyze speed is decreased by 40 to 100 times, so use it only if required</strong></p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code"># 使用 screen
screen

# 要執行的指令
sudo /usr/lib/cgi-bin/awstats.pl -config=noter.tw -update

# 離開 screen
ctrl + a, d</pre>



<p>這樣就會背景執行，突然斷線也不用擔心沒跑完，那要怎麼知道有沒有跑完呢？</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code"># 查看所有 screen
screen -ls

# 連到特定 screen (xxxx 是上面 -ls 出現的數字)
screen -r xxxx

# 如果看到 Found xxxx new qualified records. 的字樣，就可以關閉 screen
ctrl + a, k</pre>



<h2 class="para wp-block-heading" id="cron">設定排程檔</h2>



<p>跑完一次統計資料之後，就可以設定排程了，網路上看到的資料都是 10 分鐘跑一次，但是小蛙遇到的 case 跑一次要兩小時 &#8230; 十分鐘跑一次不就會後面卡一堆排程 &#8230; 在這邊花了一兩天測試。</p>



<p>除了跑很久的問題，還遇到指令的問題，例如這台主機已經有被其他人設定過 awstats，但是設定檔有問題造成無法正確執行；原本網站使用的<mark class="yellow">/usr/lib/cgi-bin/awstats.pl -update</mark>無法執行 &#8230;</p>



<p>先把剛剛移去 /root 下面放的 awstats 排程檔案移到 /etc/cron.d 下面放</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code"># 把排程檔移回
sudo mv /root/awstats /etc/cron.d

# 編輯排程檔
sudo vim /etc/cron.d/awstats

# 每天早上六點執行
0 6 * * * * root /usr/lib/cgi-bin/awstats.pl -config=noter.tw -update &gt; /dev/null</pre>



<p>小蛙的狀況如果沒有加上 -config=noter.tw 的話沒有辦法執行 Orz &#8230; 跟別人講的不一樣啊！總之加了好了，就加吧！想知道有沒有成功，可以先把運行時間調到三、五分鐘後，然後等他執行完再來看看有沒有。</p>



<h2 class="para wp-block-heading" id="apache">設定可以透過瀏覽器瀏覽</h2>



<p>編輯 <mark class="yellow">/etc/apache2/site-enabled/awstats.conf</mark> 加入以下設定</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">Alias /awstatsclasses "/usr/share/awstats/lib/"
Alias /awstats-icon/ "/usr/share/awstats/icon/"
Alias /awstatscss "/usr/share/doc/awstats/examples/css"
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
ScriptAlias /awstats/ /usr/lib/cgi-bin/
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch</pre>



<h2 class="para wp-block-heading" id="restart">開啟模組，重啟 apache</h2>



<p>因為 awstats 是用 cgi 跑的，要開啟這個模組，開啟後重啟 apache</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">sudo a2enmod cgi
sudo service apache2 restart</pre>



<p>以上設定都沒問題的話，用瀏覽器開啟</p>



<pre class="left green">http://你的網址/cgi-bin/awstats.pl?config=上面設定的網址
</pre>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/YjQTx1CxHwnf6I4Kp7kGxE7wM3KzbYfBBMyOpUyxvTacRH5YXCfXtRlXYIphdGm5FvLJ-GAjSyvyPlajxMKrbKSpDmgfWww2m97LyUtWUiJeuuW9ynfEJyzAiSRVml5F0w5OvEoWBOMNuCRJvPp7I6WxvU1tAJuThg5f0KjuFGa0g-41QN2G1VAxE5afzMlxNqexHiLXF0U27ZHvn4wVluoaKXDBdjumToDB_t0CkYZTfLjdxgITRM8UYTYvn4113aUM9-E4YwEGFK8PPljEB-4h2BASaw_FVW9M2zmRCYaAu1CqutJacjF88UW-N6b6HaZl-Mn4oMSq6XHOhw56Jlp3DCCnqfZyadtDpR0gVn0YLfnPs2ZSl7lM8Q2qMhBgT7Bag4FizNSba4UizSfZlNyD4uljfvkBPt3CkHR19tsrktDEDIruw4sGjv16z1QXfWr2jW1QxLSQnBIzeQVyFtTrl3xKCLsjUFLP7RW-bfbRi-a9Ipn6RtayE8PLshbJtvjdP84gZsRRlvhecObIwKb7xX4iFYtwWmNnV8H9TVUmM3gVAFQUgPpqfMesroonqFrkg5lEgNXwJUHaJQQx3gyKuFfqnuPUjRHri6avnIIbKerNQhyCjpkj30SWUl2MPHZjy9zDesiMK1Y9xquNmTi8QozroOS2oVaa_3mTGk6ZgSSg-KTLtM5eM5l28ilSfzTR8zTQpnA_21tzu-Z-bRmfTBY6htDpQNgxjZgSgyjZV5pi=w901-h809-no" alt="YjQTx1CxHwnf6I4Kp7kGxE7wM3KzbYfBBMyOpUyxvTacRH5YXCfXtRlXYIphdGm5FvLJ GAjSyvyPlajxMKrbKSpDmgfWww2m97LyUtWUiJeuuW9ynfEJyzAiSRVml5F0w5OvEoWBOMNuCRJvPp7I6WxvU1tAJuThg5f0KjuFGa0g 41QN2G1VAxE5afzMlxNqexHiLXF0U27ZHvn4wVluoaKXDBdjumToDB t0CkYZTfLjdxgITRM8UYTYvn4113aUM9 E4YwEGFK8PPljEB 4h2BASaw FVW9M2zmRCYaAu1CqutJacjF88UW N6b6HaZl Mn4oMSq6XHOhw56Jlp3DCCnqfZyadtDpR0gVn0YLfnPs2ZSl7lM8Q2qMhBgT7Bag4FizNSba4UizSfZlNyD4uljfvkBPt3CkHR19tsrktDEDIruw4sGjv16z1QXfWr2jW1QxLSQnBIzeQVyFtTrl3xKCLsjUFLP7RW bfbRi a9Ipn6RtayE8PLshbJtvjdP84gZsRRlvhecObIwKb7xX4iFYtwWmNnV8H9TVUmM3gVAFQUgPpqfMesroonqFrkg5lEgNXwJUHaJQQx3gyKuFfqnuPUjRHri6avnIIbKerNQhyCjpkj30SWUl2MPHZjy9zDesiMK1Y9xquNmTi8QozroOS2oVaa 3mTGk6ZgSSg KTLtM5eM5l28ilSfzTR8zTQpnA 21tzu Z bRmfTBY6htDpQNgxjZgSgyjZV5pi=w901 h809 no Ubuntu 18.04 安裝 AWStats 來統計網站流量" title="Ubuntu 18.04 安裝 AWStats 來統計網站流量"></figure></div>



<h2 class="para wp-block-heading" id="secure">限制瀏覽</h2>



<p>如果不想讓其他人看到這個頁面的話，就必須要加上這一步限制瀏覽 &#8230; 先透過以下指令建立可登入的使用者帳號及密碼</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">sudo htpasswd -c /etc/apache2/htpasswd 要登入的帳號</pre>



<p>只要在剛剛編輯的 /etc/apache2/site-enabled/awstats.conf 加上</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">&lt;Directory "/usr/lib/cgi-bin/"&gt;
AuthUserFile /etc/apache2/htpasswd
AuthName "Please Enter Your Password"
AuthType Basic
Require valid-user
&lt;/Directory&gt;</pre>



<p>重新啟動 apache 就完成囉！</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/5967/ubuntu-18-04-%e5%ae%89%e8%a3%9d-awstats-%e4%be%86%e7%b5%b1%e8%a8%88%e7%b6%b2%e7%ab%99%e6%b5%81%e9%87%8f/" data-wpel-link="internal">Ubuntu 18.04 安裝 AWStats 來統計網站流量</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://noter.tw/5967/ubuntu-18-04-%e5%ae%89%e8%a3%9d-awstats-%e4%be%86%e7%b5%b1%e8%a8%88%e7%b6%b2%e7%ab%99%e6%b5%81%e9%87%8f/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PHP 串接 AlipayHK 踩坑記錄</title>
		<link>https://noter.tw/5445/php-%e4%b8%b2%e6%8e%a5-alipayhk-%e8%b8%a9%e5%9d%91%e8%a8%98%e9%8c%84/</link>
					<comments>https://noter.tw/5445/php-%e4%b8%b2%e6%8e%a5-alipayhk-%e8%b8%a9%e5%9d%91%e8%a8%98%e9%8c%84/#comments</comments>
		
		<dc:creator><![CDATA[黃小蛙]]></dc:creator>
		<pubDate>Fri, 23 Aug 2019 14:47:58 +0000</pubDate>
				<category><![CDATA[網頁後端]]></category>
		<category><![CDATA[第三方金流]]></category>
		<category><![CDATA[沙箱]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[2019]]></category>
		<category><![CDATA[支付寶]]></category>
		<category><![CDATA[Alipay]]></category>
		<category><![CDATA[AlipayHK]]></category>
		<category><![CDATA[串金流]]></category>
		<guid isPermaLink="false">https://noter.tw/?p=5445</guid>

					<description><![CDATA[<p>最近幫朋友串接 AlipayHK，一開始根本這什麼東西都不知道，只大概知道是串金流、支付寶，串接的過程真是千辛萬苦，這邊記錄下來給有需要的人。 AlipayHK？支付寶？ 根據【教學】Alipay H&#46;&#46;&#46;</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/5445/php-%e4%b8%b2%e6%8e%a5-alipayhk-%e8%b8%a9%e5%9d%91%e8%a8%98%e9%8c%84/" data-wpel-link="internal">PHP 串接 AlipayHK 踩坑記錄</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>最近幫朋友串接 AlipayHK，一開始根本這什麼東西都不知道，只大概知道是串金流、支付寶，串接的過程真是千辛萬苦，這邊記錄下來給有需要的人。</p>



<span id="more-5445"></span>



<h2 class="para wp-block-heading">AlipayHK？支付寶？</h2>



<p>根據<a rel="noreferrer noopener nofollow external" aria-label=" (在新分頁中開啟)" href="https://unwire.hk/2018/07/10/alipay-hk/life-tech/epayment/" target="_blank" data-wpel-link="external" class="wpel-icon-right">【教學】Alipay HK vs 支付寶中國版　常見香港用家 3 大錯誤<span class="wpel-icon wpel-image wpel-icon-6"></span></a>，大概就像是香港版的支付寶，文章內提到</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p> Alipay HK 跟「支付寶」的區別<br>如以上所說 Alipay HK 及支付寶中國版是兩套獨立的系統，就連 APP 也是兩個不同的版本。Alipay HK 是獨立的本地公司，方便為香港度身訂造各種不同的優惠及使用方法。 </p></blockquote>



<p>好，小蛙了解自己要串的東西是「香港版支付寶」後，開始吧！朋友 email 一些「線索」來：</p>



<ul class="my-li bg-darkblue wp-block-list"><li>Alipay Global Portal:  https://globalprod.alipay.com/login/global.htm </li><li>Account Name (PID Email):</li><li>Default Login Password:</li><li>Default Payment Password:</li><li>AlipayHK &#8211; PC2Mobile: https://global.alipay.com/doc/website_hk/intro</li><li>Sandbox Guideline: http://offsite.dev-codpayment.com/sandbox_app/AlipayHK_Sandbox_Account.pdf</li><li>Sandbox APP Download Link: https://offsite.dev-codpayment.com/sandbox_app/sandboxapp.pdf</li></ul>



<p>不知道是支付寶網站改版還是怎樣，很多查到的資料或是連結，都沒有辦法連到正確的網頁，連照著教學文件都面臨找不到選單或位置的窘境，真的是花了很多冤枉時間，估計如果沒有寫下這篇，三天後小蛙又忘記怎麼連到對應的頁面或功能了。</p>



<h2 class="para wp-block-heading">準備密鑰</h2>



<p>首先一開始登入的頁面就怪怪的，從上面連結進去登入完之後，卻轉到<a rel="noreferrer noopener nofollow external" aria-label="這個頁面 (在新分頁中開啟)" href="https://global.alipay.com/ilogin/account_login.htm" target="_blank" data-wpel-link="external" class="wpel-icon-right">這個頁面<span class="wpel-icon wpel-image wpel-icon-6"></span></a>要再登入一次，嗯，也許是新舊首頁，就再登入一次吧！登入完之後，下面有兩個選項，Online Payment / In-store Payment 跟 Global Merchant Portal，好，看完敘述也不是很了解，不過「線上付款」選左邊的看看。</p>



<div class="wp-block-image"><figure class="aligncenter"><a href="https://lh3.googleusercontent.com/czX97PHX8kx__VDxgDc57iKexCHZdrvf_rS73jjf18-n3BqTISSt-9O0iekNmpjGh2GtBrk3X8bbAEzzrV3Q6ISqLpBtRE9TzZSIBJdOFx9CpC1WWEOVskh7n5J3yRtKChASZev6xsR6Odg0wDI1IHhIJa6vggxpVaz5Wn3_G9sP6biV_BaJUiC7o1Z4aHyI4aT_bJZa50IrIU3ZhyaeolwM45hx3nZ0SghdnKWBscOlNGWwPjk6zRekWVC13fpfFtqw8fErnjk8zWm5d3TCWmjtjFznreDtgMgsmP2YhF3tkkzTU8mXQqPNONys4mK9agnxCnTp1QEcNRDfRuQ26pnnMeBFK_jQFvpUszChKNRJyvJP5cGeCOx_I6vj4wv22oJHEMmQAL6hZybwW8BaZjJwg_m0a0f_Ht6SiL-V6beQtFpYJtRO-jCMNZibdP5q-AXhsr78ihPmL_ybs9Q6Lna2YNRcxrc3z8_8ppkeCydSkogsclWLd5iQsdvpSHX34dDC82fru70SXtACAbXu0NbQLvEXrTTmaWTCawQeTyb4vV6KbLRud33RF9OkyrDY6qAEbQ1xSKSZgimobkQs4om3SySIeJO6FYzI1nDOKpd345WLCGfNF2dINbq1EbBVTgnwbsnvS9AGG-v0B4eOzBRFUzNRkUaOMQ1tB2y8XBQIi1bfcW-rZ-u1X_E9xUDt5cNGyl6JEV3BoSLg2Oz0PFBAwQ=w1196-h288-no" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><img decoding="async" src="https://lh3.googleusercontent.com/czX97PHX8kx__VDxgDc57iKexCHZdrvf_rS73jjf18-n3BqTISSt-9O0iekNmpjGh2GtBrk3X8bbAEzzrV3Q6ISqLpBtRE9TzZSIBJdOFx9CpC1WWEOVskh7n5J3yRtKChASZev6xsR6Odg0wDI1IHhIJa6vggxpVaz5Wn3_G9sP6biV_BaJUiC7o1Z4aHyI4aT_bJZa50IrIU3ZhyaeolwM45hx3nZ0SghdnKWBscOlNGWwPjk6zRekWVC13fpfFtqw8fErnjk8zWm5d3TCWmjtjFznreDtgMgsmP2YhF3tkkzTU8mXQqPNONys4mK9agnxCnTp1QEcNRDfRuQ26pnnMeBFK_jQFvpUszChKNRJyvJP5cGeCOx_I6vj4wv22oJHEMmQAL6hZybwW8BaZjJwg_m0a0f_Ht6SiL-V6beQtFpYJtRO-jCMNZibdP5q-AXhsr78ihPmL_ybs9Q6Lna2YNRcxrc3z8_8ppkeCydSkogsclWLd5iQsdvpSHX34dDC82fru70SXtACAbXu0NbQLvEXrTTmaWTCawQeTyb4vV6KbLRud33RF9OkyrDY6qAEbQ1xSKSZgimobkQs4om3SySIeJO6FYzI1nDOKpd345WLCGfNF2dINbq1EbBVTgnwbsnvS9AGG-v0B4eOzBRFUzNRkUaOMQ1tB2y8XBQIi1bfcW-rZ-u1X_E9xUDt5cNGyl6JEV3BoSLg2Oz0PFBAwQ=w1196-h288-no" alt="czX97PHX8kx VDxgDc57iKexCHZdrvf rS73jjf18 n3BqTISSt 9O0iekNmpjGh2GtBrk3X8bbAEzzrV3Q6ISqLpBtRE9TzZSIBJdOFx9CpC1WWEOVskh7n5J3yRtKChASZev6xsR6Odg0wDI1IHhIJa6vggxpVaz5Wn3 G9sP6biV BaJUiC7o1Z4aHyI4aT bJZa50IrIU3ZhyaeolwM45hx3nZ0SghdnKWBscOlNGWwPjk6zRekWVC13fpfFtqw8fErnjk8zWm5d3TCWmjtjFznreDtgMgsmP2YhF3tkkzTU8mXQqPNONys4mK9agnxCnTp1QEcNRDfRuQ26pnnMeBFK jQFvpUszChKNRJyvJP5cGeCOx I6vj4wv22oJHEMmQAL6hZybwW8BaZjJwg m0a0f Ht6SiL V6beQtFpYJtRO jCMNZibdP5q AXhsr78ihPmL ybs9Q6Lna2YNRcxrc3z8 8ppkeCydSkogsclWLd5iQsdvpSHX34dDC82fru70SXtACAbXu0NbQLvEXrTTmaWTCawQeTyb4vV6KbLRud33RF9OkyrDY6qAEbQ1xSKSZgimobkQs4om3SySIeJO6FYzI1nDOKpd345WLCGfNF2dINbq1EbBVTgnwbsnvS9AGG v0B4eOzBRFUzNRkUaOMQ1tB2y8XBQIi1bfcW rZ u1X E9xUDt5cNGyl6JEV3BoSLg2Oz0PFBAwQ=w1196 h288 no PHP 串接 AlipayHK 踩坑記錄" title="PHP 串接 AlipayHK 踩坑記錄"></a></figure></div>



<p>AlipayHK 簽署的方式有兩種</p>



<ul class="my-li bg-darkblue wp-block-list"><li>使用 md5 </li><li>使用 RSA / DSA</li></ul>



<p>後台介面就有 md5 的私鑰，小蛙直接使用這種方式，比較方便。進到管理頁面，切換到「My Technical Service」後右邊有「See Partner ID(PID)」跟「See Key」，先把 PID 跟 Key 記起來，等一下串接會用到。 </p>



<div class="wp-block-image"><figure class="aligncenter"><a class="" href="https://lh3.googleusercontent.com/oBX3nQoz-lsn-nO6zOExqEB8BMi3WCXoOtKPQryobihacmr6-KlyCoQirK71Kx5HLNVo8-e8qW-vsgIMD3PKZK_36a5uCi8ffxcHLvJSTOwS_il73Z8F8TX7QbkPH8CeYEN8X9K5SCPxRjgVp8n5NEu5ZkHjkLK61H4qMJySeVbV3kSUw_PoR8xQbfBt_EKxm2oiZspjvt2AssXUnd_WFe3Ec-n9Ic6hH-MrJujmZ2TlO5_umUV9eqDOXzlIBMJjwD68soPnJ8dur9ayqRpSpaLWT1CkqJXE6YC4H7BBwtXFQsfDu-g_dez4t4dafQsKvA46HOtEGCR7niWw5w3cS5TCuI4N1re5LfwarPThNUIy2ZDzHT8U0TYMwsdeGmncMWX03DRJ4h0yK3VOkFQHjnzAIgd07extjcO1he-3ewwgl3xqntsmij8Y_j6pjgUIsFmG3GJSYVCONCA_hT66sMpiyzhYqUplMutxuPceUA_aLT8Ffk2FRDjWTNkw3oDnejODKwomwV431yrTHZYPNbv-dykOT6uoB5Hvs-vcCsNkSzr6GyjXRzwRHTwRpKVVl0kccFyoTTrYH111JZEStkzR07QTgA9vApBLksGO91NgI8VuBFHU-JNmyHNBSfeIL4iJ7j08Xu_12SyBznazZkVIj8TtWr5EqjSRZC0UKOiQj-R8Y82wQNw7O8dffBhTs9yJ06qab91ZuhI4zRU5gj1PtA=w701-h376-no" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><img decoding="async" src="https://lh3.googleusercontent.com/oBX3nQoz-lsn-nO6zOExqEB8BMi3WCXoOtKPQryobihacmr6-KlyCoQirK71Kx5HLNVo8-e8qW-vsgIMD3PKZK_36a5uCi8ffxcHLvJSTOwS_il73Z8F8TX7QbkPH8CeYEN8X9K5SCPxRjgVp8n5NEu5ZkHjkLK61H4qMJySeVbV3kSUw_PoR8xQbfBt_EKxm2oiZspjvt2AssXUnd_WFe3Ec-n9Ic6hH-MrJujmZ2TlO5_umUV9eqDOXzlIBMJjwD68soPnJ8dur9ayqRpSpaLWT1CkqJXE6YC4H7BBwtXFQsfDu-g_dez4t4dafQsKvA46HOtEGCR7niWw5w3cS5TCuI4N1re5LfwarPThNUIy2ZDzHT8U0TYMwsdeGmncMWX03DRJ4h0yK3VOkFQHjnzAIgd07extjcO1he-3ewwgl3xqntsmij8Y_j6pjgUIsFmG3GJSYVCONCA_hT66sMpiyzhYqUplMutxuPceUA_aLT8Ffk2FRDjWTNkw3oDnejODKwomwV431yrTHZYPNbv-dykOT6uoB5Hvs-vcCsNkSzr6GyjXRzwRHTwRpKVVl0kccFyoTTrYH111JZEStkzR07QTgA9vApBLksGO91NgI8VuBFHU-JNmyHNBSfeIL4iJ7j08Xu_12SyBznazZkVIj8TtWr5EqjSRZC0UKOiQj-R8Y82wQNw7O8dffBhTs9yJ06qab91ZuhI4zRU5gj1PtA=w701-h376-no" alt="oBX3nQoz lsn nO6zOExqEB8BMi3WCXoOtKPQryobihacmr6 KlyCoQirK71Kx5HLNVo8 e8qW vsgIMD3PKZK 36a5uCi8ffxcHLvJSTOwS il73Z8F8TX7QbkPH8CeYEN8X9K5SCPxRjgVp8n5NEu5ZkHjkLK61H4qMJySeVbV3kSUw PoR8xQbfBt EKxm2oiZspjvt2AssXUnd WFe3Ec n9Ic6hH MrJujmZ2TlO5 umUV9eqDOXzlIBMJjwD68soPnJ8dur9ayqRpSpaLWT1CkqJXE6YC4H7BBwtXFQsfDu g dez4t4dafQsKvA46HOtEGCR7niWw5w3cS5TCuI4N1re5LfwarPThNUIy2ZDzHT8U0TYMwsdeGmncMWX03DRJ4h0yK3VOkFQHjnzAIgd07extjcO1he 3ewwgl3xqntsmij8Y j6pjgUIsFmG3GJSYVCONCA hT66sMpiyzhYqUplMutxuPceUA aLT8Ffk2FRDjWTNkw3oDnejODKwomwV431yrTHZYPNbv dykOT6uoB5Hvs vcCsNkSzr6GyjXRzwRHTwRpKVVl0kccFyoTTrYH111JZEStkzR07QTgA9vApBLksGO91NgI8VuBFHU JNmyHNBSfeIL4iJ7j08Xu 12SyBznazZkVIj8TtWr5EqjSRZC0UKOiQj R8Y82wQNw7O8dffBhTs9yJ06qab91ZuhI4zRU5gj1PtA=w701 h376 no PHP 串接 AlipayHK 踩坑記錄" title="PHP 串接 AlipayHK 踩坑記錄"></a></figure></div>



<p>小蛙從來沒有用過支付寶及其相關產品，<a rel="noreferrer noopener nofollow external" aria-label="這個頁面 (在新分頁中開啟)" href="https://global.alipay.com/open/product-detail.htm?bizCode=OPEN_ONLINE_PAYMENT-Website" target="_blank" data-wpel-link="external" class="wpel-icon-right">這個頁面<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 下有介紹整個流程，小蛙看完才對 AlipayHK 比較有概念，到 <a rel="noreferrer noopener nofollow external" aria-label="這邊 (在新分頁中開啟)" href="https://global.alipay.com/doc/web/intro" target="_blank" data-wpel-link="external" class="wpel-icon-right">這邊<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 看說明文件吧！今天才發現有中文文檔！！！</p>



<h2 class="para wp-block-heading">下載範例</h2>



<p>小蛙是要在 PHP 環境下串接「支付功能」，到這邊<a rel="noreferrer noopener nofollow external" aria-label="下載 PHP 範例 (在新分頁中開啟)" href="https://global.alipay.com/doc/web_cn/demo" target="_blank" data-wpel-link="external" class="wpel-icon-right">下載 PHP 範例<span class="wpel-icon wpel-image wpel-icon-6"></span></a>。解壓縮之後裡面有一些比較重要的檔案：</p>



<pre class="left green">alipay.config.php</pre>



<p>這個檔案主要是環境變數上的設定，等等要修改 partner、key、notify_url 這些變數。</p>



<pre class="left green">alipayapi.php</pre>



<p>這支是搭配 index.php 送出的表單接到參數來產生 QRCode，因為小蛙付款時不需要讓使用者輸入 index.php 的內容，因此將這些的功能拉一些出來寫成 function，如此一來就可以在要付款的地方直接設定好固定及要接的參數。</p>



<pre class="left green">notify_url.php</pre>



<p>最後一支是用來接收付款成功之後的 callback，在這邊設定付款完成之後要做對應的動作。</p>



<p>小蛙改完上面這些東西之後，其實整個範例還是沒辦法動的，這後面會再提，一開始要測試範例當然沒辦法用正式的 key 及 partner id，接下來要先建立沙箱帳號，透過沙箱環境來測試付款。</p>



<h2 class="para wp-block-heading">使用沙箱測試</h2>



<p>iOS 或 Android 到 <a href="https://offsite.dev-codpayment.com/sandbox_app/sandboxapp.pdf" target="_blank" rel="noreferrer noopener nofollow external" aria-label="這邊 (在新分頁中開啟)" data-wpel-link="external" class="wpel-icon-right">這邊<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 下載沙箱 app。 </p>



<p>到 <a rel="noreferrer noopener nofollow external" aria-label="Alipay Sandbox Portal (在新分頁中開啟)" href="https://isandbox.alipaydev.com/user/intlAccountDetails.htm" target="_blank" data-wpel-link="external" class="wpel-icon-right">Alipay Sandbox Portal<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 去新建沙箱測試帳號，小蛙這次要串的是 AlipayHK，要到 Hong Kong Wallet 下面建一個 Merchant 跟一個 Buyer 來測試。</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/H7u4zCZIT1QjH3ThzsWNF4GMry85QInN_gF-T7JWtEi-Em57mHgh2wMnezZ-2XaSrd_BgzbKwX8CPp5vA6DlimhpvIw1EjY5UiG75g4rChPz1hhuk_ZN8GAXmEg967WKB-3pbfzmqBRVccFo8n2wDDzkpQMZgbqjhJE70WSfsWStuGWwCkNJTYEaQ0y4DsONEe8gozGtl35M953me8jk1Xodx5UdmKeVhKaB4rmmuNJzbuqdEIDSHbxXqHBU15s_F10cPU_ixk9qPM64mxcmlt6Sx9J_gTniU4ATthOPceotrey-D-PmSfJ8o3ytDJD2FmBPV_Rxdk1qWkZFK58r6FjFTEN4KhaAr5sFYds7nHcxjTmk-NhT55GLZTtJ4BTfSgHEWslgAmzPnkCy1FxfEoC0GZPD4VxBTdtpt46zuB9r0xHZYzH7eQhQ3cTMEi-kCeY93txY6r23fXcojkwynNDYVmqO9RmlJw2ajqyoLT88OTKvhQxTp4bpE9Q2i_k-VjETiwTeBebAwMtJQOse6u5A5tA9wAwZXnRKzGo256tT8iXnllvuxRF9NXFkf4nysR-dDJWFTmY_ZDLBz9M68prNBcQl0wYyHyAQO0asCMqscIvxCkSvgk8e6upO4jkx5lOfYY8EDRMHwn71vJYxRIY-kE91Lov82ZdLHtDgFKCV2wZK1c2hUe5ayEWJupvhwFnTqPTNBUqlG8xQXkP3BCZVky18BAtp7ZTF1ja1-WbaLMx0=w214-h332-no" alt="H7u4zCZIT1QjH3ThzsWNF4GMry85QInN gF T7JWtEi Em57mHgh2wMnezZ 2XaSrd BgzbKwX8CPp5vA6DlimhpvIw1EjY5UiG75g4rChPz1hhuk ZN8GAXmEg967WKB 3pbfzmqBRVccFo8n2wDDzkpQMZgbqjhJE70WSfsWStuGWwCkNJTYEaQ0y4DsONEe8gozGtl35M953me8jk1Xodx5UdmKeVhKaB4rmmuNJzbuqdEIDSHbxXqHBU15s F10cPU ixk9qPM64mxcmlt6Sx9J gTniU4ATthOPceotrey D PmSfJ8o3ytDJD2FmBPV Rxdk1qWkZFK58r6FjFTEN4KhaAr5sFYds7nHcxjTmk NhT55GLZTtJ4BTfSgHEWslgAmzPnkCy1FxfEoC0GZPD4VxBTdtpt46zuB9r0xHZYzH7eQhQ3cTMEi kCeY93txY6r23fXcojkwynNDYVmqO9RmlJw2ajqyoLT88OTKvhQxTp4bpE9Q2i k VjETiwTeBebAwMtJQOse6u5A5tA9wAwZXnRKzGo256tT8iXnllvuxRF9NXFkf4nysR dDJWFTmY ZDLBz9M68prNBcQl0wYyHyAQO0asCMqscIvxCkSvgk8e6upO4jkx5lOfYY8EDRMHwn71vJYxRIY kE91Lov82ZdLHtDgFKCV2wZK1c2hUe5ayEWJupvhwFnTqPTNBUqlG8xQXkP3BCZVky18BAtp7ZTF1ja1 WbaLMx0=w214 h332 no PHP 串接 AlipayHK 踩坑記錄" title="PHP 串接 AlipayHK 踩坑記錄"></figure></div>



<p>建好之後記下 Merchant 右邊的 Merchant UID\/PID 及 MD5 Signature key，記下 Buyer 的 Buyer account 及 Login password。</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/vBkCxawrgALVGEA_73AzM9Cncde5jyUbZmh5n28zTtsQ00xRR9rt28QapLTjFiLMz37PGvWwD338vo2DnaiE5X9lz49Sc0EKR-zuX9QTcxcr2NGmzyL2bIHH63de0TxjOIGZXpyya2qNmVutFGoqE9urue4xuiTmx_PVKBqRfg3k3uY-5bOw8h0EHevRkw_u-_QWKI7NjtobOpaHs2A6vf7u-z4bHnXtGovChr31nY1ZuTtYTfGY_IlHKYi3x4pOE_Ida_R5552DYT8thzf7H-eGcp6JSj5M19XK9rS61jQgBEei8scbUfSOHFBc0NOsBHcCgSw0YAD_Az0S_YiLxkLpm3RPcfwxwNtoSvEnzh_3tvxGCXoA3-lfaSKmrdjSWqBovKKYYQ-VgitqM9ECniWr_nBNGyXaUMjWb3GNLSXyU9sYomBINILUxc9v6gz7fj4_6_6qWp0B5Y0XlWOVyN_eN6lFfj3hgNjopK9btvwrdKIo-eywF5yGw5V3iQ5YssVUjSjeCraqUcw0WYCBXTv6BTFN6yGc92Bm65T5-9XHOdD9KAN1rLtUat7mhYTu0tZUNGRtDHJC2_cqBCB6Ia304UKmZGtYMoqQgxBdhA9qUdDrtaUnYwUUshObs7puFSdZwwi6OUFV6nzxIvPqCFqHoQHDzB_d6NAHCGOBB8LbfkqT2w0jFwop5l58xODY0_s1ASnwYfeKeAGtOctI5pD6B7I8k0d_C-1XBrnf3s5X7KW3=w499-h443-no" alt="vBkCxawrgALVGEA 73AzM9Cncde5jyUbZmh5n28zTtsQ00xRR9rt28QapLTjFiLMz37PGvWwD338vo2DnaiE5X9lz49Sc0EKR zuX9QTcxcr2NGmzyL2bIHH63de0TxjOIGZXpyya2qNmVutFGoqE9urue4xuiTmx PVKBqRfg3k3uY 5bOw8h0EHevRkw u QWKI7NjtobOpaHs2A6vf7u z4bHnXtGovChr31nY1ZuTtYTfGY IlHKYi3x4pOE Ida R5552DYT8thzf7H eGcp6JSj5M19XK9rS61jQgBEei8scbUfSOHFBc0NOsBHcCgSw0YAD Az0S YiLxkLpm3RPcfwxwNtoSvEnzh 3tvxGCXoA3 lfaSKmrdjSWqBovKKYYQ VgitqM9ECniWr nBNGyXaUMjWb3GNLSXyU9sYomBINILUxc9v6gz7fj4 6 6qWp0B5Y0XlWOVyN eN6lFfj3hgNjopK9btvwrdKIo eywF5yGw5V3iQ5YssVUjSjeCraqUcw0WYCBXTv6BTFN6yGc92Bm65T5 9XHOdD9KAN1rLtUat7mhYTu0tZUNGRtDHJC2 cqBCB6Ia304UKmZGtYMoqQgxBdhA9qUdDrtaUnYwUUshObs7puFSdZwwi6OUFV6nzxIvPqCFqHoQHDzB d6NAHCGOBB8LbfkqT2w0jFwop5l58xODY0 s1ASnwYfeKeAGtOctI5pD6B7I8k0d C 1XBrnf3s5X7KW3=w499 h443 no PHP 串接 AlipayHK 踩坑記錄" title="PHP 串接 AlipayHK 踩坑記錄"></figure></div>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/IY-w-9e7UvaY6qZDbC_INKgC1LHQLB_N5_wse8ePHZ34uVaqvRp2BWUD3w4aYhsCOwokE_k57PurT4ermxlOq-g0Up70Kcp7vx8DP3ERiMSM5gjP16lpR-r7Nuv9WfCfLp-bncb8_IQre5rl-G5xpxwA5bNfGLlqfd-oY5Wl08OhL2ApxbmleEjm0BnUYXLw0fpUTQRgI7qgWFuxSreTDrog1jebMgZNqCx4pYOyIrGqIyM-bDK1H2xeW9dzmLf8nMU_I8VxTnlRmC4N6xAsY4r9XkPo_N9zQlaFM_bkPlbDKA6Ar5M8jrRSuNt5BiNXIrsLB3qRopLC8upA1KeTCuHV__YiWblgWpOk1diEJM6eIbpEPfS9-u466hJ7yLAzZCkNAc90yAwMno6b2DEGDgWKDCh0ACew4Z4UyNPCUBmGtIqiRtsrqcAMy9NGoVnU1PNCEox6ZStnfSKkhus8r0DSSxnCT8aWObeBgdyDjX8jjZ1BpZaD1Kpc0v30SeqnSgMbO0xyLX_EAg6DzwvNBxo59N7GQLKG20mnhtBClw1Hpnj-W-jDi_f8YUf4FxejuQSvcskprcGI2M6wwzz7J0qsS_2p62lWI-lWZZEt9a3vmPonwIQQ_DoJGWQd9XGJu6nuBuKPqjtPHJXwVQzGg9s6aVHJFVpKyxVutJcd2KW0TdiJiWUL483eJ629HMZAGjioor8cEXLLtRaEokHbejLu-CtjB0-bDYymDXRxWWI3APk-=w433-h325-no" alt="IY w 9e7UvaY6qZDbC INKgC1LHQLB N5 wse8ePHZ34uVaqvRp2BWUD3w4aYhsCOwokE k57PurT4ermxlOq g0Up70Kcp7vx8DP3ERiMSM5gjP16lpR r7Nuv9WfCfLp bncb8 IQre5rl G5xpxwA5bNfGLlqfd oY5Wl08OhL2ApxbmleEjm0BnUYXLw0fpUTQRgI7qgWFuxSreTDrog1jebMgZNqCx4pYOyIrGqIyM bDK1H2xeW9dzmLf8nMU I8VxTnlRmC4N6xAsY4r9XkPo N9zQlaFM bkPlbDKA6Ar5M8jrRSuNt5BiNXIrsLB3qRopLC8upA1KeTCuHV YiWblgWpOk1diEJM6eIbpEPfS9 u466hJ7yLAzZCkNAc90yAwMno6b2DEGDgWKDCh0ACew4Z4UyNPCUBmGtIqiRtsrqcAMy9NGoVnU1PNCEox6ZStnfSKkhus8r0DSSxnCT8aWObeBgdyDjX8jjZ1BpZaD1Kpc0v30SeqnSgMbO0xyLX EAg6DzwvNBxo59N7GQLKG20mnhtBClw1Hpnj W jDi f8YUf4FxejuQSvcskprcGI2M6wwzz7J0qsS 2p62lWI lWZZEt9a3vmPonwIQQ DoJGWQd9XGJu6nuBuKPqjtPHJXwVQzGg9s6aVHJFVpKyxVutJcd2KW0TdiJiWUL483eJ629HMZAGjioor8cEXLLtRaEokHbejLu CtjB0 bDYymDXRxWWI3APk =w433 h325 no PHP 串接 AlipayHK 踩坑記錄" title="PHP 串接 AlipayHK 踩坑記錄"></figure></div>



<h2 class="para wp-block-heading">沙箱測試</h2>



<p>把上面記下來的 Merchant UID\/PID 及 MD5 Signature key 分別填入 alipay.config.php，以及修改 notify_url 位址，一定要確定這個頁面可以存取，付款完成之後 AlipayHK 會送一個 POST 過來這裡，如果無法存取會收不到付款完成通知。</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">$alipay_config['partner']    = 'PID 填到這裡';
$alipay_config['key']        = 'MD5 Signature key 填到這裡';
$alipay_config['notify_url'] = 'https://xxxx.xxx/notify_url.php';</pre>



<p>修改完成之後連到付款首頁，https://your_domain/alipayHK/index.php，可以看到一個表單，這表單是讓人測試用的，可以測試不同參數產生的 QRCode，送出之後會送到 alipayapi.php。</p>



<h2 class="para wp-block-heading">等等，跳出一堆錯誤訊息</h2>



<p>小蛙記得有兩三個問題，這範例程式有點不是很嚴謹 &#8230;</p>



<ul class="my-li bg-darkblue wp-block-list"><li>alipayapi.php 這支的第 73 行就出現一個奇怪符號，刪掉就好了。 </li><li>notify_url.php 這支的第 23 行，註解符號沒有放好。</li></ul>



<p>還有一個最後再說吧～最後一個也是小蛙花了最多時間去找出來的，修正完上面兩個錯誤後，應該這個範例程式就能運行了。</p>



<h2 class="para wp-block-heading">測試流程</h2>



<p>連到 https://your_domain/alipayHK/index.php 會看到以下畫面</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/gJs9q5gOdKzjqNglw41sdBTLZ9s7vUVqCR0WgHNv-xJjFOtXhosfkxJVTKG3_et8OPGCuvWfq3eco-pV2ImqU31FV7b9JVwWcYlnjUaFMdmQCGM78OiHWbdZl7KLJTSu9FGuhQBxtIwOfkHYXSgbKBx-dSg4q9vH9HnaKzJHZ1A7p5tHfAB1MqxLXEKRJgtrH3iA1yjolctFswrDJqsGO4-SADwxXwMMMl8LYzhZU-HKxMuzXwtJPq-1ReknYUWT2tr5kNiF7qpRjcAk3glWDh2P5TBZJRjH5SUzUc0EUic1j-orhH8t8lWhdARCq4WsWUQ7B0Uycv_tRefEtDWVQw7lGtDvImuSur3hl3ETF2OrZ_XFdhYFJ-VV4txPPG4vtylJhzlz-pRF5uozgBY3ec5_SvnryGlIlAgAHQfLLGiV_sPM1iBDlcdtSM9BVcWGp_k9Dy3mF4-uVhM_bNCUCq5RJCU7GHut89Pj3waQ4FEHpdfd1b4r3SlhPw-fJZTqqgUZHgQHEFvA2pmzXpTIqjARzPC0BunK6V7cGtpCy1K3AMNEr51UsiCEQ4n3TArvnwbWU-KCsSqpFQM4UnYbxSwBWZuE2OtCmsZVdbieI8n2QCCPQMCrDm9BOEe0FNZim6O9f248QkP751WuLGImTgxjN7ARgcP4ZlCsewkODci-WV_Z9-xsOVcRoUjVxXp2iiTjeht8fXz3_s4RRUdEwrh6EX5zz3_H16rVcdUye1NDSQ9N=w825-h999-no" alt="gJs9q5gOdKzjqNglw41sdBTLZ9s7vUVqCR0WgHNv xJjFOtXhosfkxJVTKG3 et8OPGCuvWfq3eco pV2ImqU31FV7b9JVwWcYlnjUaFMdmQCGM78OiHWbdZl7KLJTSu9FGuhQBxtIwOfkHYXSgbKBx dSg4q9vH9HnaKzJHZ1A7p5tHfAB1MqxLXEKRJgtrH3iA1yjolctFswrDJqsGO4 SADwxXwMMMl8LYzhZU HKxMuzXwtJPq 1ReknYUWT2tr5kNiF7qpRjcAk3glWDh2P5TBZJRjH5SUzUc0EUic1j orhH8t8lWhdARCq4WsWUQ7B0Uycv tRefEtDWVQw7lGtDvImuSur3hl3ETF2OrZ XFdhYFJ VV4txPPG4vtylJhzlz pRF5uozgBY3ec5 SvnryGlIlAgAHQfLLGiV sPM1iBDlcdtSM9BVcWGp k9Dy3mF4 uVhM bNCUCq5RJCU7GHut89Pj3waQ4FEHpdfd1b4r3SlhPw fJZTqqgUZHgQHEFvA2pmzXpTIqjARzPC0BunK6V7cGtpCy1K3AMNEr51UsiCEQ4n3TArvnwbWU KCsSqpFQM4UnYbxSwBWZuE2OtCmsZVdbieI8n2QCCPQMCrDm9BOEe0FNZim6O9f248QkP751WuLGImTgxjN7ARgcP4ZlCsewkODci WV Z9 xsOVcRoUjVxXp2iiTjeht8fXz3 s4RRUdEwrh6EX5zz3 H16rVcdUye1NDSQ9N=w825 h999 no PHP 串接 AlipayHK 踩坑記錄" title="PHP 串接 AlipayHK 踩坑記錄"></figure></div>



<p>直接按 Play 後會產生 QRCode</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://lh3.googleusercontent.com/DCmiHnQIp2jzdwWtcmZI-cnrjPodRBxaRbEn2LAq-bYH6UQVepA7vIGl77oxQ734rbGXSFswRZMzwdV2Z8CQvKucHpxSHJ4GE_NbnHvhV2nXxbTTHH9VuJYDOZyyqcPGkdeTS0ot1GZRK3D4cS-800xWUONbBjbh1IXw7AsLN0COoyvlBY_WUJuz9Xa2nD4Wk9WPenbgTch9ZdHnON3hmAZh5yeRiCQ_MSGldEKf42gUx3dGu8533b8kzNZ2njyz8WduUjDs5jZsv5Vw3zgMSi6X6ll-IA2wx8S9JjGYeFAWz6iZ3V6WlIC0zDiXYJmobeeVMjyJuZs_kT15YkKldEDcQklIOruVwpUYH58Hk8gKqOsEKud0QvXSjn-Nhr6D5H_e_XF16Wzhl-hLWepu6FDVZeVnNqlZksH7EC_dWnM9Udy734JSDuge3PEw07J4V5GK7SxAVR7TuBu1wgpJtEzL4fVXYt8tFblRLyo5w-IZ-HwOtqBGHJ5cvirm7cqPyNK1AGa4fR27vDPTZQTSUgTI2O9MSQQzpsfSGO3Dh7DDqv4MLYGYaX-jOFF5zF8BoyzwDwHMUqdMBMSejslfEIlIqlj1EV6FzwmdaAPWEdDQmwesJiLnBVQ3yE6IgOxFvffprxdfaQO3lDIHL7Q9E8_D32qByw-WBxOukKUrB1s2hNXJNSDpDsXa0pRdQZPSyNYMC8VyJ-GCTFVb5MMu1BoNmKjOo2vRXqI-DOKOhvgtyiiH=s201-no" alt="DCmiHnQIp2jzdwWtcmZI cnrjPodRBxaRbEn2LAq bYH6UQVepA7vIGl77oxQ734rbGXSFswRZMzwdV2Z8CQvKucHpxSHJ4GE NbnHvhV2nXxbTTHH9VuJYDOZyyqcPGkdeTS0ot1GZRK3D4cS 800xWUONbBjbh1IXw7AsLN0COoyvlBY WUJuz9Xa2nD4Wk9WPenbgTch9ZdHnON3hmAZh5yeRiCQ MSGldEKf42gUx3dGu8533b8kzNZ2njyz8WduUjDs5jZsv5Vw3zgMSi6X6ll IA2wx8S9JjGYeFAWz6iZ3V6WlIC0zDiXYJmobeeVMjyJuZs kT15YkKldEDcQklIOruVwpUYH58Hk8gKqOsEKud0QvXSjn Nhr6D5H e XF16Wzhl hLWepu6FDVZeVnNqlZksH7EC dWnM9Udy734JSDuge3PEw07J4V5GK7SxAVR7TuBu1wgpJtEzL4fVXYt8tFblRLyo5w IZ HwOtqBGHJ5cvirm7cqPyNK1AGa4fR27vDPTZQTSUgTI2O9MSQQzpsfSGO3Dh7DDqv4MLYGYaX jOFF5zF8BoyzwDwHMUqdMBMSejslfEIlIqlj1EV6FzwmdaAPWEdDQmwesJiLnBVQ3yE6IgOxFvffprxdfaQO3lDIHL7Q9E8 D32qByw WBxOukKUrB1s2hNXJNSDpDsXa0pRdQZPSyNYMC8VyJ GCTFVb5MMu1BoNmKjOo2vRXqI DOKOhvgtyiiH=s201 no PHP 串接 AlipayHK 踩坑記錄" title="PHP 串接 AlipayHK 踩坑記錄"></figure></div>



<p>用<a rel="noreferrer noopener nofollow external" aria-label="沙箱 app (在新分頁中開啟)" href="https://offsite.dev-codpayment.com/sandbox_app/sandboxapp.pdf" target="_blank" data-wpel-link="external" class="wpel-icon-right">沙箱 app<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 掃描上面的 QRCode，在用上面的 Buyer account 跟 Login password 登入進行沙箱付款，付款完成之後 AlipayHK 會 POST 資料到上面的 notify_url.php，接著做後續處理。</p>



<h2 class="para wp-block-heading">付款完成後處理</h2>



<p>最後一個動作就是讓 Sever 知道付款完成了，AlipayHK 發送一個 POST 到 notify_url.php，小蛙卡在不管怎樣回來的結果都是「驗證錯誤」，小蛙試了很久，也看了很多官方文件、其他網友分享的過程，都沒有找到解法。</p>



<p>然後小蛙就一步一步 trace code，發現通通都過了，驗證方法都一樣卻出來不一樣的東西，最後發現是這行在搞鬼 &#8230; 真不知道怎麼說，AlipayHK 都好心放出範例檔了，卻如此不嚴謹 &#8230; （也可能是小蛙自己太笨，說不定只有小蛙一個人卡在這）</p>



<p>到 alipay.config.php 裡面找到</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">$alipay_config['cacert']    = getcwd().'\\cacert.pem'; </pre>



<p>改成</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">$alipay_config['cacert']    = getcwd().'/cacert.pem';</pre>



<p>打完收工～</p>



<p><strong>串金流系列：</strong></p>



<ul class="my-li bg-darkblue wp-block-list"><li><a href="https://noter.tw/5445/php-%e4%b8%b2%e6%8e%a5-alipayhk-%e8%b8%a9%e5%9d%91%e8%a8%98%e9%8c%84/" data-wpel-link="internal">PHP 串接 AlipayHK 踩坑記錄</a></li><li><a href="https://noter.tw/5539/ios-untrusted-enterprise-developer%ef%bc%8d%e4%b8%b2%e6%8e%a5-alipayhk-%e6%b2%99%e7%ae%b1%e6%b8%ac%e8%a9%a6%e5%95%8f%e9%a1%8c/" data-wpel-link="internal">iOS: Untrusted Enterprise Developer－串接 AlipayHK 沙箱測試問題</a></li></ul>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/5445/php-%e4%b8%b2%e6%8e%a5-alipayhk-%e8%b8%a9%e5%9d%91%e8%a8%98%e9%8c%84/" data-wpel-link="internal">PHP 串接 AlipayHK 踩坑記錄</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://noter.tw/5445/php-%e4%b8%b2%e6%8e%a5-alipayhk-%e8%b8%a9%e5%9d%91%e8%a8%98%e9%8c%84/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>PHP：UTF-8 跟 Big5 的糾結</title>
		<link>https://noter.tw/109/php%ef%bc%9autf-8-%e8%b7%9f-big5-%e7%9a%84%e7%b3%be%e7%b5%90/</link>
					<comments>https://noter.tw/109/php%ef%bc%9autf-8-%e8%b7%9f-big5-%e7%9a%84%e7%b3%be%e7%b5%90/#respond</comments>
		
		<dc:creator><![CDATA[黃小蛙]]></dc:creator>
		<pubDate>Mon, 17 Sep 2018 14:39:51 +0000</pubDate>
				<category><![CDATA[網頁後端]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[big5]]></category>
		<category><![CDATA[utf-8]]></category>
		<category><![CDATA[iconv]]></category>
		<category><![CDATA[亂碼]]></category>
		<category><![CDATA[rawurlencode]]></category>
		<category><![CDATA[encodeURIComponent]]></category>
		<guid isPermaLink="false">https://noter.tw/?p=109</guid>

					<description><![CDATA[<p>今天在公司處理到一個編碼的問題，由於有許多歷史包袱都是以 big5 下去開發，短時間內沒有辦法把所有系統轉換成 utf-8 的情況下，就經常要面對編碼轉換的問題，這邊記錄一下今天遇到問題的解法。 有個&#46;&#46;&#46;</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/109/php%ef%bc%9autf-8-%e8%b7%9f-big5-%e7%9a%84%e7%b3%be%e7%b5%90/" data-wpel-link="internal">PHP：UTF-8 跟 Big5 的糾結</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>今天在公司處理到一個編碼的問題，由於有許多歷史包袱都是以 big5 下去開發，短時間內沒有辦法把所有系統轉換成 utf-8 的情況下，就經常要面對編碼轉換的問題，這邊記錄一下今天遇到問題的解法。</p>



<span id="more-109"></span>



<p>有個新的系統 B 必須要串接舊系統 A，B 是 utf-8 環境，A 是 big5 環境，目前設計是 B 透過 AJAX 來讀取 A 的資料，A 這邊吃的是經過 encodeURIComponent 處理過的資料，例如：「你好」 -&gt;&nbsp;「%A7%41%A6%6E」，前情提要到這邊。</p>



<p>小蛙的笨腦袋就開始想怎麼處理：</p>



<ul class="my-li bg-darkblue wp-block-list"><li>該頁面拿到的資料都是 UTF-8，拿到之後用 iconv 轉 big5 先</li><li>轉成 big5 之後，再做 encodeURIComponent 就好了</li><li>easy ~</li></ul>



<p>整個串起來之後，正確的「你好」應該是「%A7%41%A6%6E」，小蛙卻得到「%EF%BF%BDA%EF%BF%BDn」，看起來好像差很大，於是想說可能是因為 php 要透過 javascript encodeURIComponent 來轉，雖然轉好 big5 但是又在 utf-8 的文本中印出導致的，於是開始往「一切都在 php 中轉好，javascript 直接拿到轉好值就丟出去」的方向進行，小蛙改變策略：</p>



<ul class="my-li bg-darkblue wp-block-list"><li>在 php 中使用 iconv 把 UTF-8 轉成 big5</li><li>在 php 中使用 <a rel="noreferrer noopener nofollow external" href="https://blog.longwin.com.tw/2015/11/php-rawurlencode-urlencode-diff-2015/" target="_blank" data-wpel-link="external" class="wpel-icon-right">rawurlencode<span class="wpel-icon wpel-image wpel-icon-6"></span></a> 轉換</li><li>把結果透過 javascript 去要資料</li></ul>



<p>嗯，看起來應該是無懈可擊！哦哦哦哦哦哦 ~ 高興的尖叫起來，隔壁同事都看了小蛙一眼，但回傳的結果怪怪的啊，看一下後台 log，「你好」變成了「%A7A%A6n」，額外測試一個「外部」卻變成了「%A5~%B3%A1」，花惹發？嗯 ~ 港節好像比第一次得到的更接近正解一點(?)，再來想想問題在哪，重新順一下流程好了，第一步跟第二步都做轉換的動作，其中一定有一個轉換失效了，要不第一個要不第二個(廢話)。</p>



<p>Google 了一下看到這篇「<a href="http://www.edbiji.com/doccenter/showdoc/4/nav/680.html" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">php 转码函数 你还在用iconv吗？<span class="wpel-icon wpel-image wpel-icon-6"></span></a>」，既然懷疑轉換出問題，那就把他換掉看看，事情絕對沒有憨人所想的那麼簡單，結果沒變！沒變 &#8230; 記得以前有一次使用 base64 來做過這件事，正當打算要來試的時候，超猛同事走了過來問說發生什麼事，小蛙大概跟他講解了一下，他只回了一句，那你把 iconv 轉的東西印出來看就知道了。</p>



<p>看來出現新的曙光，Google 查到這篇「<a href="https://stackoverflow.com/questions/14674834/php-convert-string-to-hex-and-hex-to-string" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">PHP convert string to hex and hex to string<span class="wpel-icon wpel-image wpel-icon-6"></span></a>」，於是把 iconv 改成&nbsp;mb_convert_encoding，然後再透過該文章中的 function 來做轉換，就可以成功轉換成系統 A 需要的格式了！</p>



<p>可喜可樂！留個備份，記錄超強同事的神蹟！</p>



<h2 class="wp-block-heading">發現問題 (2018-10-16更新)</h2>



<p>上面的作法用了一段時間後發現有些字串會解析成不一樣的東西，例如：<code>帶卜辭</code>實際上收到卻變成<code>帶尸辭</code>、<code>恆春</code>變成<code>恆柢</code>、<code>小屯</code>變成<code>匕屯</code>。<br>以「帶卜辭」來當作轉換的例子<br>帶 =&gt; 「%B1a」<br>卜 =&gt; 「%A4R」<br>辭 =&gt; 「%C3%E3」</p>



<p>如果用上面的方法轉出來，轉出來會是全小寫，<br>帶 &lt;= 「%b1a」<br>尸 &lt;= 「%a4r」<br>辭 &lt;= 「%c3%e3」</p>



<p>下方是把它改成全大寫<br>婢 &lt;= 「%B1A」<br>卜 &lt;= 「%A4R」<br>辭 &lt;= 「%C3%E3」</p>



<p>但以上兩者都不對，因為原本的方法轉出來之後，就通通變成小寫，而大小寫卻對應到不一樣的字，造成這次的問題。</p>



<h2 class="wp-block-heading">使用 rawurlencode (2018-10-16更新)</h2>



<p>只要先把要轉換的文字，正確地轉到 big5 之後，透過 rawurlencode 來做編碼就可以編出正確的內容囉！</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">// 先把要的文字轉成 big5，再整個透過 rawurlencode 轉換
$txt = rawurlencode(mb_convert_encoding($txt, "big5", "utf-8"));</pre>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/109/php%ef%bc%9autf-8-%e8%b7%9f-big5-%e7%9a%84%e7%b3%be%e7%b5%90/" data-wpel-link="internal">PHP：UTF-8 跟 Big5 的糾結</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://noter.tw/109/php%ef%bc%9autf-8-%e8%b7%9f-big5-%e7%9a%84%e7%b3%be%e7%b5%90/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>NCR &#038;#xxxxx; 處理方式：Java &#038; VBA</title>
		<link>https://noter.tw/119/ncr-%e8%99%95%e7%90%86%e6%96%b9%e5%bc%8f%ef%bc%9ajava-vba/</link>
					<comments>https://noter.tw/119/ncr-%e8%99%95%e7%90%86%e6%96%b9%e5%bc%8f%ef%bc%9ajava-vba/#respond</comments>
		
		<dc:creator><![CDATA[黃小蛙]]></dc:creator>
		<pubDate>Mon, 16 Jul 2018 14:54:25 +0000</pubDate>
				<category><![CDATA[網頁後端]]></category>
		<category><![CDATA[decimal]]></category>
		<category><![CDATA[NCR]]></category>
		<category><![CDATA[VBA]]></category>
		<category><![CDATA[&#]]></category>
		<category><![CDATA[Java]]></category>
		<guid isPermaLink="false">https://noter.tw/?p=119</guid>

					<description><![CDATA[<p>工作上常常需要把資料庫的東西匯出，但有一些資料是以&#160;NCR (Numeric Character Reference) 的方式儲存或呈現，這些在網頁上看都沒有什麼問題，但在 Excel 裡面&#46;&#46;&#46;</p>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/119/ncr-%e8%99%95%e7%90%86%e6%96%b9%e5%bc%8f%ef%bc%9ajava-vba/" data-wpel-link="internal">NCR &#038;#xxxxx; 處理方式：Java &#038; VBA</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>工作上常常需要把資料庫的東西匯出，但有一些資料是以&nbsp;NCR (Numeric Character Reference) 的方式儲存或呈現，這些在網頁上看都沒有什麼問題，但在 Excel 裡面「你好嗎」如果是 NCR 就會看到這樣「你好嗎」，這邊記錄用 VBA 跟 Java 個別的處理方式。</p>



<span id="more-119"></span>



<p>先來搞清楚 NCR 的構成吧！大致上會是以 &amp;# 開頭，中間串接個數字，最後以分號 ; 結尾，大概就是像這樣</p>



<p>你 =&gt;&nbsp;&amp;#20320;<br>好 =&gt;&nbsp;&amp;#22909;<br>嗎 =&gt;&nbsp;&amp;#21966;</p>



<p>上面幾個是 Decimal NCR 十進位制的 NCR，下面放幾個 16 進制的 NCR 來比較看看</p>



<p>你 =&gt;&nbsp;&amp;#x4F60;<br>好 =&gt;&nbsp;&amp;#x597D;<br>嗎 =&gt;&nbsp;&amp;#x55CE;</p>



<p>想要知道 10 進位跟 16 進位 NCR 的請前往<a href="https://zh.wikipedia.org/wiki/字符值引用" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">維基百科<span class="wpel-icon wpel-image wpel-icon-6"></span></a>，小蛙這邊簡單歸納自己的做法：</p>



<ol class="my-li bg-darkblue wp-block-list">
<li>10 進制的 pattern 為 &amp;#(wwwww);&nbsp; &nbsp; -&gt; w 為 10 進制數字</li>



<li>16 進制的 pattern 為 &amp;#x(hhhh);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-&gt; h 為 16 進制</li>
</ol>



<h2 class="wp-block-heading para">Java 版本 NCR 處理</h2>



<p>Java code 是大概寫一下，裡面沒有加入任何防呆跟錯誤處理，例如：Integer.parseInt 這邊有可能 parse 到不是 integer 而拋出錯誤，請務必注意(乖小孩要自己加上喔)；或是 pattern 可以寫得更限縮一些。這次主要是要放上 VBA 的 Code，順道把 Java 的一起擺上來<del>作為紀念</del>幫助有需要的人，看出哪裡有問題的就 &#8230; 自己修改囉！也可以在下面留言給小蛙 ~ 感恩！</p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">public static String convertDecimalNCRToString(String txt){
	Pattern p = Pattern.compile("&amp;#(.*?);");
	Matcher m = p.matcher(txt);
	while(m.find()){
		String match = m.group(1);
		if(!match.startsWith("x")){
			txt = txt.replaceAll("&amp;#" + m.group(1) + ";", "" + (char)Integer.parseInt(m.group(1)));				
		}else{
			txt = txt.replaceAll("&amp;#" + m.group(1) + ";", "" + (char)Integer.parseInt(m.group(1).replaceFirst("x", ""), 16));
		}
	}
	return txt;
}</pre>



<h2 class="wp-block-heading">VBA 版本 NCR 處理</h2>



<p>放上 VBA 的 Code，大致上的概念是差不多的，不過這裡要注意，小蛙剛好有機會想說寫看看 VBA，New RegExp 用這個類別的時候，不知道怎麼用一直出現未定義的錯誤，後來才知道要去設定 References，<a href="https://stackoverflow.com/a/43128681" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">參考這裡<span class="wpel-icon wpel-image wpel-icon-6"></span></a>。另外，要列出最後一列的寫法是&nbsp;Cells(ActiveSheet.Rows.Count, 1).End(xlUp).Row，最後一欄的寫法是 Cells(1, ActiveSheet.Columns.Count).End(xlToLeft).Column，如果用到的是 excel 內建函數或方法的話，前面要加上&nbsp;WorksheetFunction.。</p>



<p> 2018-07-24 更新：</p>



<p>原本的寫法會造成 〹〹 這種連續 NCR 解析錯誤 Orz&#8230; 加上每次都要設定 References 感覺很麻煩，也改寫成 CreateObject(&#8220;vbscript.regexp&#8221;)，這樣以後可以直接使用，不用再去設定 References。(注意：如果 excel 裡的 NCR 非常多，執行的時候會很慢，請耐心等待) </p>



<pre class="wp-block-preformatted wp-block-syntaxhighlighter-code">Sub ConvNCR()
    'Dim regex As New RegExp
    Dim regex As Object
    Set regex = CreateObject("vbscript.regexp")
    Dim i, j As Integer
    Dim s As String
    Dim r As String
    
    'pattern of Decimal NCR
    regex.Pattern = "&amp;#((\w+));"
    regex.Global = True
    regex.IgnoreCase = True
    regex.MultiLine = True
          
    For i = 1 To Cells(ActiveSheet.Rows.Count, 1).End(xlUp).Row
        For j = 1 To Cells(1, ActiveSheet.Columns.Count).End(xlToLeft).Column
            s = Cells(i, j).Value
            r = s
            If Not IsNull(s) Then
                If regex.Test(s) Then
                    c = regex.Execute(s).Count
                    For w = 0 To c - 1
                        t = regex.Execute(s)(w).SubMatches(0)
                        If InStr(t, "x") Then
                           'to deal with hex NCR ሴ
                            t = WorksheetFunction.Hex2Dec(Right(t, 4))
                        End If
                        'relace by real "word"
                        r = Replace(r, regex.Execute(s)(w), WorksheetFunction.Unichar(t), , 1)
                        Cells(i, j).Value = r
                    Next w
                End If
            End If
        Next j
    Next i
End Sub</pre>



<p>以上，有需要的人夾去配吧！最後附上很有用的 <a href="https://r12a.github.io/app-conversion/" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">編碼轉換工具<span class="wpel-icon wpel-image wpel-icon-6"></span></a> (iframe 內嵌於<a href="https://noter.tw/12003/unicode-code-converter-特殊字元轉換器/" data-wpel-link="internal">本站</a>) 以及 <a href="https://itw01.com/V48OESL.html" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external" class="wpel-icon-right">Javascript 轉換方式<span class="wpel-icon wpel-image wpel-icon-6"></span></a>。</p>



<p><strong>Excel 相關文章：</strong></p>



<ul class="my-li bg-darkblue wp-block-list"><li><a href="https://noter.tw/9117/excel-filter-and-validation/" data-wpel-link="internal">加入篩選器及下拉選單 (資料驗證)</a></li><li><a href="https://noter.tw/9063/excel-auto-search-and-fill-data-by-vlookup-index-match/" data-wpel-link="internal">搜尋表格 填入資料 強大的 VLOOKUP, INDEX, MATCH</a></li><li><a href="https://noter.tw/9061/split-a-excel-to-files/" data-wpel-link="internal">分拆 Excel 成多個檔案 ( Split A Excel to Files)</a></li><li><a href="https://noter.tw/6550/excel-%e4%bd%bf%e7%94%a8-poi-%e8%ae%80%e5%8f%96%e6%96%87%e5%ad%97%e6%a0%bc%e5%bc%8f%e6%97%a5%e6%9c%9f%e5%8d%bb%e8%ae%8a%e6%88%90%e6%95%b8%e5%ad%97%e7%9a%84%e5%95%8f%e9%a1%8c/" data-wpel-link="internal">Excel 使用 POI 讀取文字格式日期卻變成數字的問題</a></li><li><a href="https://noter.tw/4300/java-%e8%ae%80%e5%8f%96-excel-%e6%96%87%e4%bb%b6xls-xlsx-%e4%bd%bf%e7%94%a8-apache-poi/" data-wpel-link="internal">Java 讀取 Excel 文件(xls, xlsx) – 使用 Apache POI</a></li><li><a href="https://noter.tw/2711/oracle-%e5%8c%af%e5%85%a5%e5%8c%af%e5%87%ba-excel-%e9%80%8f%e9%81%8e-sql-developer/" data-wpel-link="internal">Oracle 匯入/匯出 Excel (透過 SQL Developer)</a></li><li><a href="https://noter.tw/1732/jsp%e5%b0%87%e8%b3%87%e6%96%99%e5%8c%af%e5%87%ba%e6%88%90excel%e7%9b%b4%e6%8e%a5%e4%b8%8b%e8%bc%89-by-jexcelapi-jxl/" data-wpel-link="internal">JSP將資料匯出成Excel直接下載 by JExcelApi (jxl)</a></li><li><a href="https://noter.tw/1569/java-excel-jxl/" data-wpel-link="internal">Java + Excel = JXL</a></li><li><a href="https://noter.tw/119/ncr-%e8%99%95%e7%90%86%e6%96%b9%e5%bc%8f%ef%bc%9ajava-vba/" data-wpel-link="internal">NCR &amp;#xxxxx; 處理方式：Java &amp; VBA</a></li></ul>
<p>這篇文章 <a rel="nofollow" href="https://noter.tw/119/ncr-%e8%99%95%e7%90%86%e6%96%b9%e5%bc%8f%ef%bc%9ajava-vba/" data-wpel-link="internal">NCR &#038;#xxxxx; 處理方式：Java &#038; VBA</a> 最早出現於 <a rel="nofollow" href="https://noter.tw" data-wpel-link="internal">記下來</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://noter.tw/119/ncr-%e8%99%95%e7%90%86%e6%96%b9%e5%bc%8f%ef%bc%9ajava-vba/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
