createOrder
:建立訂單。updateAmount
:更新金額。//orderController API接口
export class OrderController implements IOrderController {
knexSql: Knex;
orderModel: IOrderModel;
productModel: IProductModel;
constructor({ knexSql, orderModel, productModel }: {
knexSql: Knex;
orderModel: IOrderModel;
productModel: IProductModel;
}) {
this.knexSql = knexSql;
this.orderModel = orderModel;
this.productModel = productModel;
}
//建立訂單
public createOrder: IOrderController["createOrder"] = (req, res, _next) => {
let { paymentProvider, paymentWay, contents } = req.body
console.log("🚀 ~ OrderController ~ paymentProvider, paymentWay, contents :", paymentProvider, paymentWay, contents )
res.json({ status: "success" });
//1.資料驗證
//2.將資料寫入database---> ORDER
//3/金流API的串接(ECPAY,PAYPAL)
//4. return database create success
};
//更新金額
public updateAmount: IOrderController["updateAmount"] = (_req, _res, _next) => {
// Todo
}
}
public createOrder: IOrderController['createOrder'] = (req, res, _next) => {
let { paymentProvider, paymentWay, contents } = req.body
console.log(
"~ file: ordreController.ts ~ line 52 ~ OrderController ~ paymentProvider, paymentWay, contents",
paymentProvider,
paymentWay,
contents
);
res.jsonp({ status: 'success' });
};
createOrder
的主要用途:import express from 'express';
import { ControllerContext } from "@/manager/controllerManager";
export const mountOrderRouter = ({
controllerCtx
}: { controllerCtx: ControllerContext }) => {}
let router = express.Router();
router.post('/create', controllerCtx.orderController.createOrder);
return router;
export interface ControllerContext {
productController: IProductController;
orderController: IOrderController;
}
const orderController = OrderController.createConstructor({
orderModel: modelCtx.orderModel,
})
createConstructor
方法創建 orderController
,並且確保這個控制器在初始化時擁有所需的 orderModel
作為其依賴項,以便在後續的業務邏輯中使用。OrderController
:manager –––> modelManager.tsimport { IOrderModel, OrderModel } from "@/model/order";
//在一個地方管理和使用不同的模型
//定義一個模型相關的環境資訊
export interface ModelContext {
orderModel: IOrderModel;
}
//與資料庫相關的初始化操作
export const modelManager = ({ knexSql }: { knexSql: Knex }): ModelContext => {
const orderModel = OrderModel.createModel({ knexSql })
return { orderModel }
}
ModelContext
的介面,這個介面包含了一個屬性 orderModel
,其類型為 IOrderModel
。OrderController
實例:Controller –––>OrderController.ts
createController
:public static createController(
{ knexSql, orderModel, productModel }:
{ knexSql: Knex; orderModel: IOrderModel; productModel: IProductModel })
}
OrderController
實例:return new OrderController({ knexSql, orderModel, productModel });
ControllerContext
介面export interface ControllerContext {
orderController: IOrderController;
}
ControllerContext
介面,新增orderController (訂單控制器)。
controllerManager
函數export const controllerManager = ({ knexSql, modelCtx }: {
knexSql: Knex;
modelCtx: ModelContext
}): ControllerContext => {
新增加knexSql
: 一個 Knex 的實例,用於與資料庫進行交互。
orderController
const orderController = OrderController.createController({
knexSql,
orderModel: modelCtx.orderModel,
productModel: modelCtx.productModel,
});
這部分創建了 orderController
,使用 OrderController
的 createController
方法,並傳入 knexSql
、orderModel
和 productModel
。
class App {
private knexSql: Knex;//新增
constructor() {
this.knexSql = createDatabase();
this.controllerCtx = controllerManger({
knexSql: this.knexSql,
})
}
private routerSetup() {
this.app.use('/orders', mountOrderRouter({ controllerCtx: this.controllerCtx }))
}
import { mountOrderRouter } from './routes/order';
private routerSetup() {
this.app.use('/', indexRouter);
this.app.use('/users', usersRouter);
this.app.use('/products',
mountProductRouter({ controllerCtx: this.controllerCtx })
);//將mountProductRouter的router傳出來
this.app.use('orders',
mountOrderRouter({ controllerCtx: this.controllerCtx })
);//將mountOrderRouter的router傳出來
}
import { mountOrderRouter } from './routes/order'
時,從 ./routes/order
模組中匯入了一個名為 mountOrderRouter
的函數。接著,這個函數被用在 this.app.use
中,它是 Express.js 用來設置中介軟體和路由器的方法。
this.app.use('orders', mountOrderRouter({ controllerCtx: this.controllerCtx }))
意思是,在 Express 應用程序中,將所有指向 /orders
路徑的請求交給 mountOrderRouter
函數處理,而 mountOrderRouter
函數會返回一個路由器實例。
controllerCtx: this.controllerCtx
是傳遞給 mountOrderRouter
的參數,通常是一些需要在路由處理器中用到的上下文信息。
輸入驗證資料:
驗證結果:
public createOrder: IOrderController['createOrder'] = (req, res, _next) => {
//1.資料驗證
//contents [{id,amount,price}, ...]
if (paymentProvider !== "ECPAY" && paymentProvider !== "PAYPAL")
res.json({ status: "failed", message: "paymentProvider not valid" });
};
npm install express validator
import { isEmpty } from "lodash";
import { body } from 'express-validator';
//1.資料驗證
public createOrderValidator = () => {
//驗證Provider
const paymentProviderValidator = (value: any) => {
return [PaymentProvider.ECPAY, PaymentProvider.PAYPAL].includes(value);
}
//驗證paymentWay
const paymentWayValidator = (value: any) => {
return [PaymentWay.CVS, PaymentWay.PAYPAL].includes(value);
}
//驗證contents資料細項
const contentValidator = (value: OrderContent[]) => {
if (isEmpty(value)) return false;
for (const product of value) {
if ([product.productId, product.amount, product.price].some((vail) => !vail))
return false;
}
return true;
}
return [
//設定驗證不同參數的內容是否合法
body("paymentProvider", "Invalid payment provider")
.custom(paymentProviderValidator)
]
}
}
public createOrderValidator = () => {}
const paymentProviderValidator = (value: any) => {
return [PaymentProvider.ECPAY, PaymentProvider.PAYPAL].includes(value);
}
const paymentWayValidator = (value: any) => {
return [PaymentWay.CVS, PaymentWay.PAYPAL].includes(value);
}
const contentValidator = (value: OrderContent[]) => {
if (isEmpty(value)) return false;
for (const product of value) {
if ([product.productId, product.amount, product.price].some((val) => !val))
return false;
}
return true;
}
return [
body('paymentProvider', 'Invalid payment provider').custom(paymentProviderValidator),
body('paymentWay', 'Invalid payment way').custom(paymentWayValidator),
body('contents', 'Invalid product contents')
.isArray()
.custom(contentValidator),
]
export interface IOrderController
{ createOrderValidator(): ValidationChain[];//新增 }
定義方法結構:
:
router --> order.tsrouter.post(
'/create',
//middleware中介層
controllerCtx.orderController.createOrderValidator(),//新增
//controller create Order正式的內容
controllerCtx.orderController.createOrder)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
驗證結果:
:
:
transactionHandler交易函數:
utils ––>index.ts// transaction 資料庫交易功能
export const transactionHandler = async <T = any>(
//傳入參數
knex: Knex,
callback: (trx: Knex.Transaction) => Promise<T>,
options: {
retryTimes?: number,
maxBackOff?: number,
isolation?: ISOLATION_LEVEL;
} = {}
) => {
const { retryTimes = 100, maxBackOff = 1000, isolation } = options;
let attempts = 0;
//開啟transaction
const execTransaction = async (): Promise<T> => {
const trx = await knex.transaction();
//執行外面傳進來的callback function
try {
if (isolation) await trx.raw(`SET TRANSACTION ISOLATION_LEVEL SERIALIZABLE`)
const result = await callback(trx);
await trx.commit();
return result;
} catch (err: any) {
await trx.rollback();
if (err.code !== '1205') throw err;
if (attempts > retryTimes) throw Error('[Transaction] retry times is up to max');
attempts++;
await sleep(maxBackOff);
return execTransaction();
};
};
//將transaction執行結果傳出去
return await execTransaction();
};
1.knex: Knex
2.callback: (trx: Knex.Transaction) => Promise<T>
3.options = { isolation?: ISOLATION_LEVEL }
const { retryTimes = 100, maxBackOff = 1000, isolation } = options;
options
物件中提取 retryTimes
, maxBackOff
, 和 isolation
三個屬性。let attempts = 0;
const execTransaction = async (): Promise<T> => {
const trx = await knex.transaction();
try {
if (isolation) await trx.raw(`SET TRANSACTION ISOLATION LEVEL ${isolation}`);
const result = await callback(trx);
await trx.commit();
return result;
} catch (err: any) {
await trx.rollback();
if (err.code !== '1205') throw err;
if (attempts > retryTimes) throw Error('[Transaction] retry times is up to max');
attempts++;
await sleep(maxBackOff);
return execTransaction();
}
};
execTransaction
是一個泛型異步函數,返回類型為 Promise<T>,表示該函數會返回一個解決為類型 T 的承諾。//將transaction執行結果傳出去
return await execTransaction();
等待execTransaction
函數執行完成,然後將其結果返回給調用者。
enum ISOLATION_LEVELS {
READ_UNCOMMITTED = 'READ_UNCOMMITTED',
READ_COMMITTED = 'READ_COMMITTED',
REPEATABLE_READ = 'REPEATABLE_READ',
SERIALIZABLE = 'SERIALIZABLE'
}
sleep
函數:用於在重試前等待指定時間。function sleep(maxBackOff: number) {
return new Promise((resolve) => setTimeout(() => resolve(1), maxBackOff));
}