iT邦幫忙

2023 iThome 鐵人賽

1
自我挑戰組

用ChatGPT詠唱來完成工作與點亮前後端技能樹系列 第 52

【Day52】ChatGPT幫我完成工作:分散式詠唱來寫NodeJS & JavaScript!

  • 分享至 

  • xImage
  •  

上一篇我們進行的是將「整個程式碼」的重構開發都交給ChatGPT
但這樣要處理工作的話,要花太多時間在詠唱

這一篇會介紹比較分片、碎片式的方式來撰寫程式!
什麼意思呢?

也就是說,為了減少詠唱所花費的精神,我們交給ChatGPT的任務會選擇比較單一的內容
不會讓ChatGPT從頭幫我們又搭建環境、又要設置NestJS的專案結構、模組與路由
又要它同時兼顧一些零零叩叩的規則與要求

目前的ChatGPT免費版,也就是GPT-3.5,要做到這樣的程度,可能還是有一些難度

所以,我們這篇與ChatGPT之間的關係,就像是美髮院的設計師與助理的關係
助理要負責洗頭、把頭髮吹乾、打掃地板、整理推車
甚至在設計師調好染膏或藥水之類的,要幫忙上染膏

這些工作都很花時間、費時,也有專業度的成分在裡面
但對設計師來說就是瑣碎、設計師自己沒有時間去做的事情

ChatGPT也是一樣,我們現在要給他交付的任務,就是這樣單一任務
他就能夠把任務做得很好

一起來看範例,Go Go!

1. Type 裝飾器⇒ Type定義加上swagger + class-validator

有關swagger與 class-validator 的介紹可以看這兩篇:
【Day44】ChatGPT請教教我:NestJS!(三)- Controller & 資料驗證 !class-validator & class-transformer!
【Day47】ChatGPT請教教我:NestJS!(六)- API測試兼文件!swagger!

對我來說,先定義好最基本的TypeScript型別,然後再交給ChatGPT幫我補上對應的裝飾器
可以省下我很多時間,省去既要定義 name: string 又要加上 @IsString() 裝飾器的麻煩

直接上範例!我這邊有一個type的一部分
以下的type範例出自於我自己的side project,與商用、工作用程式碼無關

type AppendTerm = {
  name: string;
  money: number;
};

https://ithelp.ithome.com.tw/upload/images/20231106/20135750mH8HYUc7I4.png

好的,我會幫你將這段程式碼改寫成 DTO 並加上 class-validator 與 swagger 的裝飾器。以下是改寫後的程式碼:

import { IsString, IsNumber } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class AppendTermDto {
  @ApiProperty({
    description: '名稱',
    example: '節氣活動'
  })
  @IsString()
  name: string;

  @ApiProperty({
    description: '金額',
    example: 1000
  })
  @IsNumber()
  money: number;
}

這樣就將 AppendTerm 改寫成了一個名為 AppendTermDto 的 DTO,並加上了 class-validator 與 swagger 的裝飾器,同時也加上了說明與範例。如果還有其他需要幫忙的地方,請隨時告訴我。

接下來的type轉換都不需要再給其他內容了,直接貼都沒問題,像這樣
https://ithelp.ithome.com.tw/upload/images/20231106/20135750AtRZjNsKh3.png

好的,我會幫你將這段程式碼改寫成 DTO 並加上 class-validator 與 swagger 的裝飾器。以下是改寫後的程式碼:

import { IsString, IsNumber, ValidateNested, ArrayNotEmpty } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';

export class AppendTermDto {
  @ApiProperty({
    description: '名稱',
    example: '節氣活動'
  })
  @IsString()
  name: string;

  @ApiProperty({
    description: '金額',
    example: 1000
  })
  @IsNumber()
  money: number;
}

export class MenuDataDto {
  @ApiProperty({
    description: '名稱',
    example: '套餐一'
  })
  @IsString()
  name: string;

  @ApiProperty({
    description: '金額',
    example: 2000
  })
  @IsNumber()
  money: number;

  @ApiProperty({
    description: '附加項目',
    type: [AppendTermDto],
    example: [{ name: '節氣活動', money: 1000 }]
  })
  @ValidateNested({ each: true })
  @ArrayNotEmpty()
  @Type(() => AppendTermDto)
  appendMenu: AppendTermDto[];
}

這樣就將 MenuData 改寫成了一個名為 MenuDataDto 的 DTO,並加上了 class-validator 與 swagger 的裝飾器,同時也加上了說明與範例。如果還有其他需要幫忙的地方,請隨時告訴我。

