第三方金流串接 – 資料庫交易設定

更新於 發佈於 閱讀時間約 37 分鐘

※ 訂單管理器(orderController)具有兩種功能:

  • 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​
}
}

※ 確認 req.body 是否正確傳遞了所需的數據: 

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' });

};

程式碼解說:

1.方法定義:

  • public createOrder:定義一個公開的 createOrder 方法,符合 IOrderController 介面中 createOrder 方法的定義。
  • (req, res, _next):這個方法接受三個參數:req(請求對象)、res(回應對象)和 _next(下一個中介軟體函數,這裡未使用)。

2.請求主體

  • 從 req.body 中提取 paymentProvider、paymentWay 和 contents 屬性。這些屬性應該符合 CreateOrderRequestParams 介面。
  • 在後續的程式中可能會需要重新賦值這些變數,所以使用let。

3.紀錄日誌

  • 將 paymentProvider、paymentWay 和 contents 的值輸出到控制台,用於除錯。

4.回應客戶端

  • res.json 是 Express.js 提供的方法,用來向客戶端回傳 JSON 格式的資料。
  • 這裡回傳了一個物件 { status: 'success' },表示操作已成功完成。

createOrder 的主要用途

  1. 接收請求:從客戶端接收一個包含訂單詳情的 HTTP 請求。
  2. 解析數據:從請求主體中提取訂單的詳細資訊,例如支付提供者、支付方式和訂單內容。
  3. 處理邏輯:根據提取的數據執行必要的業務邏輯,例如計算總金額、檢查產品庫存等。
  4. 保存訂單:將處理後的訂單數據保存到資料庫中。
  5. 回應客戶端:返回一個成功或失敗的回應給客戶端,通常包括訂單的詳細資訊或錯誤訊息。

※ 建立order路由:routes –––> order.ts

raw-image

1.匯入模組:

import express from 'express';
import { ControllerContext } from "@/manager/controllerManager";

程式碼解說:

  • express:從 express 模組匯入,用來創建路由器。
  • ControllerContext:從 controllerManager 匯入,代表控制器上下文的型別。

2.安裝訂單路由器:

export const mountOrderRouter = ({
controllerCtx
}: { controllerCtx: ControllerContext }) => {}

程式碼解說:

  • 定義並導出 mountOrderRouter 函數。
  • 函數接受一個參數 controllerCtx,類型為 ControllerContext,其中包含了所有的控制器。
  • 通過這個參數,可以訪問並調用 orderController 的方法來處理訂單相關的請求。

3.使用express router:

let router = express.Router();
router.post('/create', controllerCtx.orderController.createOrder);

程式碼解說:

  • 使用 express.Router() 創建一個新的路由器實例。
  • 使用 router.post('/create', controllerCtx.orderController.createOrder) 設置一個 POST 路徑 /create,當這個路徑被請求時,調用 orderController 的 createOrder 方法來處理請求。

4.返回路由器:

return router;

程式碼解說:

  • 返回已設定好的路由器,這樣可以將它掛載到應用程式的主路徑上。

※ ControllerContext新增orderController :manager –––> controllerManager.ts

export interface ControllerContext {
productController: IProductController;
orderController: IOrderController;
}

程式碼解說:

  • 新增 orderController 屬性到 ControllerContext 介面,表示控制器上下文中包含了訂單控制器。

※ ControllerContext新增orderController 實例:manager –––> controllerManager.ts

 const orderController = OrderController.createConstructor({
orderModel: modelCtx.orderModel,
})

程式碼解說:

  • 透過 createConstructor 方法創建 orderController,並且確保這個控制器在初始化時擁有所需的 orderModel 作為其依賴項,以便在後續的業務邏輯中使用

※ controllerManager.ts中建立 OrderController :manager –––> modelManager.ts

raw-image
import { IOrderModel, OrderModel } from "@/model/order";

//在一個地方管理和使用不同的模型
//定義一個模型相關的環境資訊
export interface ModelContext {
orderModel: IOrderModel;
}

//與資料庫相關的初始化操作
export const modelManager = ({ knexSql }: { knexSql: Knex }): ModelContext => {
const orderModel = OrderModel.createModel({ knexSql })
return { orderModel }
}

