※ 利用transactionHandler將資料寫入database
● Controller ––>orderController.ts :
使用try-catch區塊進行錯誤處理。
try {
} catch (err) {
// 處理錯誤
res.status(500).json({ errors: err })
throw err;
}
程式碼解說:
1.try
區塊:
如果在這個區塊內發生錯誤,代碼執行將跳轉到catch區塊。
2.catch
區塊:
- 當 try 區塊中的程式碼引發錯誤時,將執行這個區塊。變數err表示所拋出的錯誤。
- 錯誤處理:在 catch 區塊內,你可以處理錯誤,這裡展示了如何向客戶端返回一個 HTTP 500 狀態碼,表示伺服器內部錯誤,並將錯誤訊息以 JSON 格式返回給客戶端。
- 重新拋出錯誤:throw err; 這行將錯誤重新拋出,以便讓上層的錯誤處理邏輯能夠捕獲並處理這個錯誤。
● Knex.js 的資料庫連接:使用transactionHandler
函數處理一個異步的數據庫交易
try {
await transactionHandler(this.knexSql, async (trx: Knex.Transaction) => {
})
}
程式碼解說:
- await:這個關鍵字用來等待一個返回 Promise 的異步操作完成,並獲取其返回值。在這段程式碼中,await 會使程式碼暫停執行,直到 transactionHandler 完成。
- transactionHandler(this.knexSql):呼叫 transactionHandler 函數並傳入 this.knexSql 作為參數。這個函數會處理所有與資料庫事務相關的邏輯。
async (trx: Knex.Transaction) => { }
:傳遞給transactionHandler
的第二個參數,是一個非同步回調函數,該函數接受一個交易對象trx
作為參數。- trx:代表交易物件,用來在交易中執行所有資料庫操作。這確保了所有操作都在一個交易上下文中,並在交易結束時進行提交或回滾。
- Knex.Transaction:定義 trx 的類型,保證它是 Knex.js 提供的交易物件,具有交易的所有方法和屬性。
● 將content(產品資訊) 內的資料寫進preOrder(預購):
try {
await transactionHandler(this.knexSql, async (trx: Knex.Transaction) => {
//對裡面每一個項目product做寫入database動作
contents.map(product) => this.productModel.
}
)}
程式碼解說:
contents.map:
- contents 是一個陣列,其中包含多個產品物件。
- map 方法用來遍歷 contents 陣列中的每個 product,並對每個 product 執行一個回調函數。
● 新增 preOrder(產品的預售資料):model ––> products.ts
/production的型別和定義-新增(對伺服器來說是做預售的動作)
export interface IProductModel extends IBase<Product> {
preSell(product: Pick<Product, "id" | "amount" | "price">, trx?: Knex.Transaction)
: Promise<Boolean>;
}
程式碼解說:
1.介面 IProductModel:
主要用來定義產品模型所需的方法和屬性。
2.擴展自 IBase<Product>
:
- IBase 是一個基礎介面,通常定義了常見的 CRUD 操作(Create、Read、Update、Delete)。
- IProductModel 擴展了這個基礎介面,表示它繼承了 IBase 所定義的所有方法和屬性。
3.preSell
方法,用於處理預售檢查:
- 方法宣告:preSell「指的是方法的唯一標識符」(product: Pick<Product, "id" | "amount" | "price">)「指的是接受的參數」: Promise<Boolean>「指的是返回值類型」。
- 參數 product:
- 這個參數使用了 Pick 工具類型,表示從 Product 類型中選擇了 id、amount 和 price 三個屬性。
- product 是一個包含這些屬性的物件。
參數 trx
(可選):
- trx?: Knex.Transaction 表示 trx 是一個可選的參數,用於在交易中進行操作。
- Knex.Transaction 是 Knex.js 中的交易物件,確保操作在交易上下文中執行
- 返回值:
- 這個方法返回一個 Promise,其解析值為 Boolean 類型。
- Promise<Boolean> 表示這個方法是非同步的。
● 實作對外預售:
public preSell = async
(product: Pick<Product, "id" | "amount" | "price">, trx?: Knex.Transaction)
=> {
//從資料庫裡面找資料
let queryBuilder = this.knexSql(this.tableName)
.where({ id: product.id })
.where(this.schema.amount, '>=', product.amount)
.update(
this.schema.preOrder,
this.knexSql.raw(`?? + ?`, [this.schema.preOrder, product.amount])
);
if (trx) queryBuilder = queryBuilder.transacting(trx);
const result = await queryBuilder;
//成功update就會是大於一的數字
return !!result;
}
程式碼解說:
1.方法宣告:
- product:這個參數是一個物件,包含 id、amount 和 price 屬性,代表產品的預售資訊。
- trx:這個可選參數是 Knex.js 交易物件,用於在事務中進行操作。
2.從資料庫裡面找資料並進行更新
let queryBuilder = this.knexSql(this.tableName)
.where({ id: product.id })
.where(this.schema.amount, '>=', product.amount)
.update(
this.schema.preOrder,
this.knexSql.raw(`?? + ?`, [this.schema.preOrder, product.amount])
);
- 建立一個 SQL 查詢,進行條件篩選並更新資料庫中的資料。
- ?? 和 ? 是 Knex.js 的占位符,用來動態插入資料表名稱和數值。
- ??:用於插入資料庫欄位名,這裡插入的是 this.schema.preOrder。
- ?:用於插入數值,這裡插入的是 product.amount。
raw
是 Knex.js 提供的一個方法,用於執行原始 SQL 查詢或建構複雜的 SQL 表達式。
3.是否在交易中進行操作:
if (trx) queryBuilder = queryBuilder.transacting(trx);
- 如果提供了 trx 參數,將查詢設定為在這個交易中進行。
4.執行查詢並取得結果:
const result = await queryBuilder;
5.返回結果:
return !!result;
- 這行確保返回一個布林值,表示更新操作是否成功。成功更新時,結果應該是一個大於零的數字,表示受影響的行數。
● 對裡面每一個項目product做寫入database動作:controller ––> orderController.ts
const results = await Promise.all(
//對裡面每一個項目product做寫入database動作
contents.map(
async (product) =>
await this.productModel.preSell(
{
id: product.productId,
...pick(product, ['price', 'amount'])
},
trx
)));
1.Promise.all
:
const results = await Promise.all()
程式碼解說:
- Promise.all 用來同時執行多個非同步操作,並等待所有這些操作完成。
- 返回一個包含每個操作結果的陣列。
2.非同步函數:
async (product) => await this.productModel.preSell()
程式碼解說:
- 定義了一個非同步函數 async (product) =>,並立即調用 preSell 方法。
- 這個非同步函數接收一個 product 作為參數,表示 contents 陣列中的每一個產品物件。
3.呼叫 preSell
方法:
await this.productModel.preSell(
{
id: product.productId,
...pick(product, ['price', 'amount'])
},
trx
)
程式碼解說:
- 參數:
- 傳遞一個物件 { id: product.productId, ...pick(product, ['price', 'amount']) },其中包含產品的 id、price 和 amount 屬性。
- trx 是可選的交易物件,用來確保操作在交易上下文中執行。
- pick 函數:
- pick(product, ['price', 'amount']) 用來選擇 product 物件中的 price 和 amount 屬性。
- 這樣生成的物件包括 id, price, 和 amount。
pick
被用來選擇product
物件中的price
和amount
屬性,然後將這些屬性與id
一起傳遞給preSell
。
● 檢查 results
陣列中的任意一個元素是否為 false:
if (results.some(result => !result))
throw new Error('Cannot buy, because out of stuff.');
程式碼解說:
- results.some(result => !result):
- results:這是一個布林值陣列,表示每個產品的預售結果。
- some 方法:some 方法用來測試陣列中的某些元素是否通過由提供的函數實現的測試。只要有一個元素通過測試,就會返回 true。
- 箭頭函數 result => !result:這是一個測試函數,用來檢查陣列中的元素是否為 false。如果有任何 result 的值是 false,那麼 result => !result 就會返回 true。
- throw new Error('Cannot buy, because out of stuff.');:
- throw:關鍵字用來拋出一個異常。
- new Error:創建一個新的錯誤物件,錯誤訊息為 'Cannot buy, because out of stuff.'。
- 這表示如果有任何一個產品的預售結果為 false,程式碼會拋出一個錯誤,中止操作並向使用者提供相關訊息。
● 計算 contents
陣列中所有產品的總價格:
1.reduce 方法:
const totalPrice = contents.reduce()
程式碼解說:
- reduce 方法用來遍歷陣列中的每個元素,並將它們累加成單一的值。
- 語法:array.reduce(callback, initialValue)。
2.回調函數:
(acc, product) => acc + product.price * product.amount
程式碼解說:
- acc(累加器):用來累加每個產品的總價格。
- product:當前正在遍歷的產品物件。
- 返回值:回調函數的返回值是 acc + product.price * product.amount,表示將當前產品的價格乘以數量後加到累加器中。
3.初始值 0
:
, 0
程式碼解說:
- 這是累加器的初始值,表示從 0 開始累加。
※ 開立訂單:controller ––> orderController.ts
● 利用orderModel開立訂單
//自做id
const uid = genUID();
//利用orderModel開立訂單
await this.orderModel.create({
id: uid,
total: totalPrice,
createdAt: new Date(),
updatedAt: new Date(),
paymentProvider,
paymentWay,
status: OrderStatus.WAITING,
contents,
},
trx
)
});
1.生成唯一識別碼 uid
:
const uid = genUID();
程式碼解說:
- 呼叫 genUID 函數來生成一個唯一識別碼 uid,用於標識這個訂單。
2.建立訂單資料 orderData
:
const orderData = {
id: uid,
total: totalPrice,
createdAt: new Date(),
updatedAt: new Date(),
paymentProvider,
paymentWay,
status: OrderStatus.WAITING,
contents,
};
程式碼解說:
- id: uid:使用生成的唯一識別碼。
- total: totalPrice:訂單的總價格,先前計算得到。
- createdAt: new Date():設定訂單的創建時間為當前時間。
- updatedAt: new Date():設定訂單的更新時間為當前時間。
- paymentProvider:支付提供者,例如 PayPal。
- paymentWay:支付方式,例如信用卡。
- status: OrderStatus.WAITING:訂單狀態設置為 "等待中"。
- contents:訂單的內容,即包含的產品信息。
※ 建立genUID(生成唯一識別碼),創造13位時間戳 + 7位額外字符 = 20位總長度:utlis ––> index.ts
● 新增 genUID
export const genUID = () => {
//讓偶數位變成英文:1a3d5e7h9v
const alpha = "abcdefghij";
// timestamp 13+7=20位長度的字串
const timestampStr = new Date().getTime().toString();
const code = timestampStr
.split("") //["1","2","3"...]
.map((v, idx) => (idx % 2 ? v : alpha[Number(v)]))
.join("") //已有13位數
//安裝genUID來產生剩下7位數
const id = uuid().split("-")[0];
return `${code}${id.substring(0, id.length - 1)}`;
}
1.定義字母映射:
const alpha = 'abcdefghij';// 每個數字對應的字母
程式碼解說:
- 用來將數字 0-9 對應為字母 'a'-'j'。
2. 獲取時間戳:
const timestampStr = new Date().getTime().toString();
程式碼解說:
- 獲取當前的時間戳並轉換為字符串形式。
3.時間戳轉換和字母映射:
const code = timestampStr.split('')
.map((v, idx) => idx % 2 ? v : alpha[Number(v)])
.join('');
程式碼解說:
- 將時間戳轉換為一個字符陣列。
- 使用 map 方法遍歷陣列中的每個元素,對於索引為偶數的數字,將其轉換為對應的字母。
- 最後將陣列重新轉換為字符串。
※ 安裝UUID是一個具體的標準化識別碼。
npm install uid
※ 導入 uuid 並使用 v4 方法生成一個隨機的 UUID:
import { v4 as uuid } from "uuid";
4.生成 UUID 部分:
const id = uuid().split('-')[0];
return `${code}${id.substring(0, id.length - 1)}`;
程式碼解說:
uuid()
方法生成一個新的 UUID。- split('-') 方法將 UUID 按照 - 分隔符拆分成一個陣列。
- [0] 用來提取陣列中的第一個元素,也就是 UUID 的前半部分。
- 從
id
字串的開頭 (索引0) 截取到倒數第二個字符的位置 (id.length - 1)。 - 返回一個新的字串,由變數
code
的值和id
字串從頭到倒數第二個字符的組成。例如,"ABC1234"。
5.導入 genUID 的函式:controller ––> orderController.ts
import { genUID} from "@/utils";
3.將訂單資料寫入資料庫:
await this.orderModel.create(orderData, trx);
程式碼解說:
- this.orderModel.create(orderData, trx):呼叫 create 方法,將 orderData 傳遞進去,並使用 trx 交易物件來確保這些操作在交易中進行。
4.返回成功訊息:
res.json({ status: 'success' });
- 向客戶端返回一個 JSON 格式的成功訊息,表示訂單創建成功。
※ 解決資料庫欄位不符問題:model ––> base.ts
將對象轉為 JSON 字串。
// check if a string is json
if (typeof value === "object") return JSON.stringify(value);
程式碼解說:
typeof value === "object"
: 檢查value
是否是一個物件。typeof
運算符可以返回一個表示變數資料類型的字符串,例如 "number"、"string"、"undefined" 和 "object"。return JSON.stringify(value);
: 如果value
確實是一個物件,則使用JSON.stringify()
方法將它轉換為一個 JSON 字符串並返回。

建一個SQL查詢,從一個指定的表中選擇指定的欄位:
let sqlBuilder = this.knexSql(this.tableName).select(this.schema).where({ id });
程式碼解說:
.where({ id })
: 這是條件子句,用於篩選資料。此處使用了一個簡潔的物件描述法查詢id
欄位。

※ 確認結果:
postman:

MySQL:
