createOrder:
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";
mountOrderRouter
:export const mountOrderRouter = ({
controllerCtx
}: { controllerCtx: ControllerContext }) => {}
let router = express.Router();
router.post('/create', controllerCtx.orderController.createOrder);
return router;
OrderController
實例:createController
:public static createController(
{ knexSql, orderModel, productModel }:
{ knexSql: Knex; orderModel: IOrderModel; productModel: IProductModel })
}
OrderController
實例:return new OrderController({ knexSql, orderModel, productModel });
OrderController
:IOrderController
和 OrderController
:import { IOrderController, OrderController } from "@/controller/orderController";
Knex
:import { Knex } from "knex";
ControllerContext
介面:export interface ControllerContext {
orderController: IOrderController; //新增
}
controllerManger
函數:export const controllerManger = ({ knexSql, modelCtx }:
{ knexSql: Knex; modelCtx: ModelContext; }): ControllerContext => {}
orderController
:const orderController = OrderController.createController({
knexSql,
orderModel: modelCtx.orderModel,
productModel: modelCtx.productModel,
});
return {
orderController
}
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 }))
}
輸入驗證資料:
驗證結果:
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 = () => {
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)) 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),
]
}
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)) 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[];//新增 }
定義方法結構:
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
驗證結果:
:
router.post(
'/create',
//middleware中介層
controllerCtx.orderController.createOrderValidator(),//新增
//controller create Order正式的內容
controllerCtx.orderController.createOrder)
:
:
transactionHandler
的函數,用於處理資料庫事務並提供重試機制:
enum ISOLATION_LEVELS {
READ_UNCOMMITTED = 'READ_UNCOMMITTED',
READ_COMMITTED = 'READ_COMMITTED',
REPEATABLE_READ = 'REPEATABLE_READ',
SERIALIZABLE = 'SERIALIZABLE'
}
const { retryTimes = 100, maxBackOff = 1000, isolation } = options;
let attempts = 0;
execTransaction
函數: 這個內部函數用於執行事務,並在失敗時進行重試。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 的承諾。sleep
函數:用於在重試前等待指定時間。function sleep(maxBackOff: number) {
return new Promise((resolve) => setTimeout(() => resolve(1), maxBackOff));
}