第三方金流串接 – 產品功能Model製作

閱讀時間約 24 分鐘

※ 認識MVC架構:

raw-image

※ 認識lodash:

Lodash 是一個很實用的 JavaScript 函式庫。它提供了很多預設的實用函數,可以更容易地處理數據、操作物件和字串,並控制函數的執行。目的是為了簡化重複性的代碼,減少開發時間和錯誤。

Lodash 中常用的功能:

  1. 陣列處理:_.map, _.filter, _.reduce 等函數,讓你可以方便地操作陣列。
  2. 物件處理:_.assign, _.cloneDeep, _.merge 等函數,讓物件的操作變得簡單。
  3. 數據查詢:_.find, _.findIndex, _.includes 等函數,幫助你快速查找數據。
  4. 函數處理:_.debounce, _.throttle, _.once 等函數,控制函數的調用次數。
  5. 字串處理:_.camelCase, _.kebabCase, _.startCase 等函數,方便格式化字串。

※ 認識@types/lodash:

這個是 TypeScript 的型別定義包,用於 Lodash 函式庫。當你在用 TypeScript 開發時,它提供 Lodash 函數的型別定義,這樣編譯器在使用 Lodash 時得到自動完成功能和型別檢查。如果只是用 JavaScript,那就不需要下載這個型別定義包。

※ 建立Model :

在 MVC (Model-View-Controller) 架構中,Model 是負責處理資料邏輯和業務邏輯的部分。Model 的主要功能有:

  • 資料管理:Model 負責與資料庫交互,執行 CRUD(建立、讀取、更新、刪除)操作。
  • 業務邏輯:Model 處理應用程式的業務邏輯,確保數據的正確性和一致性。
  • 資料封裝:Model 將資料封裝成物件,提供簡單的介面給 Controller 使用。

※ 在 Model 資料夾中建立 base.ts 檔案,處理通用資料庫操作:

raw-image


※ 定義資料庫操作 :

export interface IBase<T> {
findAll(trx?: Knex.Transaction): Promise<T[] | null>;
findOne(id: any, trx?: Knex.Transaction): Promise<T | null>;
create(data: Omit<T, 'id'>, trx?: Knex.Transaction): Promise<T | null>;
update(id: any, data: Partial<Omit<T, 'id'>>, trx?: Knex.Transaction): Promise<T | null>;
delete(id: any, trx?: Knex.Transaction): Promise<void>;
}

程式碼解說:

  • interface 是用來定義物件結構的一種方式。它描述了物件應該有哪些屬性及其類型。
  • IBase 是一個介面,用來定義一組針對資料庫操作的常見方法。
  • <T>只是個占位符,它代表的是未來會被傳入的具體類型。等到其他類別或介面繼承 IBase 時,才會傳入真正的類型。
  • trx?表示這個參數是可選的,不傳遞也沒關係。它是 Knex.js 用於資料庫交易(transaction)的物件。
  • Omit:目的是排除某個屬性。在創建資料時,由系統自動生成或管理的屬性,有ID、時間戳(created_at 和 updated_at)、唯一標識碼(UUID)、系統內部狀態。
  • findAll(trx?: Knex.Transaction): Promise<T[] | null>; 查找所有記錄,返回一個包含所有記錄的 Promise,類型為陣列(T[]),或是空值(null)。
  • findOne(id: any, trx?: Knex.Transaction): Promise<T | null>; 根據 ID 查找單一記錄,返回一個包含該記錄的 Promise,類型為 T 或是空值(null)。
  • create(data: Omit<T, 'id'>, trx?: Knex.Transaction): Promise<T | null>; 創建一個新的記錄,傳入的資料不包含 ID(Omit<T, 'id'>),返回一個包含創建結果的 Promise,類型為 T 或是空值(null)。
  • update(id: any, data: Partial<Omit<T, 'id'>>, trx?: Knex.Transaction): Promise<T | null>: 說的是,更新指定 ID 的記錄,傳入需要修改的屬性,返回一個包含更新結果的 Promise,類型為 T 或是空值(null)。
  • delete(id: any, trx?: Knex.Transaction): Promise<void>; 根據 ID 刪除記錄,返回一個 Promise,完成後不返回數據。
  • T 表示單一的一個資料元素,而 T[] 則表示一個包含多個資料元素的陣列。也就是說,查詢所有資料用 T[] 表示資料陣列,查詢單一資料或創建新資料用 T 表示單個資料。
  • void 是 TypeScript 和 JavaScript 中用來表示「沒有返回值」的函數。

※ 建立資料庫模型基礎框架:

export abstract class Base<T> implements IBase<T> {
protected knexSql: Knex;
protected tableName: string = '';
protected schema = {};
constructor({ knexSql, tableName }: { knexSql: Knex, tableName?: string }) {
this.knexSql = knexSql;
if (tableName) this.tableName = tableName;
}

程式碼解說:

  1. export abstract class Base<T> implements IBase<T>
    • 定義了一個泛型抽象類別 Base這個框架,來實現 IBase<T> 這個介面。這表示 Base 類別必須包含 IBase 介面定義的方法,但因為它是抽象類別,所以不能直接實例化。
  2. protected knexSql: Knex;
    • 定義了一個受保護的屬性 knexSql,類型為 Knex。這表示這個屬性只能在 Base 類別及其子類中使用。目的是增強程式碼的封裝性和安全性
  3. protected tableName: string = '';
    • 定義了一個受保護的屬性 tableName,類型為 string,並預設值為空字串。
  4. protected schema = {};
    • 定義了一個受保護的屬性 schema,預設為空物件。這可能用來存儲資料庫表格的結構定義。
  5. constructor({ knexSql, tableName }: { knexSql: Knex, tableName?: string })
    • 定義了一個建構函數,接受一個包含 knexSql 和可選 tableName 的物件作為參數。
    • this.knexSql = knexSql;:將傳入的 knexSql 賦值給類別屬性。
    • if (tableName) this.tableName = tableName;:如果傳入了 tableName,就將其賦值給類別屬性。

※ 查詢所有記錄:

public findAll = async (trx?: Knex.Transaction) => {
//select col1, col2, ...from tableName
let sqlBuilder = this.knexSql(this.tableName).select(this.schema);

if (trx) sqlBuilder = sqlBuilder.transacting(trx);

const result = await sqlBuilder;

if (isEmpty(result)) return null;

return result.map(this.DBData2DataObject) as T[];
}

程式碼解說:

  1. 方法定義
public findAll = async (trx?: Knex.Transaction) => {}
  • public:表示這個方法是公開的,可以被外部訪問。
  • findAll:方法名稱。
  • async:表示這個方法是非同步的,會返回一個 Promise。
  • trx?: Knex.Transaction:可選的參數 trx,類型為 Knex 的交易物件。
  1. 建立 SQL 查詢
let sqlBuilder = this.knexSql(this.tableName).select(this.schema);
  • 使用 Knex.js 建立一個 SQL 查詢建構物件(sqlBuilder),從 this.tableName 表中選擇 this.schema 中定義的欄位。
  1. 交易處理
if (trx) sqlBuilder = sqlBuilder.transacting(trx);

如果有提供 trx 交易物件,則將查詢納入該交易,以確保資料的一致性和原子性。

  1. 執行查詢
const result = await sqlBuilder;
  • 執行查詢並等待結果。
  1. 結果處理
if (isEmpty(result)) return null;
return result.map(this.DBData2DataObject) as T[];
  • 如果結果為空,返回 null。
  • 如果有結果,會將結果轉換成資料物件,並以 T[] 型別返回。

※ 查詢單筆記錄:

public findOne = async (id: any, trx?: Knex.Transaction) => {
let sqlBuilder = this.knexSql(this.tableName).select(this.schema).where(id);

if (trx) sqlBuilder = sqlBuilder.transacting(trx);

const result = await sqlBuilder;

if (isEmpty(result)) return null;

return this.DBData2DataObject(result[0]) as T;
}

※ 建立單筆記錄:

public create = async (data: Omit<T, 'id'>, trx?: Knex.Transaction) => {
let sqlBuilder = this.knexSql(this.tableName).insert(this.DataObject2DBData(data));

if (trx) sqlBuilder = sqlBuilder.transacting(trx);

const result = await sqlBuilder;

if (isEmpty(result)) return null;
const id = result[0];
return await this.findOne(id, trx);

}

程式碼解說:

  1. 方法定義
public create = async (data: Omit<T, 'id'>, trx?: Knex.Transaction) => {
  • data: Omit<T, 'id'>:傳入的資料不包含 id 屬性,類型為 T(除了 id)。
  1. 建立 SQL 插入查詢
let sqlBuilder = this.knexSql(this.tableName).insert(this.DataObject2DBData(data));
  • 使用 Knex.js 建立一個插入查詢,將資料插入到 this.tableName 表中,資料經過 this.DataObject2DBData(data) 處理。
  1. 結果處理
if (isEmpty(result)) return null;
const id = result[0];
return await this.findOne(id, trx);
    • 如果有結果,提取插入記錄的 id,並使用 findOne 方法根據 id 查詢並返回新創建的記錄。

    ※ 更新單筆記錄:

    public update = async (id: any, data: Partial<Omit<T, 'id'>>, trx?: Knex.Transaction) => {
    let sqlBuilder = this.knexSql(this.tableName).update(this.DataObject2DBData(data).where({ id }));

    if (trx) sqlBuilder = sqlBuilder.transacting(trx);

    await sqlBuilder;

    return await this.findOne(id, trx);}

    ※ 刪除單筆記錄:

    public delete = async (id: any, trx?: Knex.Transaction) => {
    let sqlBuilder = this.knexSql(this.tableName).where({ id }).del();

    if (trx) sqlBuilder = sqlBuilder.transacting(trx);

    await sqlBuilder;

    return

    }
    程式碼解說:
    return;
    • 方法完成後不返回任何數據,因為這個方法的主要目的是刪除指定記錄。

    ※ 將資料庫中的資料轉換為應用程式中的資料格式:

    private DBData2DataObject = (data: any) => {
    const transform = mapValues(data, (value, key) => {
    if (['updatedAt', 'createdAt'].includes(key)) return new Date(value);
    if (isJson(value)) return JSON.parse(value);
    return value;
    });
    return mapKeys(transform, (_value, key) => camelCase(key));
    }

程式碼解說:

  1. mapValues:對資料進行處理,針對每個鍵值對進行操作。
    • 日期轉換:如果鍵是 updatedAt 或 createdAt,將其值轉換為 Date 型別。
    • JSON 解析:如果值是 JSON 字串,解析成對象。
    • 其他值:保持不變。
  2. mapKeys:將鍵轉換為駝峰式命名(camelCase),更符合 JavaScript 的命名慣例。

※ 將應用程式中的資料轉換為資料庫中的資料格式:

private DataObject2DBData = (data: any) => {
const transform = mapValues(data, (value, key) => {
if (['updatedAt', 'createdAt'].includes(key)) return new Date(value);
if (isJson(value)) return JSON.parse(value);
return value;
});
return mapKeys(transform, (_value, key) => snakeCase(key));
}

程式碼解說:

  1. mapKeys:將鍵轉換為蛇形命名(snake_case),更符合資料庫的命名慣例。

總結:

  • DBData2DataObject:將資料庫資料轉換為應用程式資料,使用駝峰式命名。
  • DataObject2DBData:將應用程式資料轉換為資料庫資料,使用蛇形命名。

※ 判斷輸入的字串是否為有效的 JSON 格式:

export const isJson = (value: string) => {
try {
return Boolean(JSON.parse(value));
} catch (e) {
return false;
}
}
  1. 函數定義
export const isJson = (value: string) => {}
  • export:表示這個函數可以被其他模組引用。
  • const isJson:定義了一個常數函數,名稱為 isJson。
  • (value: string):函數接受一個字串類型的參數 value。
  1. try 區塊:
try {
return Boolean(JSON.parse(value));
}
  • JSON.parse(value):嘗試將字串解析為 JSON。
  • Boolean():將解析結果轉換為布林值。如果解析成功,返回 true。
  1. catch 區塊
catch (e) {
return false;
}
  • 如果解析失敗,捕捉錯誤並返回 false。

※ Model資料夾內 建立產品檔案 – product.ts

raw-image

※ 定義Product 的 介面:

export interface Product {
//符合 TypeScript 的語法和類型系統
id: number;
name: string;
amount: number;
description: string;
pre_order: number;
price: number;
}

程式碼解說:

  • id: number;:產品的唯一標識符,類型為數字。
  • name: string;:產品名稱,類型為字串。
  • amount: number;:產品數量,類型為數字。
  • description: string;:產品描述,類型為字串。
  • pre_order: number;:預購數量,類型為數字。
  • price: number;:產品價格,類型為數字。

※ 定義IProductModel 的 介面:

export interface IProductModel extends IBase<Product> {}

程式碼解說:

  1. export interface IProductModel extends IBase<Product> {}:
    • export:表示這個介面可以被其他模組引用。
    • interface IProductModel:定義了一個介面 IProductModel。
    • extends IBase<Product>:表示 IProductModel 繼承自 IBase<Product>。
  2. IBase<Product>:這表示 IProductModel 繼承了 IBase 介面的所有方法和屬性,而這些方法和屬性的泛型類型為 Product。這意味著 IProductModel 會擁有 IBase 定義的所有 CRUD 操作(如 findAll, findOne, create, update, delete),但這些操作針對的是 Product 類型的資料。

簡單來說,IProductModel 介面是 IBase 介面的具體化,專門用於操作 Product 類型的資料。這樣定義能讓代碼更具模組化和可重用性。

※ 實作class:

  1. 類別定義
export class ProductModel extends Base<Product> implements IProductModel {}

程式碼解說:

  • export class ProductModel:定義並導出一個名為 ProductModel 的類別。
  • extends Base<Product>:表示這個類別繼承自 Base<Product> 類別。
  • implements IProductModel:表示這個類別實現了 IProductModel 介面。
  1. tableName 屬性:
tableName = 'products';

程式碼解說:

  • 定義資料表的名稱為 products。
  1. schema 屬性
schema = {
id: 'id',
name: 'name',
amount: 'amount',
description: 'description',
pre_order: 'pre_order',
price: 'price'
};

程式碼解說:

  • 定義資料表的結構,把資料物件的屬性對應到資料庫的欄位。
  1. 建構函數
constructor({ knexSql, tableName }: { knexSql: Knex; tableName?: string }) {
super({ knexSql, tableName });
}
  • 接受一個包含 knexSql 和可選 tableName 的物件作為參數,並呼叫父類別的建構函數super),將這些參數傳遞給父類別進行初始化。
  1. 靜態方法 createModel
static createModel = ({
knexSql,
tableName,
}: {
knexSql: Knex;
tableName?: string;
}) => {
return new ProductModel({ knexSql, tableName })
}

程式碼解說:

  • static:表示這是一個靜態方法,可以直接使用 ProductModel.createModel() 來調用。
  • createModel:方法名稱。
  • 參數:接受一個包含 knexSql 和可選 tableName 的物件。
  • 返回:創建並返回一個 ProductModel 的新實例,也就無需每次手動傳入參數來初始化它。
    全端網頁開發專業知識分享
    留言0
    查看全部
    發表第一個留言支持創作者!
    ※ 認識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_
    ※ 下載Typescript 建立 TypeScript 和 Express 的基本目錄結構和必要配置。 npm install -g typescript-express-generator 建立模板引擎: ts-express --view=ejs server 安裝node_m
    ※ 必備開發環境: Node.Js環境。 Npm或yarn套件管理工具。 Docker倉庫套件:可以快速建立MySQL的資料庫。 下載 Docker Desktop:Docker Desktop for Windows。 ※ Docker快速建立MySQL資料庫,使用步驟: 準備Dock
    ※ 開發第三方金流串接使用工具: Typescript。 MySQL。 ※ 開發第三方金流串接使用技術: 基礎的docker使用方式。 用Typescript開發Node.JS的金流服務伺服器。 前端框架VueJs。 ※ 常見第三方金流平台介紹: PayPal: 特色: 全球最大
    ※ 具備三項工具: 編輯器 終端機 瀏覽器 ※ 步驟一:建立資料夾 mkdir mongodb-demo cd mongodb-demo code . ※ 步驟二:初始化,建立package.json npm init -y ※ 步驟三:安裝網頁伺服器 – Express 因為
    ※ 認識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_
    ※ 下載Typescript 建立 TypeScript 和 Express 的基本目錄結構和必要配置。 npm install -g typescript-express-generator 建立模板引擎: ts-express --view=ejs server 安裝node_m
    ※ 必備開發環境: Node.Js環境。 Npm或yarn套件管理工具。 Docker倉庫套件:可以快速建立MySQL的資料庫。 下載 Docker Desktop:Docker Desktop for Windows。 ※ Docker快速建立MySQL資料庫,使用步驟: 準備Dock
    ※ 開發第三方金流串接使用工具: Typescript。 MySQL。 ※ 開發第三方金流串接使用技術: 基礎的docker使用方式。 用Typescript開發Node.JS的金流服務伺服器。 前端框架VueJs。 ※ 常見第三方金流平台介紹: PayPal: 特色: 全球最大
    ※ 具備三項工具: 編輯器 終端機 瀏覽器 ※ 步驟一:建立資料夾 mkdir mongodb-demo cd mongodb-demo code . ※ 步驟二:初始化,建立package.json npm init -y ※ 步驟三:安裝網頁伺服器 – Express 因為
    你可能也想看
    Google News 追蹤
    Thumbnail
    接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
    Thumbnail
    🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
    Thumbnail
    一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
    Thumbnail
    ※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
    Thumbnail
    ※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
    Thumbnail
    在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
    Thumbnail
    本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
    Thumbnail
    TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
    Thumbnail
    因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
    Thumbnail
    題目敘述 題目會給我們一組定義好的界面和需求,要求我們設計一個資料結構,可以滿足平均O(1)的插入元素、刪除元素、隨機取得元素的操作。 RandomizedSet() 類別建構子 bool insert(int val) 插入元素的function界面 bool remove(int val
    Thumbnail
    接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
    Thumbnail
    🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
    Thumbnail
    一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
    Thumbnail
    ※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
    Thumbnail
    ※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
    Thumbnail
    在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
    Thumbnail
    本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
    Thumbnail
    TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
    Thumbnail
    因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
    Thumbnail
    題目敘述 題目會給我們一組定義好的界面和需求,要求我們設計一個資料結構,可以滿足平均O(1)的插入元素、刪除元素、隨機取得元素的操作。 RandomizedSet() 類別建構子 bool insert(int val) 插入元素的function界面 bool remove(int val