Node express OAuth 2.0 使用 nodemailer 套件透過 gmail 發送電子信箱

node 14.20.1

步驟

  1. 申請 Google 認證模式OAuth
    以上獲得
    • CLINENT_ID
    • CLINENT_SECRET
    • REFRESHTOKEN
    • ACCESSTOKEN
  2. 安裝nodemailer

一.申請 Google 認證模式OAuth

一.Google 更改認證模式,Nodemailer 官方文件 也推薦改使用 OAuth 2.0 來介接 Gmail

建立 OAuth
Console
Cloud Google

  1. 建立一個新專案 2.專案名 3.Api服務,搜尋Gmail Api 4.啟用Gmail Api 5.憑證->Auth 用戶端 ID 6選取外部 測試使用者 憑證 → 建立憑證 → OAuth 用戶端 ID
取得用戶端編號Client ID與用戶端密鑰Client Secret 網路應用程式 已授權的重新導向 URI」選擇「新增 URL」
https://developers.google.com/oauthplayground
取得 Refresh token 與 Access token

接下來請開啟這個網址這裡,然後點一下右上齒輪,並將「Use your own OAuth credentials」打勾輸入剛剛的 Client ID 以及 Client Secret



Step2 取得Token


以上獲得
  • CLINENT_ID
  • CLINENT_SECRET
  • REFRESHTOKEN
  • ACCESSTOKEN
加到環境變數內請三考

二.nodemailer:發信功能串接

安裝 nodemailer
nodemailer官網

1
npm install --save nodemailer 

新增一個頁面router

1
2
3
// routes
var contact = require('./routes/contact');
app.use('/contact', contact);

到contact 頁面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
var express = require('express');
var router = express.Router();
var nodemailer = require('nodemailer');

//環境變數
require('dotenv').config()
router.get('/', (req, res)=> {
res.render('contact');
});
//這是寄送的成功訊息
router.get('/review', (req, res)=> {
res.render('contactReview');
});

//這是寄送的表單
router.post('/post', (req, res) =>
{
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
type: "OAuth2",
user: process.env.ACCOUNT,
clientId: process.env.GMAIL_CLINENT_ID,
clientSecret: process.env.GMAIL_CLINENT_SECRET,
refreshToken: process.env.GMAIL_CLINENT_REFRESHTOKEN,
accessToken: process.env.GMAIL_CLINENT_ACCESSTOKEN,
}
});
let mail = req.body.email;
var mailOptions = {
form: '"我自己"<xxx@gmail.com>',
to :`xxx@gmail.com,${mail}`,
subject:req.body.title+':'+req.body.username +'的訂單',
text: req.body.description
}
transporter.sendMail(mailOptions, (error, info) =>
{
console.log('info',info);
if(error){
return console.log(error,'訂單沒有送出!');
}
res.redirect('review');
})
});
module.exports = router;

token 有失效的問題

刷新令牌
刷新令牌2

// 完整程式碼https://github.com/hsiangfeng/node-nodemailer-finish
//https://hackmd.io/@yy933/Hy9rKyAE3?utm_source=preview-mode&utm_medium=rec

參考文獻
全端勇士之路 Node.js-OAuth 2.0 & nodemailer & Gmail
Send mails from Gmail using Nodemaile
Sending Emails with Node.js Using SMTP, Gmail, and OAuth2
Sending Emails with Node.js Using SMTP, Gmail, and OAuth2
Use Expressjs to send mails with gmail OAuth 2.0 and nodemailer
OAUTH2
Nodemailer/Gmail - What exactly is a refresh token and how do I get one?

Vite React問題

React Vite clear Cache on client side React Vite

客戶端清除緩存
瀏覽器緩存
在index.html新增

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />


# React Warning non-passive event listener to a scroll-blocking 'touchstart'
反應警告非被動事件偵聽器到滾動阻塞“touchstart”

Vite 打包優化

優化指標

  • FCP(First Contentful Paint):白屏時間(第一個文本繪製時間)
  • Speed Index:首屏時間
  • TTI(Time To Interactive): 第一次可交互的時
  • lighthouse score:Chrome瀏覽器審查工具性能評分

借助 Chrome 完成

  • Network 分析
  • Lighthouse 分析
  • Bundle 分析

Network 分析

網絡加載狀態「 1.9MB 」的 index.js 文件佔用了比較大的加載時間,阻塞了其它資源的加載,是一個優化點

Lighthouse 分析

  • Analyze page load 分析網站加載情形
  • Performance 性能
  • Accessibility 無障礙功能
  • Best Practices 最佳作法
  • Seo
  • Pwa漸進式Web應用

Bundle 分析

安裝 rollup-plugin-visualizer 構建(打包)分析插件

rollup-plugin-visualizer

1
npm i rollup-plugin-visualizer -D

vite vite.config.js引入visualizer()

1
2
3
4
5
6
7
8
9
//引入
import { visualizer } from "rollup-plugin-visualizer";

module.exports = {
plugins: [
vue(),
visualizer()
],
};

打包之後會在項目根目錄生成 stats.html 文件,打開

github說明rollup-plugin-visualizer 構建(打包)分析插件
github說明rollup-plugin-visualizer 構建(打包)分析插件

GZIP 配置

1
npm i vite-plugin-compression -D

//引入
import { vite-plugin-compression } from “vite-plugin-compression”;

1
2
3
4
5
6
7
8
9

//引入vite-plugin-compression
import viteCompression from 'vite-plugin-compression'
module.exports = {
plugins: [
vue(),
viteCompression()
],
};

打包後就會生成 gzip 文件了

github說明-gzip安裝與設置
github說明-gzip安裝與設置

圖片壓縮

1
2
安裝
npm i vite-plugin-imagemin -D