程式碼解說:

  • IOrderController:介面,定義了訂單控制器應該具備的方法和結構。
  • OrderController:類別,實現了 IOrderController 介面,包含處理訂單相關邏輯的方法。
  • 定義了一個名為 ModelContext 的介面,這個介面包含了一個屬性 orderModel,其類型為 IOrderModel
  • 參數解構: modelManager函數接收一個參數 { knexSql },其類型為 { knexSql: Knex }。這意味著 knexSql 是一個 Knex 類型的物件,這個物件通常用於與資料庫進行互動。
  • 創建 orderModel: 在函數內部,我們使用 OrderModel.createModel 方法創建了一個 orderModel 實例,並將 knexSql 作為參數傳遞給 createModel 方法,這樣 orderModel 就能與資料庫進行互動。
  • 返回 ModelContext 物件: 最後,我們返回一個包含 orderModel 屬性的物件,這個物件的類型為 ModelContext。


※ 建立createController來製造一個新的 OrderController 實例Controller –––>OrderController.ts

raw-image


1.靜態方法 createController

public static createController(
{ knexSql, orderModel, productModel }:
{ knexSql: Knex; orderModel: IOrderModel; productModel: IProductModel })

}

程式碼解說:

  • 靜態方法:定義一個靜態方法 createController,這意味著你可以直接通過類別來調用這個方法,而不需要創建類別的實例。
  • 參數:
    • knexSql:資料庫連接實例,用於資料庫操作。
    • orderModel:訂單模型,用於操作和管理訂單資料。
    • productModel:產品模型,用於操作和管理產品資料。

2.創建並返回 OrderController 實例

return new OrderController({ knexSql, orderModel, productModel });

程式碼解說:

  • 使用傳入的參數創建一個新的 OrderController 實例。
  • 將 knexSql, orderModel, 和 productModel 傳遞給 OrderController 的建構函數進行初始化。

※ 新增orderController (訂單控制器):manager -->controllerManager.ts

raw-image


1. 定義 ControllerContext 介面

export interface ControllerContext {
orderController: IOrderController;
}

程式碼解說:

ControllerContext 介面,新增orderController (訂單控制器)

2. 定義 controllerManager 函數

export const controllerManager = ({ knexSql, modelCtx }: {
knexSql: Knex;
modelCtx: ModelContext
}): ControllerContext => {

程式碼解說:

新增加knexSql: 一個 Knex 的實例,用於與資料庫進行交互。

3. 創建 orderController

const orderController = OrderController.createController({
knexSql,
orderModel: modelCtx.orderModel,
productModel: modelCtx.productModel,
});

程式碼解說:

這部分創建了 orderController,使用 OrderControllercreateController 方法,並傳入 knexSqlorderModelproductModel



※ 在app.ts新增程式碼 :src --> app.ts

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 }))

  }

1.類別定義和建構函數

程式碼解說:

  • knexSql 屬性:定義了一個私有屬性 knexSql,類型為 Knex,用於資料庫操作。
  • 建構函數:
    • 初始化資料庫連接:調用 createDatabase() 函數初始化 knexSql。
    • 初始化控制器上下文:使用 controllerManger 函數,傳入 knexSql 來初始化 controllerCtx。

2.路由設置方法

程式碼解說:

  • 掛載訂單路由:使用 this.app.use 將訂單路由掛載到 /orders 路徑,並調用 mountOrderRouter 函數,傳入 controllerCtx。

※ 將訂單相關的路由配置到 Express 應用中 :src --> app.ts

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 的參數,通常是一些需要在路由處理器中用到的上下文信息。

※ 在postman驗證 

輸入驗證資料:

raw-image
raw-image


驗證結果:

raw-image
raw-image

※ 資料驗證方式Controller --> orderController.ts

1. 第一種驗證方式

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" });

};

程式碼解說:

  • 資料驗證:在處理訂單創建之前,先對請求資料進行驗證。
  • paymentProvider 驗證:
    • 檢查 paymentProvider 是否為 "ECPAY" 或 "PAYPAL"。
    • 如果 paymentProvider 不是這兩者之一,回應客戶端一個 JSON 物件,表示操作失敗,並包含錯誤訊息 "paymentProvider not valid"。

2.第二種驗證方式使用npm套件做資料驗證 — express validator

安裝軟體:

npm install express validator

使用express validator驗證:Controller --> orderController.ts

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)
    ]
  }
}

程式碼解說:

1.方法定義:

public createOrderValidator = () => {}
  • 這是一個公開的方法,為了驗證訂單創建請求,返回一組驗證規則。

