Node基礎之CSRF 篇

CSRF 跨站請求偽造,這是一個非常常見的攻擊手法

防禦 CSRF

1
npm install --save csurf

引入方式

1
var csurf = require('csurf')

那使用方式其實很簡單,它採用 middleware 的作法

1
2
3
4
5
6
7
8
9
10
11
12
var cookieParser = require('cookie-parser')

var csrfProtection = csrf({ cookie: true })

app.get('/form', csrfProtection, function (req, res) {
// pass the csrfToken to the view
res.render('send', { csrfToken: req.csrfToken() }) // 傳給 View csrf 的 token
})

app.post('/process', parseForm, csrfProtection, function (req, res) {
res.send('data is being processed')
})

在表單傳送上必須夾帶 CSRF 的資訊

*注意若使用了 CSURF 之後 PostMan 就會無法動作唷

Node createServer 起手式

新增一個專案名稱,並在專案內新增一個app.js,在app.js 加入以下程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 載入 http 模組
var http = require(‘http’)
// 使用 http.createServer() 方法建立 Web 伺服器,回傳一個 Server 實例
var server = http.createServer()
// 註冊 request 請求事件監聽,當前端請求過來,此事件就會被觸發,並且執行 callback 函式
server.on(‘request’, function (request, response) {
//表頭:函式內用 response 中的 writeHead 寫入如果正確呼叫到這支程式的狀態與回傳的內容,狀態:200為成功呼叫到此程式,把 Content-type 的 text/plain=>字串, 如果要回傳是網頁元素則是把 Content-type 改為 text/html
response.writeHead(200, {
// "Content-type": "text/html",
//"Content-type": "text/plain",

});
//text/html
//response.write("<h1>hello node!</h1>");

response.write(‘Hello World!’)
//
response.end()
// 也可以簡化成 response.end(‘Hello World!’)
})
// 綁定 port通訊埠,啟動伺服器
server.listen(8000, function () {
console.log(‘伺服器已在 port 8000 運行 …’)
})

終端機執行 node app.js , 開啟 http://127.0.0.1:8000

三考

yarn 安裝

yarn

yarn 安裝指令

前置作業node 版本為14.20.1

1
2
nvm ls
nvm use 14.20.1

全局安裝

1
npm install --global yarn

檢查安裝
通過運行檢查是否安裝了 Yarn:

1
yarn --version

1.22.19

Vue & Firebase Realtime Database CRUD

新增資料夾 config/firebaseConfig.js’內容如下

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
導入資料庫
import firebase from ‘firebase/compat/app’
import ‘firebase/compat/auth’
import ‘firebase/compat/firestore’
import ‘firebase/compat/database’

const config = {
apiKey: “apiKey”,
authDomain: “apiKey”,
databaseURL: “apiKey”,
projectId: “apiKey”,
storageBucket: “apiKey”,
messagingSenderId: “apiKey”,
appId: “apiKey”
}

firebase.initializeApp(config);
let app = null
if (!firebase.apps.length) {
app = firebase.initializeApp(firebaseConfig)
}


export const ref = firebase.database().ref()
export const firebaseAuth = firebase.auth;

export const db = firebase.database()
export const StoreDB = firebase.firestore()
export default firebase

Create

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
49
<!–訊息表單新增–>
<template>
<form @submit.prevent=”addMessage“>

<div class=”form-group”>
<label>Name:</label>
<input type=”text” class=”form-control” v-model=”message.subject”/>
</div>

<div class=”form-group”>
<label>message</label>
<input type=”text” class=”form-control” v-model=”message.desc”/>
</div>

<div class=”form-group”>
<input type=”submit” class=”btn btn-primary” value=”送出”/>
</div>

</form>

</template>

<script>
import firebase from ‘@/config/firebaseConfig.js’;
const messageRef = firebase.database().ref(‘/message/’);

export default {
data () {
return {
message:”,
}
},
methods: {
addMessage() {
//資料表 .push({ })
messageRef.push({
subject: this.subject,
desc: this.desc,
timeStamp: new Date()
})
},
}
create(){
this.addMessage();
}

}

</script>

Read

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
<template>
<div>
<ul>
<li v-for=”item in message” :key=”item.index”>
{{ item.subject }} – {{. item.desc. }}
</li>
</ul>
</div>
</template>

<script>
import firebase from ‘@/config/firebaseConfig.js’;
//資料表名稱message
const messageRef = firebase.database().ref(‘/message/’);