//引入
import viteImagemin from ‘vite-plugin-imagemin’

1
2
3
4
5
6
7
8
9
10
11
//引入
import viteImagemin from 'vite-plugin-imagemin'

export default defineConfig({
plugins: [
vue(),
visualizer(),//構建(打包)分析插件
viteCompression(), //GZIP 配置
viteImagemin(), // 圖片壓縮
],
})

打包以後出現以下數據

vite v5.1.3 building for production…
✓ 18 modules transformed.
dist/index.html 0.46 kB │ gzip: 0.30 kB
dist/assets/index-CKKEtT6e.css 1.27 kB │ gzip: 0.65 kB
dist/assets/index-CWKaKe3g.js 55.26 kB │ gzip: 22.43 kB
✓ built in 712ms

✨ [vite-plugin-imagemin]- compressed image resource successfully:
dist/vite.svg -17% 1.46kb / tiny: 1.22kb

✨ [vite-plugin-imagemin]- 壓縮圖片資源成功:
dist/vite.svg -17% 1.46kb / 小:1.22kb

✨ [vite-plugin-compression]:algorithm=gzip - compressed file successfully:
dist//Users/larahuang/Desktop/作品集/lara_webdesign/assets/index-CKKEtT6e.css.gz 1.24kb / gzip: 0.63kb
dist//Users/larahuang/Desktop/作品集/lara_webdesign/assets/index-CWKaKe3g.js.gz 53.99kb / gzip: 21.89kb

github壓縮說明 image

閉包 (Closure)

閉包範例與解釋

閉包是函式被建立時所在的環境,環境由任意的局域變數所組成,這些變數是由在閉包建立的時間點上存在於作用域裡的所有變數,特殊的環境變數--本來以為應該消失,可是卻依然存在的變數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function addFn() {
let num = 10

function ClosureAddFn(newNum) {
num = num + newNum
return num
}
//函示內 return 函示就會變成 『閉包』
return ClosureAddFn
}
const useClosure = addFn()

console.log(useClosure(10)) //20
console.log(useClosure(10)) //30
console.log(useClosure(10)) //40
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function addFn() {
let num = 10

function ClosureAddFn(newNum) {
num = num + newNum
return num
}
//函示內 return 函示就會變成 『閉包』
return ClosureAddFn
}
const useClosure = addFn()
console.log(useClosure(10)) //20
console.log(useClosure(10)) //30
console.log(useClosure(10)) //40

const useClosure2 = addFn()
console.log(useClosure2(100)) //110
console.log(useClosure2(100)) //210

參考資料

  • 閉包(Closure)的細節:共用與非共用變數
  • 閉包(Closure)的細節(二):模擬私有變數
  • 閉包(Closure)的細節(三):閉包與記憶體洩漏
  • 閉包(Closure)的細節(四):閉包效能與prototype

共用與非共用變數

一般函式在執行完畢時,內部的區域變數會被釋放掉,但如果在執行時產生了閉包。那閉包會保留其所在巢狀函式內的變數。讓他不至於被系統釋放掉。現在有一個問題是,閉包保留下來的變數,會與其他閉包共用嗎?這其實取決於閉包產生的位置與時間點。

同一個呼叫期間內的閉包共享變數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function closuretest(){
x=10;
function changex(cx){
x=cx;
}
function showx(){
console.log(x)
}
f1= changex;
f2= showx;
}
closuretest();
console.log(f2())//顯示10
console.log(f1(5))
console.log(f2())//顯示5

不同呼叫期間的閉包各自擁有變數

1
2
3
4
5
6
7
8
9
10
function closuretest(ox){
var x=ox;
return function(){
console.log(x)
}
}
var f1=closuretest(5);
var f2=closuretest(10);
console.log(f1());//顯示5
console.log(f2());//顯示10

閉包效能與prototype

閉包有效能上的影響,主要是反映在處理速度與記憶體的占用。
以下誤用的的寫法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function MyPlayer(name,race,sex) {
this.name = name.toString();
this.race = race.toString();
this.sex = sex.toString();
this.getName = function() {
return this.name;
};
this.getRace = function() {
return this.race;
};
this.getSex = function() {
return this.sex;
};
}

不同呼叫期間的閉包各自擁有變數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function MyPlayer(name,race,sex) {
this.name = name.toString();
this.race = race.toString();
this.sex = sex.toString();
}
MyPlayer.prototype.getName = function() {
return this.name;
};
MyPlayer.prototype.getRace = function() {
return this.race;
};
MyPlayer.prototype.getSex = function() {
return this.sex;
};

利用原型可以讓物件共享方法,這比上面有效率的多。
這範例說明了prototype與閉包的使用時機,不過嚴格來說上面兩種做法都有點不切實際,因為其實變數本身就是公開屬性了,在為其增加公開方法其實沒必要。比較常的做法是用閉包來模擬並且存取私有變數。用prototype來實作通用方法以及存取通用的私有變數。

Array.of()

Array.of() 方法從任何可迭代物件傳回一個陣列。

Array.of() 方法從任意數量的參數建立一個新數組。

Array.of() 方法可以採用任何類型的參數。

1
2
3
let fruits = Array.of("Banana", "Orange", "Apple", "Mango");
console.log(fruits)
//(4) ['Banana', 'Orange', 'Apple', 'Mango']

參考資料
Array.of()

entries()

entries()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

const fruits = ["Banana", "Orange", "Apple", "Mango"];

// 建立條目
const list = fruits.entries();

// 列出條目
let text = "";
for (let x of list) {
console.log(x)
}
//(2) [0, 'Banana']
//(2) [1, 'Orange']
//(2) [2, 'Apple']
//(2) [3, 'Mango']

參考資料
entries()