2.驗證函數:

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;

}
  • 驗證支付提供者是否為合法值。
  • 驗證訂單內容是否為非空陣列,且每個產品的 productId、amount 和 price 都存在。

3.返回驗證規則:

return [
body('paymentProvider', 'Invalid payment provider').custom(paymentProviderValidator),
body('paymentWay', 'Invalid payment way').custom(paymentWayValidator),
body('contents', 'Invalid product contents')
.isArray()
.custom(contentValidator),
]
  • 設定驗證不同參數的內容是否合法:
    • paymentProvider 必須是合法的支付提供者。
    • paymentWay 必須是合法的支付方式。
    • contents 必須是非空陣列且符合內容驗證器規則。

使用createOrderValidator() 函數來驗證數據 

export interface IOrderController

{ createOrderValidator(): ValidationChain[];//新增 }

程式碼解說:

定義方法結構

  • createOrderValidator() 方法被定義在 IOrderController 介面中,強制所有實現這個介面的類別都必須實現這個方法。
  • 返回 ValidationChain[]:這表明方法會返回一組驗證規則,這些規則用於驗證訂單創建請求中的參數。

※ 加入中介層router --> order.ts

raw-image
 router.post(
'/create',
//middleware中介層
controllerCtx.orderController.createOrderValidator(),//新增
//controller create Order正式的內容
controllerCtx.orderController.createOrder)

程式碼解說:

  • 中介層(middleware)使用:
    • 在設置路由時,將這個驗證方法作為中介層,確保每次收到請求時都先進行數據驗證。
  • 處理邏輯:如果驗證通過,則由 createOrder 方法處理請求。

驗證數據是否錯誤 

 if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}

程式碼解說:

  • if (!errors.isEmpty()):檢查是否有任何驗證錯誤。
  • return res.status(400).json({ errors: errors.array() }):
    • 如果有錯誤,則返回一個 HTTP 400 錯誤狀態碼,表示請求不合法。
    • 同時返回一個 JSON 對象,包含所有驗證錯誤。

驗證結果:

raw-image


※ transaction 資料庫交易主要流程

  1. 開始交易:使用 knex.transaction() 開始交易。
  2. 設置隔離級別:如果提供了 isolation,則設置交易隔離級別。
  3. 執行回調函數:調用回調函數執行具體的交易操作。
  4. 提交事務:如果回調函數成功,提交交易。
  5. 處理錯誤:如果回調函數失敗,回滾交易並根據錯誤類型進行重試或拋出異常。
  6. 重試機制:在特定錯誤(如死鎖)發生時,進行重試,直到達到最大重試次數。

※ 將資料寫入資料庫transactionHandler交易函數

※ transaction 資料庫交易功能utils ––>index.ts

raw-image
// 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
    • knex 是一個資料庫連接實例,用於執行 SQL 查詢。
  • 2.callback: (trx: Knex.Transaction) => Promise<T>
    • callback 是一個帶有交易 (transaction) 物件 trx 的回呼函式 (callback function)。這個回呼函式需要返回一個 Promise,並且可以執行多個資料庫操作。

3.options = { isolation?: ISOLATION_LEVEL }

    • retryTimes?: number:最大重試次數,預設值為 100。
    • maxBackOff?: number:最大回退時間(毫秒),預設值為 1000。
    • options 是一個選用的參數物件,可以指定交易的隔離級別。isolation 可以是不同的隔離級別,定義了交易之間的隔離程度。

4.解構選項參數

const { retryTimes = 100, maxBackOff = 1000, isolation } = options;

程式碼解說:

  • 利用解構賦值 (destructuring assignment) 從 options 物件中提取 retryTimes, maxBackOff, 和 isolation 三個屬性。
  • retryTimes:設置重試次數,如果未提供則默認為 100。
  • maxBackOff:設置最大回退時間(毫秒),如果未提供則默認為 1000。
  • isolation:事務隔離級別,默認為未設置。

5.初始化重試計數器

let attempts = 0;

程式碼解說:

  • 初始化 attempts(追蹤或交易重試的次數) 變數為 0,表示尚未進行任何重試。
  • 當交易處理失敗並需要重試時,attempts 會自增,用來追蹤已進行的重試次數。