然後再進階一層,這是一個有用到剛剛AppendTerm 之外,還有另外用Enum的
是一個更複雜的case
https://ithelp.ithome.com.tw/upload/images/20231106/20135750cjDDG4NDZy.png

enum ReceiptType {
  '尚未出貨',
  '已出貨,物流中',
  '已到貨尚未簽收',
  '已簽收完畢',
}

type GoodsData = {
   name: string,
   money: number,
   number: number , //數量 ,需要>=1
   note: string ,
   appendTermList: Array<AppendTerm>, // 額外指定項目
   receiptType:ReceiptType,
   uid: string,
}

好的,我會幫你將這段程式碼改寫成 DTO 並加上 class-validator 與 swagger 的裝飾器,包括處理 enum 類型。以下是改寫後的程式碼:

import { IsString, IsNumber, Min, IsEnum, ArrayNotEmpty, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger';

enum ReceiptType {
  '尚未出貨',
  '已出貨,物流中',
  '已到貨尚未簽收',
  '已簽收完畢',
}

export class GoodsDataDto {
  @ApiProperty({
    description: '名稱',
    example: '商品名稱'
  })
  @IsString()
  name: string;

  @ApiProperty({
    description: '金額',
    example: 1000
  })
  @IsNumber()
  money: number;

  @ApiProperty({
    description: '數量',
    example: 5
  })
  @IsNumber()
  @Min(1)
  number: number;

  @ApiProperty({
    description: '備註',
    example: '商品備註'
  })
  @IsString()
  note: string;

  @ApiProperty({
    description: '附加項目',
    type: [AppendTermDto],
    example: [{ name: '節氣活動', money: 1000 }]
  })
  @ValidateNested({ each: true })
  @ArrayNotEmpty()
  @Type(() => AppendTermDto)
  appendTermList: AppendTermDto[];

  @ApiProperty({
    description: '收據狀態',
    enum: ReceiptType,
    enumName: 'ReceiptType',
    example: ReceiptType['尚未出貨']
  })
  @IsEnum(ReceiptType, {
    message: '請提供有效的收據狀態'
  })
  receiptType: ReceiptType;

  @ApiProperty({
    description: '用戶 ID',
    example: 'user123'
  })
  @IsString()
  uid: string;
}

這樣就將 GoodsData 改寫成了一個名為 GoodsDataDto 的 DTO,並加上了 class-validator 與 swagger 的裝飾器,同時也加上了說明與範例,並處理了 enum 類型的欄位。如果還有其他需要幫忙的地方,請隨時告訴我。

全部舒舒服服。

複雜的裝飾器設定,以及繁雜的description、example都幫你根據命名自動判斷
只要順過一次,確定ChatGPT的判斷是不是正確的,再做範例內容或是要求的微幅調整就好

而且假設我對限制條件的裝飾器不熟,也可以這樣問
https://ithelp.ithome.com.tw/upload/images/20231106/20135750e29FaKqfPB.png

你可以使用 MaxLength 裝飾器來限制 note 欄位的最大長度為 100 字。以下是修改後的程式碼:

import { IsString, IsNumber, Min, IsEnum, ArrayNotEmpty, ValidateNested, MaxLength } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger';

// ... (前面的程式碼)

export class GoodsDataDto {
  // ... (其他欄位)

  @ApiProperty({
    description: '備註',
    example: '商品備註'
  })
  @IsString()
  @MaxLength(100, { message: '備註不能超過100字' })
  note: string;

  // ... (其他欄位)
}

現在 note 欄位的最大長度被限制為 100 字。如果還有其他需要幫忙的地方,請隨時告訴我。

可以省去google 「class-validator string長度限制 」之類字眼的時間

2. 複雜的SQL語句撰寫

有關SQL語法的介紹可以看這幾篇:
【Day38】ChatGPT請教教我:資料庫!MySQL!MySQL Workbench!
【Day39】ChatGPT請教教我:基礎SQL語法!NodeJS連結SQL!
【Day40】ChatGPT請教教我:進階SQL語法!JOIN!

雖然會了SQL語法,但很多時候可能因為需要的query比較複雜
操作資料的技巧又不夠純熟的時候,不仿試看看請ChatGPT產生看看

範例一:

假設我們有一個這兩個表格

訂購紀錄表:

名稱 訂購數量 交易時間
A - 05 1 2023-11-05 12:49:55
B - 24 3 2023-11-05 09:00:23
B - 03 2 2023-11-05 09:50:45
A - 12 2 2023-11-05 21:53:27

類別表:

名稱 類型
A - 05 家電
A - 12 家電
B - 24 美妝
B - 03 美妝

希望得出「每天、每小時,每個類型的訂購數量統計」

https://ithelp.ithome.com.tw/upload/images/20231106/201357507D6ZoudgK7.png

以下是你可以使用的 SQL 語法來得出「每天、每小時,每種類型的訂購數量統計」:

SELECT
    DATE(orders.transaction_time) as order_date,
    HOUR(orders.transaction_time) as order_hour,
    categories.type_name,
    SUM(orders.quantity) as total_quantity
FROM
    orders
JOIN
    categories ON orders.name = categories.name
GROUP BY
    order_date, order_hour, categories.type_name
ORDER BY
    order_date, order_hour, categories.type_name;

這個 SQL 語法會將訂單表 (orders) 與類別對照表 (categories) 進行連接,並以交易時間 (transaction_time) 的日期和小時、類型名稱進行分組,最後計算每個分組的訂購數量總和。

請確保你在資料庫中有正確的表格和欄位名稱,並進行適當的資料庫連接。如果還有其他需要幫忙的地方,請隨時告訴我。

然後我這邊額外提供這個demo用表格創建與資料創建的sql語法

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    quantity INT,
    transaction_time TIMESTAMP
);

CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    type_name VARCHAR(255)
);

-- 插入訂單資料
INSERT INTO orders (name, quantity, transaction_time) VALUES
  ('A - 05', 1, '2023-11-05 12:49:55'),
  ('B - 24', 3, '2023-11-05 09:00:23'),
  ('B - 03', 2, '2023-11-05 09:50:45'),
  ('A - 12', 2, '2023-11-05 21:53:27');

-- 插入類別資料
INSERT INTO categories (name, type_name) VALUES
  ('A - 05', '家電'),
  ('A - 12', '家電'),
  ('B - 03', '美妝'),
  ('B - 24', '美妝');

接著就可以看到Query 出來的結果,確實就是我們要的
https://ithelp.ithome.com.tw/upload/images/20231106/20135750Lyy5EgFskY.png

3. 轉換SQL ⇒ 改用knex撰寫

有關knex的介紹可以看看這篇:
【Day49】ChatGPT請教教我:NestJS!(八)- SQL & Knex!SQL注入攻擊!?

先來將剛剛的Query做改寫看看
https://ithelp.ithome.com.tw/upload/images/20231106/20135750Y0MvYa47ho.png

當然,我可以幫你將這段 SQL 語法轉換成 Knex 的方法鏈語法。以下是轉換後的程式碼:

const knex = require('knex')({
  client: 'mysql',
  connection: {
    host : 'your_host',
    user : 'your_user',
    password : 'your_password',
    database : 'your_database'
  }
});

knex('orders')
  .join('categories', 'orders.name', 'categories.name')
  .select(
    knex.raw('DATE(orders.transaction_time) as order_date'),
    knex.raw('HOUR(orders.transaction_time) as order_hour'),
    'categories.type_name',
    knex.raw('SUM(orders.quantity) as total_quantity')
  )
  .groupBy('order_date', 'order_hour', 'categories.type_name')
  .orderBy('order_date')
  .orderBy('order_hour')
  .orderBy('categories.type_name')
  .then(rows => {
    console.log(rows);
  })
  .catch(error => {
    console.error(error);
  })
  .finally(() => {
    knex.destroy();
  });

請將 your_hostyour_useryour_passwordyour_database 替換成你實際的資料庫連接資訊。這段程式碼使用了 Knex 的方法鏈,依照你的 SQL 語法進行了轉換。它會執行查詢並輸出結果,或是在發生錯誤時輸出錯誤訊息。

如果你有任何其他問題,請隨時告訴我。

執行起來的結果,相當不錯
https://ithelp.ithome.com.tw/upload/images/20231106/201357505XFuKC6btr.png

很複雜的query好像只能靠.raw() 處理,不過來一個應該不需要那麽多.raw的例子看看

`SELECT isSuccess, count(*) as count ,save FROM job_table
WHERE endTime >= '${startDate}' and endTime<= '${endDate}' 
	and type in ('typeA','typeC')
GROUP BY isSuccess,saveWae`

https://ithelp.ithome.com.tw/upload/images/20231106/20135750WBqMXDqjT9.png

以下是將你的 SQL 查詢轉換為 Knex 語法並使用 await 的程式碼:

const knex = require('knex')({
  client: 'mysql',
  connection: {
    host: 'your_host',
    user: 'your_user',
    password: 'your_password',
    database: 'your_database'
  }
});