export default {
data () {
return {
message:”
}
},
methods: {
getMessage () {
const vm= this;
// 簡寫 messageRef.on(‘value’,(snapshot) =>{
messageRef.on(‘value’, function(snapshot) {
vm.message = snapshot.val();
})
}
},
create(){
this.getMessage () ;
}
}
</script>

Firebase 語法

ref():尋找資料庫路徑
set():新增資料
on(隨時監聽)
push – 新增資料
child 子路徑
remove 移除

SCSS 入門:變數、巢狀、混入、繼承

& ,> ,+,~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.onclick{
//自己
&.onclick{
color:green;
cursor: pointer;
}
//children-必須是親生的才可以
> .icon{
color:#2196f3;
}
//同一階的第一個
+ .content {
color:red;
}
//~,同一階-弟弟妹妹們都獲選
~ .test{
color:blue;
}

&

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.menu {
.open & {
display:flex;
color:green;
font-size:38px;
}
}
//
編譯後
.menu {
.open & {
display:flex;
color:green;
font-size:38px;
}
}

&

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
scss
.list {
color: red;

&__item {
color: cornflowerblue;

&--active {
color: blue;
}
}
}
/*編譯後
.list {
display: flex;
}

.list__item {
color: red;
}

.list__item--active {
color: blue;
}

變數 Variables:$開頭

Scss

1
2
3
4
5
6
7
$font-stack:  Helvetica, sans-serif;
$primary-color: #2196f3;

body {
font: 100% $font-stack;
color: $primary-color;
}

變數編譯後Css

1
2
3
4
5
body {
font: 100% Helvetica, sans-serif;
color: #2196f3;
}

巢狀

Scss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
nav {
ul {
margin: 0;
padding: 0;
list-style: none;
}

li { display: inline-block; }

a {
display: block;
padding: 6px 12px;
text-decoration: none;
&:hover {
color: red;
}
}
}

巢狀編譯後Css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nav ul {
margin: 0;
padding: 0;
list-style: none;
}
nav li {
display: inline-block;
}
nav a {
display: block;
padding: 6px 12px;
text-decoration: none;
}
nav a:hover {
color: red;
}

混入 Mixins

@mixin,@include

Scss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 經常重複使用的樣式
@mixin transform($property) {
-webkit-transform: $property;
-ms-transform: $property;
transform: $property;
}

// 需要套用樣式的程式碼
.box {
@include transform(rotate(30deg));
}
.avatar {
@include transform(rotate(90deg));
}

混入 Mixins編譯後Css

1
2
3
4
5
6
7
8
9
10
.box {
-webkit-transform: rotate(30deg);
-ms-transform: rotate(30deg);
transform: rotate(30deg);
}
.avatar {
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}

混繼承 Extent/Inderitance

@mixin,@include

Scss

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
49
50
51
52
/* This CSS will print because %message-shared is extended. */
%message-shared {
border: 1px solid #ccc;
padding: 10px;
color: #333;
}

// This CSS won't print because %equal-heights is never extended.
%equal-heights {
display: flex;
flex-wrap: wrap;
}

.message {
@extend %message-shared;
}

.success {
@extend %message-shared;
border-color: green;
}

.error {
@extend %message-shared;
border-color: red;
}

.warning {
@extend %message-shared;
border-color: yellow;
}

%flex-center {
display: flex;
justify-content: center;
align-items: center;
}

.header {
@extend %flex-center;
background-color: red;
}

.section {
@extend %flex-center;
background-color: blue;
}

.footer {
@extend %flex-center;
background-color: green;
}

混繼承 Extent編譯後Css

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
/* This CSS will print because %message-shared is extended. */
.message, .success, .error, .warning {
border: 1px solid #ccc;
padding: 10px;
color: #333;
}

.success {
border-color: green;
}

.error {
border-color: red;
}

.warning {
border-color: yellow;
}

.header, .section, .footer {
display: flex;
justify-content: center;
align-items: center;
}

.header {
background-color: red;
}

.section {
background-color: blue;
}

.footer {
background-color: green;
}

@mixin 和 @extend 使用

@mixin 的好處:減少重複撰寫樣式,卻也可能造成編譯後的 CSS 樣式大量重複,使檔案異常肥大。

@mixin 和 @extend 兩者的使用時機與差異

是否需傳遞參數
是否需考慮編譯後 CSS 大小

模組 Modules

編譯前 SCSS:要作為模組載入的 SCSS 檔案,名稱必須帶有底線,例如 _base.scss。

Sass 安裝

方法一

npm 安裝

建立編譯環境

1
npm install -g sass

sass –version版本號

1
sass --version

參考資料
sass 安裝

# 新增styles.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$primary: #30c39e;
%transition{
transition: all .3s ease;
}

.btn {
background: $primary;
@extend %transition;
&:hover {
background: darken($primary, 5%);
}
&:active {
background: darken($primary, 10%);
}
}

進入在你的scss檔案的資料夾,並執行sass watch監控sass 輸出 css
1
2
3
4
//window
sass --watch rwdScss:rwdCss

sass scss\styles.scss css\styles.css

方法二

vs-code 安裝套件:Live Sass Compiler

指定輸出CSS的路徑:

喜好設定->設定打開Setting .json

設置參數放工作區設定的大括弧內

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//css產出設定
"liveSassCompile.settings.formats": [
{
"format": "expanded",
"extensionName": ".css",//
"savePath": "/css"///輸出的路徑
},
{
"extensionName": ".min.css", //正式版副檔名為min.css
"format": "compressed", //壓縮成一行css,正式版本
"savePath": "/dist/style"
}
],

建立一個資料夾:

內有scss/style.scss

視窗下方藍色區域是否出現了一個小小的按鈕寫著Watch Sass,按下執行

畫面會新增 style.css.map
畫面會新增 style.css

Vue Google Sheet Api憑證申請與串接

ConsoleGoogle

一.申請Google Sheet Api憑證

Step1.新增專案

Step2.啟用Api服務

搜尋Google Sheet

啟用Google Sheet服務

Step3.建立憑證(api金鑰 /Oauth /服務帳戶)

api金鑰:key=API_KEY

憑證=>點選API金鑰

=>金鑰限制 => 應用程式限制 =>HTTP 參照網址 (網站)

=>網站限制 =>點選新增:新增網址

=>API 限制:不限制金鑰

如果只是使用Google Sheet 以下(Oauth,服務帳戶)非必要
Oauth



服務帳戶

Api金鑰(憑證Api金鑰)等下串接會用到

二.新增Sheet試算表 粗字體是docId

https://docs.google.com/spreadsheets/d/19YWnM9PrXXEUOcT9zWkQYpiZ1W3o6i-UToEGsUPcFcU/edit#gid=0

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
<template>
<div class="hello">
<table class="Lists" align="center">
<tr v-for="item in lists" :key="item">
<td v-for="j in item" :key="j">{{ j }}</td><td><i class="fa-solid fa-circle-plus"></i></td>
</tr>
</table>
</div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios';

const lists = ref<any>();
const getData = async () => {
//docId=>文件id
const docId = '憑證Api金鑰';
const apiKey = '試算表的ID';
const api = `https://sheets.googleapis.com/v4/spreadsheets/${docId}/values/商品列表?alt=json&key=${apiKey}`;

await axios
.get(api)
.then(res => {
lists.value = res.data.values;
console.log(res.data);
for (var i = 0; i <= lists.value.length; i++) {
// title
var title = lists.value[0];
var objTitle = {};
for (var j = 0; j <= title.length; j++) {
objTitle = title[j]
}
}
});

}

onMounted(() => {
getData();
});
</script>

錯誤訊息大全

404

處理方式:=>網站限制 =>點選新增:新增網址 =>API 設置為不限制 =>金鑰限制 => 應用程式限制 =>HTTP 參照網址 (網站) =>網站限制 =>點選新增:新增網址 =>API 限制:不限制金鑰

403

Requests from referer \u003cempty\u003e are blocked.
處理方式:API 設置為不限制
=>API 限制:不限制金鑰

檢查你的 Sheet Id 能否正確取得資料

權限被拒絕

permissionPERMISSION DENIED
處理方式:
命名工作區

將Google Sheet 改為共用

改為 Pinia Composition API寫法

安裝 Pinia

新增stores / googlesheet.js

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
import { defineStore } from 'pinia';
import axios from "../utils/http";
import { ref } from 'vue';

export const useGooglesheetStore = defineStore('googlesheet', () =>
{

const getLists = ref();
const getGoogleSheetData = async() =>
{
const docId = import.meta.env.VITE_API_GOOGLE_SHEET_ID;
const apiKey = import.meta.env.VITE_API_GOOGLE_KEY;
const api = `https://sheets.googleapis.com/v4/spreadsheets/${docId}/values/商品列表?alt=json&key=${ apiKey}`;
try {
const res = await axios.get(api);
getLists.value = res.data.values;
console.log(getLists.value)
} catch (error) {
console.log(error)
}

}
return {
getGoogleSheetData,getLists
}
})

#專案內:.env.development,.env.production,.env.staging
新增

1
2
3
VITE_API_GOOGLE_KEY = GOOGLE_KEY

VITE_API_GOOGLE_SHEET_ID = GOOGLE_SHEET_ID

#頁面使用goolgesheet 數據
//引入{ storeToRefs } from ‘pinia’=>

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
<template>
<div class="hello">
<table class="Lists" align="center">
<tr v-for="item in getLists" :key="item">
<td v-for="j in item" :key="j">{{ j }}</td>
<td><i class="fa-solid fa-circle-plus"></i></td>
</tr>
</table>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { onMounted } from 'vue';
// @ts-ignore
import { useGooglesheetStore } from '../stores/googlesheet';
const store = useGooglesheetStore();
//數據的解構
const { getLists } = storeToRefs(store);
//function 的解構
const { getGoogleSheetData } = store;

onMounted(() => {
getGoogleSheetData()
});
</script>

將你的試算表轉成Api

將您的 Google 試算表轉換為 REST API

選擇登入

Step1 建立連結


Step2 將Google Sheet改為共用,並且 貼到共用的連結表單



Vue 天氣Api

OpenWeatherMap
建立一個帳號,建立一個金鑰
https://openweathermap.org/

#使用fetch
${ import.meta.env.VITE_API_URL },${ import.meta.env.VITE_API_KEY }為環境變數
請參閱

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
<script setup lang="ts">
import { ref, onMounted } from 'vue'
interface listType {
dt?: number | null,
message?: number | null,
cnt?: number | null,
list:any[]
}

interface weatherType {
cod?: number|null,
message?: number | null,
cnt?: number | null,
list: listType[]
}

const weathers = ref<any>();
const getData = async () => {
const api = `${import.meta.env.VITE_API_URL}forecast?q=Taichung,tw&APPID=${import.meta.env.VITE_API_KEY }&lang=zh_tw&units=metric`
try {
const data = await fetch(api);
if (!data.ok) {
throw Error('fetch data 失敗');
}
if (data.status === 200) {
weathers.value = await data.json();
console.log(await data.json())
}
} catch (error) {
// throw Error(error?:any)
}
}
onMounted(() => {
getData();
});
</script>

Vite vue語法糖 axios (api 串接方式)

安裝

1
npm install axios --save

Vite 環境變數使用方式

  • 引入axios import axios from 'axios'
  • 新增interface listType
  • 定義list 為陣列 const lists = ref([]);
  • 使用 async ...await
  • try ...catch
  • 環境變數的使用:反斜線與${import.meta.env.環境變數的名稱} 例如:`${import.meta.env.VITE_API_URL}/api/questions`
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
<script setup lang="ts">
import { ref,onMounted } from 'vue'
import axios from 'axios'

interface listType {
UID?: string;
category?: string;
comment?: string;
descriptionFilterHtml?: string;
discountInfo?: string | any;
editModifyDate?: string;
endDate?: string;
hitRate?: number;
imageUrl?: string;
masterUnit?: object;
otherUnit?: object;
showInfo?: object;
showUnit?: string;
sourceWebName?: string | any;
sourceWebPromote?: string;
startDate?: string | any;
subUnit?: object;
supportUnit?: object;
version?: string;
title?: string | any;
webSales?: string;
}
const lists = ref<listType[]>([]);
const getData = async () => {
try {
const api = `${import.meta.env.VITE_API_URL}/frontsite/trans/SearchShowAction.do?method=doFindTypeJ&category=200`;
await axios.get(api)
const res = await axios.get(api);
console.log('culture', res.data, typeof res.data[0].masterUnit
, 'otherUnit', typeof res.data[0].otherUnit, 'showInfos', typeof res.data[0].showInfo);
if (res.status === 200) {
lists.value = res.data;
}

} catch (error) {
console.log(error)
}

}
onMounted(() => {
getData();
})
</script>

github 說明-axios 安裝與使用
github 說明-axios 安裝與使用