6.開始一個新的資料庫交易

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 的承諾。
  • 使用 knex.transaction() 開始一個新的交易,並將交易對象存儲在 trx 變數中。
  • 如果指定了 isolation(隔離級別),則設置該事務的隔離級別。
  • 執行傳入的回調函數 callback,並將 trx 作為參數傳遞。
  • 如果回調函數成功,則提交交易並返回結果。
  • 如果回調函數失敗,則回滾交易。
  • 如果錯誤代碼不是 1205(通常表示死鎖),則重新拋出錯誤。
  • 檢查重試次數是否超過 retryTimes,如果超過則拋出錯誤。
  • 增加 attempts 計數器,等待指定的回退時間後,重新執行 execTransaction。

7.將transaction執行結果傳出去

 //將transaction執行結果傳出去
  return await execTransaction();

程式碼解說:

等待execTransaction函數執行完成,然後將其結果返回給調用者。


※ 定義資料隔離性介面:

enum ISOLATION_LEVELS {
READ_UNCOMMITTED = 'READ_UNCOMMITTED',
READ_COMMITTED = 'READ_COMMITTED',
REPEATABLE_READ = 'REPEATABLE_READ',
SERIALIZABLE = 'SERIALIZABLE'
}

程式碼解說:

  1. READ_UNCOMMITTED:
    • 讀取未提交:允許一個事務讀取另一個事務尚未提交的變更。這種隔離級別風險最高,因為會有「髒讀」的問題。
  2. READ_COMMITTED:
    • 讀取已提交:只允許讀取已提交的變更,防止「髒讀」,但可能會有「不可重複讀」的問題,即同一事務中讀取的數據可能會改變。
  3. REPEATABLE_READ:
    • 可重複讀:確保同一事務中的多次讀取結果一致,防止「髒讀」和「不可重複讀」,但可能會有「幻讀」問題,即在事務期間其他事務新增的記錄會被讀取到。
  4. SERIALIZABLE:
    • 可序列化:最高級別的隔離,所有事務按順序執行,防止「髒讀」、「不可重複讀」和「幻讀」。這種隔離級別會帶來較高的性能開銷。

※ 新增sleep 函數:用於在重試前等待指定時間。

function sleep(maxBackOff: number) {

return new Promise((resolve) => setTimeout(() => resolve(1), maxBackOff));

}

程式碼解說:

  • maxBackOff:表示延遲的時間,單位為毫秒。
  • setTimeout 用於設置一個定時器,在 maxBackOff 毫秒後執行回調函數。
  • 回調函數中調用 resolve(1),解決(resolve)這個承諾,表示延遲結束。






















全端網頁開發專業知識分享
留言
avatar-img
留言分享你的想法!

































