async function fetchJobData(startDate, endDate) {
  try {
    const result = await knex('job_table')
      .select('isSuccess')
      .count('* as count')
      .select('save')
      .where('endTime', '>=', startDate)
      .andWhere('endTime', '<=', endDate)
      .whereIn('type', ['typeA', 'typeC'])
      .groupBy('isSuccess', 'save');

    return result;
  } catch (error) {
    throw error;
  } finally {
    knex.destroy();
  }
}

const startDate = 'your_start_date'; // 替換為實際的起始日期
const endDate = 'your_end_date'; // 替換為實際的結束日期

fetchJobData(startDate, endDate)
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error(error);
  });

請將 'your_host''your_user''your_password''your_database''your_start_date''your_end_date' 替換為你的資料庫連接資訊以及日期範圍。這個程式碼使用了 await 語法,以非同步方式執行查詢,並返回結果或處理錯誤。

不錯不錯,方便方便
這個功能在我把程式碼進行改寫翻新,改用knex語法的時候很常使用到

3. migration 轉換

有關migration的介紹,可以看看這篇的後半段:
【Day50】ChatGPT請教教我:NestJS!(九)- Knex!transaction!migration!

在學了migration 之後,想針對現有的表格進行migration的設置
但可能連一開始建立這個結構的CREAT SQL語法都不太清楚,先來問看看ChatGPT要怎麼取得
https://ithelp.ithome.com.tw/upload/images/20231106/20135750YgThH7wiyB.png

要取得現有 MySQL 表格的 CREATE 語法,你可以使用以下的 SQL 查詢:

SHOW CREATE TABLE your_table_name;

這個查詢會返回指定表格的 CREATE 語法。請將 your_table_name 替換為你想要取得 CREATE 語法的表格名稱。

例如,如果你想要取得名為 orders 的表格的 CREATE 語法,你可以執行以下的 SQL 查詢:

SHOW CREATE TABLE orders;

系統會返回類似以下的結果:

Table   Create Table
orders  CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `quantity` int(11) DEFAULT NULL,
  `transaction_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

這段文字中包含了 orders 表格的 CREATE 語法。

實際執行之後,我得到了一段creat的語法,直接丟給他
接著問他要怎麼建立這個表格的migration
https://ithelp.ithome.com.tw/upload/images/20231106/20135750YhYPpShpaM.png

要建立這個表格的 migration 語法,你可以使用 Knex 的方法來定義表格的結構。以下是對應於你提供的 CREATE TABLE 語法的 Knex migration 語法:

exports.up = function(knex) {
  return knex.schema.createTable('orders', function(table) {
    table.bigIncrements('id').unsigned().primary();
    table.string('name', 255).nullable();
    table.integer('quantity').nullable();
    table.timestamp('transaction_time').nullable();
  });
};

exports.down = function(knex) {
  return knex.schema.dropTable('orders');
};

這個 migration 將會建立一個名為 orders 的表格,並定義了相應的欄位,類型和限制。在 up 函數中,我們使用了 Knex 的方法來建立表格,並在 down 函數中使用了 dropTable 來刪除表格。

如果你使用 Knex CLI 來管理 migrations,你可以將這個 migration 存放在你的 migrations 資料夾中,並使用 knex migrate:latest 來執行 migration。

然後追加需求
https://ithelp.ithome.com.tw/upload/images/20231106/20135750lcFiPt5RfD.png

要在現有的表格中增加一個 user_id 欄位,你可以使用 Knex 的 migration 來實現。以下是對應於你的需求的 migration 語法:

exports.up = function(knex) {
  return knex.schema.alterTable('orders', function(table) {
    table.string('user_id', 20).notNullable().defaultTo('');
  });
};

exports.down = function(knex) {
  return knex.schema.alterTable('orders', function(table) {
    table.dropColumn('user_id');
  });
};

up 函數中,我們使用了 alterTable 方法來修改現有的表格 orders。然後我們使用 string 方法來添加一個 user_id 欄位,並指定了類型為字串,長度最大為 20,並設定為不可為空,預設值為空字串。

down 函數中,我們再次使用 alterTable 方法來修改表格,這次使用 dropColumn 來刪除 user_id 欄位。

請將這個 migration 存放在你的 migrations 資料夾中,並使用 knex migrate:latest 來執行 migration。這樣就會在你的表格中新增了 user_id 欄位。

4. JavaScript 日常提問 - 正規表達式

接下來的這一點的內容比較是前後端都一樣可能遇到的!
時常在做處理的時候,可能會需要擷取某個特定格式中的字串

如果對於「正規表達式」這樣的語法不夠熟練的話
嗯,問ChatGPT方便到不行

假設,我們今天要處理這樣的一段文字

青菜 1 份 - 35 元
滷蛋 1 顆 - 10 元
滷肉飯 3 碗 - 90 元

希望可以

  1. 找到「份」或「顆」或「碗」前面的數量,命名為 number
  2. 該「number」前面的所有文字,命名為 name
  3. 找到「元」前面的數字,命名為 money

直接請ChatGPT來處理這樣的正規表達式吧
https://ithelp.ithome.com.tw/upload/images/20231106/20135750w3rrJOcVs3.png

你可以使用 JavaScript 搭配正則表達式來達到你的目的。以下是一段範例程式碼:

const text = `
青菜 1 份 - 35 元
滷蛋 1 顆 - 10 元
滷肉飯 3 碗 - 90 元
`;

const regex = /(.+)\s(\d+)\s(?:份|顆|碗)\s-\s(\d+)\s元/g;

let match;
while ((match = regex.exec(text)) !== null) {
  const name = match[1];
  const number = parseInt(match[2]);
  const money = parseInt(match[3]);
  
  console.log(`名稱:${name.trim()}, 數量:${number}, 金額:${money}`);
}

這段程式碼會在給定的文字中搜尋符合正則表達式的部分,並將相應的變數取出來。具體來說:

  1. name 取得了符合正則表達式中第一個括號的內容,即所有文字。
  2. number 取得了符合正則表達式中第二個括號的內容,即數量。
  3. money 取得了符合正則表達式中第三個括號的內容,即金額。

這樣你就可以在循環中獲得每一行的相關資訊了。請注意,這段程式碼假設了文字格式的一致性,如果格式有變化,可能需要做進一步的調整。

執行結果:
https://ithelp.ithome.com.tw/upload/images/20231106/20135750IfSaBw8hwT.png

再來一個範例,判斷「元」前面的數字,但是有可能有逗號
像這樣「15,056」這類的文字
https://ithelp.ithome.com.tw/upload/images/20231106/20135750tIpJEBliv9.png

你可以使用正則表達式來達到這個目的。以下是一段 JavaScript 代碼:

const text = "switch一台12,535元";
const regex = /(\d{1,3}(,\d{3})*(\.\d+)?)元/;
const match = text.match(regex);

if (match && match[1]) {
  const numberWithCommas = match[1];
  const numberWithoutCommas = numberWithCommas.replace(/,/g, '');
  console.log(numberWithoutCommas);
} else {
  console.log('未找到匹配的數字');
}

這段代碼首先定義了一個正則表達式 (\\d{1,3}(,\\d{3})*(\\.\\d+)?)元,它將匹配包含逗號的數字,直到找到 "元" 為止。

接著使用 text.match(regex) 來在文本中尋找匹配,並將結果儲存在 match 中。

如果找到了匹配,則從 match 中取得匹配到的數字字串,並使用 .replace(/,/g, '') 去掉逗號,最終得到了你想要的數字。

如果未找到匹配,則會輸出 "未找到匹配的數字"。

爆改一下文字內容測試他能不能正確匹配
https://ithelp.ithome.com.tw/upload/images/20231106/20135750XxN08tU4hc.png
好耶可以~

結語

本次對話記錄在這邊:
https://chat.openai.com/share/a7a73590-1a54-43ad-87fc-92505162d084

上面幾種方式都是我自己在工作上覺得很實用的部分
那也比起上一篇直接貼一大串給ChatGPT,效果更穩定

但也更需要自己去「組合」這些取得的程式碼
把自身的問題切割成小問題,然後丟給ChatGPT處理
自己再把相對應的程式碼融合在一起
輕鬆解決那些枯燥的繁重工作,甚至那些自己不熟悉的部分

另外預告一下,本系列預計為54篇 + 1篇完結感言
這一篇沒能來得及在昨天發出,連續發文的挑戰小小的斷掉了有一些可惜
不過因為在思考與規劃篇幅的部分,還有生出一些範例來示範真的花了一些時間

謝謝各位的閱讀!
我是開著ChatGPT工作的前端工程師一宵三筵,我們有可能明天見!


上一篇
【Day51】ChatGPT幫我完成工作:直接詠唱來重構翻新!PHP 轉NestJS + Knex!
下一篇
【Day53】ChatGPT幫我完成工作:寫Jest單元測試!NestJS & Knex & Jest!
系列文
用ChatGPT詠唱來完成工作與點亮前後端技能樹56
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言