※ 訂單開立流程 → 完結的流程 前端資料驗證。 將商品的數量寫入(預扣)→ id。 利用ID去打第三方金流的API來產生第三方金流的訂單。 當使用者繳完錢之後,第三方金流他會打我們提供的update資訊的API。 ※ 從前端需要傳入的資料: 商品ID、商品數量、使用哪個payment
※ 路由設定: 在routes資料夾中,建立product.ts: ※ 設置產品相關的路由: 1.匯入模組: import express from 'express'; import { ControllerContext } from '@/manager/controllerM
※ 建立一個管理者的資料夾,負責建立不同的管理 — manager: ※ 在manager資料夾中,建立modelManager.ts: ※ 設定資料庫模型的管理: 匯入模組: import { IProductModel, ProductModel } from "@/model/prod
※ 認識MVC架構: ※ 認識lodash: Lodash 是一個很實用的 JavaScript 函式庫。它提供了很多預設的實用函數,可以更容易地處理數據、操作物件和字串,並控制函數的執行。目的是為了簡化重複性的代碼,減少開發時間和錯誤。 Lodash 中常用的功能: 陣列處理:_.map
※ 認識Knex.js: Knex.js是一個專門串接資料庫的抽象化層,它支援多種關聯式資料庫,包括 PostgreSQL、MySQL、MariaDB、SQLite3、Oracle 和 Amazon Redshift 等。好處是連接以上的資料庫時,可以直接使用Knex.js的語法,就會自動創建出
※ 架構設計 ※ 資料庫規劃 id:流水號,唯一代替產品名稱的辨識代碼,AUTO_INCREMENT。 name:VARCHAR(255),NOT NULL。 amount:INTEGER(整數),UNSIGNED(不能是負數)。 description:TEXT,描述產品。 pre_
※ 訂單開立流程 → 完結的流程 前端資料驗證。 將商品的數量寫入(預扣)→ id。 利用ID去打第三方金流的API來產生第三方金流的訂單。 當使用者繳完錢之後,第三方金流他會打我們提供的update資訊的API。 ※ 從前端需要傳入的資料: 商品ID、商品數量、使用哪個payment
※ 路由設定: 在routes資料夾中,建立product.ts: ※ 設置產品相關的路由: 1.匯入模組: import express from 'express'; import { ControllerContext } from '@/manager/controllerM
※ 建立一個管理者的資料夾,負責建立不同的管理 — manager: ※ 在manager資料夾中,建立modelManager.ts: ※ 設定資料庫模型的管理: 匯入模組: import { IProductModel, ProductModel } from "@/model/prod
※ 認識MVC架構: ※ 認識lodash: Lodash 是一個很實用的 JavaScript 函式庫。它提供了很多預設的實用函數,可以更容易地處理數據、操作物件和字串,並控制函數的執行。目的是為了簡化重複性的代碼,減少開發時間和錯誤。 Lodash 中常用的功能: 陣列處理:_.map
※ 認識Knex.js: Knex.js是一個專門串接資料庫的抽象化層,它支援多種關聯式資料庫,包括 PostgreSQL、MySQL、MariaDB、SQLite3、Oracle 和 Amazon Redshift 等。好處是連接以上的資料庫時,可以直接使用Knex.js的語法,就會自動創建出
※ 架構設計 ※ 資料庫規劃 id:流水號,唯一代替產品名稱的辨識代碼,AUTO_INCREMENT。 name:VARCHAR(255),NOT NULL。 amount:INTEGER(整數),UNSIGNED(不能是負數)。 description:TEXT,描述產品。 pre_
你可能也想看
Google News 追蹤
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
Thumbnail
在前一篇我們已經成功地建立簽核表單及簽核節點並關聯回請假表單,而本篇會接著介紹如何管理簽核節點狀態並同步更新簽核表單狀態。
Thumbnail
本文介紹瞭如何在後端系統開發時設計不同表單的簽核流程,包括請假表單和採購表單。以及如何動態生成簽核表單,並建立簽核節點。另外還介紹瞭如何利用繼承來簡化簽核流程的設定。
Thumbnail
前面已經安裝好IIS後,並且也新建站台了,那麼接下來這篇就會分享如何使用它
※ Express 專案步驟筆記清單 Node.js 環境建置核對 新增專案資料夾 設定 package.json npm init -y 設定程式入口為 app.js 安裝 Express:npm install express 設定主程式 app.js 建構應用程式伺服器 設定
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
Thumbnail
Accept:用戶端能夠接收的內容類型。 Accept: text/plain, text/html Accept-Charset:瀏覽器可以接受的字元編碼集。 Accept-Charset: utf8 Accept-Encoding:指定瀏覽器可以支援的web伺服器返回內容壓縮編碼
Thumbnail
HTTP伺服器端 package main import ( "net/http" ) type Refer struct { handler http.Handler refer string } //實現中介軟體邏輯​ func (this *Refer) ServeHTTP(
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
Thumbnail
在前一篇我們已經成功地建立簽核表單及簽核節點並關聯回請假表單,而本篇會接著介紹如何管理簽核節點狀態並同步更新簽核表單狀態。
Thumbnail
本文介紹瞭如何在後端系統開發時設計不同表單的簽核流程,包括請假表單和採購表單。以及如何動態生成簽核表單,並建立簽核節點。另外還介紹瞭如何利用繼承來簡化簽核流程的設定。
Thumbnail
前面已經安裝好IIS後,並且也新建站台了,那麼接下來這篇就會分享如何使用它
※ Express 專案步驟筆記清單 Node.js 環境建置核對 新增專案資料夾 設定 package.json npm init -y 設定程式入口為 app.js 安裝 Express:npm install express 設定主程式 app.js 建構應用程式伺服器 設定
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
Thumbnail
Accept:用戶端能夠接收的內容類型。 Accept: text/plain, text/html Accept-Charset:瀏覽器可以接受的字元編碼集。 Accept-Charset: utf8 Accept-Encoding:指定瀏覽器可以支援的web伺服器返回內容壓縮編碼
Thumbnail
HTTP伺服器端 package main import ( "net/http" ) type Refer struct { handler http.Handler refer string } //實現中介軟體邏輯​ func (this *Refer) ServeHTTP(