iT邦幫忙

2024 iThome 鐵人賽

DAY 28
0
Software Development

DDD? Clean Architecture? Microservices? 帶你用.NET實作打造一個現代化微服務!系列 第 28

Day 28 - Cursor AI 魔法:一鍵打造現代化 To-Do List 網頁應用

  • 分享至 

  • xImage
  •  

前言

不曉得大家知道前陣子網路上非常紅的 Cursor IDE 嗎?它是基於 VS Code 改造而來,並高度整合了 AI 的一個 IDE。它不像是 Github Copilot 那樣只是一個 Extension 的 AI,而是用 VS Code 的開源程式碼將 AI 整合進編輯器中。不斷聽說非常的厲害,今天就來玩看看。這篇不是教學,比較像是我第一次使用的經驗紀錄。

今天的目標是做一個 HTML5 的前端網頁,因為不想要牽扯太多安裝,就先不使用 NodeJS 了,有興趣的可以自行叫 Cursor 做。

安裝 Cursor

安裝看起來也很簡單,應該不需要多教學,我電腦內的 Cursor 也是我編寫邊安裝的,就不教學了。

建立 WebApp 資料夾

為了不失焦,我們先建立一些檔案結構。一樣用 Cursor 開啟我們的 microservices 專案資料夾,並在 /src/ 內新增 WebApp 資料夾。

再來給他實作的目標檔案,建立 index.htmldashboard.html,目的是想讓 Cursor 在這裡實作。

之後餵給它我們當前微服務 BFF 的介面資料,建立 swagger.jsonsample.http 檔案。

https://ithelp.ithome.com.tw/upload/images/20241010/20168953Hx8bmD3y3h.png

複製 swagger.json

從 BFF Gateway Swagger 的標頭取得你的 Swagger 內容,並貼到 swagger.json 中。

https://ithelp.ithome.com.tw/upload/images/20241010/20168953ygv1fL98Tj.png

填寫 sample.http

還記得我們之前在 BFF Gateway 做了一個 BFF.Gateway.http 的測試檔案嗎?我們利用這個檔案執行每一個 endpoint 的測試,並把 Response 寫進裡面給 Cursor 參考。主要是讓 AI 知道我們的資料結構。

我做出來的大致如下:

@HostAddress = http://localhost:5282

### AccountController - Register
POST {{HostAddress}}/api/Account/register
Content-Type: application/json

{
  "firstName": "Alice",
  "lastName": "Smith",
  "email": "alice.smith@mail.com",
  "password": "1234"
}

# Response
# HTTP/1.1 200 OK
# Connection: close
# Content-Type: application/json; charset=utf-8
# Date: Thu, 10 Oct 2024 08:31:18 GMT
# Server: Kestrel
# Transfer-Encoding: chunked
# 
# {
#   "id": "9dbed11e-d350-4334-91ef-b982adfb3dc8",
#   "firstName": "Alice",
#   "lastName": "Smith",
#   "email": "alice.smith@mail.com",
#   "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0b2RvLWF1ZGllbmNlIiwiaXNzIjoidG9kby1pc3N1ZXIiLCJleHAiOjE3Mjg1NDkzNzgsInN1YiI6IjlkYmVkMTFlLWQzNTAtNDMzNC05MWVmLWI5ODJhZGZiM2RjOCIsImdpdmVuX25hbWUiOiJBbGljZSIsImZhbWlseV9uYW1lIjoiU21pdGgiLCJqdGkiOiJmZGRhYzc3OS03NjYzLTQ0YmEtODZhZS1kMWZiMTY4ODYzZDUiLCJpYXQiOjE3Mjg1NDkwNzgsIm5iZiI6MTcyODU0OTA3OH0.fgcmySjSwM8hh5QZApPY21ukO5qBz6KTngqYdcMbtcc"
# }

### AccountController - Login
POST {{HostAddress}}/api/Account/login
Content-Type: application/json

{
  "email": "alice.smith@mail.com",
  "password": "1234"
}

# Response
# HTTP/1.1 200 OK
# Connection: close
# Content-Type: application/json; charset=utf-8
# Date: Thu, 10 Oct 2024 08:32:29 GMT
# Server: Kestrel
# Transfer-Encoding: chunked

# {
#   "id": "9dbed11e-d350-4334-91ef-b982adfb3dc8",
#   "firstName": "Alice",
#   "lastName": "Smith",
#   "email": "alice.smith@mail.com",
#   "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0b2RvLWF1ZGllbmNlIiwiaXNzIjoidG9kby1pc3N1ZXIiLCJleHAiOjE3Mjg1NDk0NTAsInN1YiI6IjlkYmVkMTFlLWQzNTAtNDMzNC05MWVmLWI5ODJhZGZiM2RjOCIsImdpdmVuX25hbWUiOiJBbGljZSIsImZhbWlseV9uYW1lIjoiU21pdGgiLCJqdGkiOiJiMmE5ZGM2My1lNjA0LTQ5MmQtOTA2Mi05YWU4MjFmYTRjN2EiLCJpYXQiOjE3Mjg1NDkxNTAsIm5iZiI6MTcyODU0OTE1MH0.GQGNeLzh-zxbTR5cxBhH4cSfWRfCE-KJxsHF5C_ixfY"
# }

### TodoListController - Create Todo List
POST {{HostAddress}}/api/TodoList/create
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0b2RvLWF1ZGllbmNlIiwiaXNzIjoidG9kby1pc3N1ZXIiLCJleHAiOjE3Mjg1NDk0NTAsInN1YiI6IjlkYmVkMTFlLWQzNTAtNDMzNC05MWVmLWI5ODJhZGZiM2RjOCIsImdpdmVuX25hbWUiOiJBbGljZSIsImZhbWlseV9uYW1lIjoiU21pdGgiLCJqdGkiOiJiMmE5ZGM2My1lNjA0LTQ5MmQtOTA2Mi05YWU4MjFmYTRjN2EiLCJpYXQiOjE3Mjg1NDkxNTAsIm5iZiI6MTcyODU0OTE1MH0.GQGNeLzh-zxbTR5cxBhH4cSfWRfCE-KJxsHF5C_ixfY
Content-Type: application/json

{
  "userId": "9dbed11e-d350-4334-91ef-b982adfb3dc8",
  "name": "Work",
  "description": "Work notes"
}

# Response
# HTTP/1.1 200 OK
# Connection: close
# Content-Type: application/json; charset=utf-8
# Date: Thu, 10 Oct 2024 08:33:23 GMT
# Server: Kestrel
# Transfer-Encoding: chunked

# {
#   "id": "929556e7-5414-46c2-809c-7fae000270c5",
#   "userId": "9dbed11e-d350-4334-91ef-b982adfb3dc8",
#   "name": "Work",
#   "description": "Work notes",
#   "status": "Active"
# }

### TodoListController - Remove Todo List
DELETE {{HostAddress}}/api/TodoList/remove/929556e7-5414-46c2-809c-7fae000270c5
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0b2RvLWF1ZGllbmNlIiwiaXNzIjoidG9kby1pc3N1ZXIiLCJleHAiOjE3Mjg1NDk0NTAsInN1YiI6IjlkYmVkMTFlLWQzNTAtNDMzNC05MWVmLWI5ODJhZGZiM2RjOCIsImdpdmVuX25hbWUiOiJBbGljZSIsImZhbWlseV9uYW1lIjoiU21pdGgiLCJqdGkiOiJiMmE5ZGM2My1lNjA0LTQ5MmQtOTA2Mi05YWU4MjFmYTRjN2EiLCJpYXQiOjE3Mjg1NDkxNTAsIm5iZiI6MTcyODU0OTE1MH0.GQGNeLzh-zxbTR5cxBhH4cSfWRfCE-KJxsHF5C_ixfY

# Response
# HTTP/1.1 200 OK
# Connection: close
# Content-Type: application/json; charset=utf-8
# Date: Thu, 10 Oct 2024 08:37:16 GMT
# Server: Kestrel
# Transfer-Encoding: chunked

# {
#   "id": "929556e7-5414-46c2-809c-7fae000270c5",
#   "userId": "9dbed11e-d350-4334-91ef-b982adfb3dc8",
#   "name": "Work",
#   "description": "Work notes",
#   "status": "Removed"
# }

### TodoItemController - Create Todo Item
POST {{HostAddress}}/api/TodoItem/create
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0b2RvLWF1ZGllbmNlIiwiaXNzIjoidG9kby1pc3N1ZXIiLCJleHAiOjE3Mjg1NDk0NTAsInN1YiI6IjlkYmVkMTFlLWQzNTAtNDMzNC05MWVmLWI5ODJhZGZiM2RjOCIsImdpdmVuX25hbWUiOiJBbGljZSIsImZhbWlseV9uYW1lIjoiU21pdGgiLCJqdGkiOiJiMmE5ZGM2My1lNjA0LTQ5MmQtOTA2Mi05YWU4MjFmYTRjN2EiLCJpYXQiOjE3Mjg1NDkxNTAsIm5iZiI6MTcyODU0OTE1MH0.GQGNeLzh-zxbTR5cxBhH4cSfWRfCE-KJxsHF5C_ixfY
Content-Type: application/json

{
  "listId": "929556e7-5414-46c2-809c-7fae000270c5",
  "content": "Good Job"
}

# Response
# HTTP/1.1 200 OK
# Connection: close
# Content-Type: application/json; charset=utf-8
# Date: Thu, 10 Oct 2024 08:34:28 GMT
# Server: Kestrel
# Transfer-Encoding: chunked

# {
#   "id": "3c609cbe-9a54-4e95-af36-9330c45e22c8",
#   "listId": "929556e7-5414-46c2-809c-7fae000270c5",
#   "content": "Good Job",
#   "status": "Todo",
#   "color": "#FFFF00"
# }

### TodoItemController - Finish Todo Item
POST {{HostAddress}}/api/TodoItem/finish
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0b2RvLWF1ZGllbmNlIiwiaXNzIjoidG9kby1pc3N1ZXIiLCJleHAiOjE3Mjg1NDk0NTAsInN1YiI6IjlkYmVkMTFlLWQzNTAtNDMzNC05MWVmLWI5ODJhZGZiM2RjOCIsImdpdmVuX25hbWUiOiJBbGljZSIsImZhbWlseV9uYW1lIjoiU21pdGgiLCJqdGkiOiJiMmE5ZGM2My1lNjA0LTQ5MmQtOTA2Mi05YWU4MjFmYTRjN2EiLCJpYXQiOjE3Mjg1NDkxNTAsIm5iZiI6MTcyODU0OTE1MH0.GQGNeLzh-zxbTR5cxBhH4cSfWRfCE-KJxsHF5C_ixfY
Content-Type: application/json

{
  "id": "3c609cbe-9a54-4e95-af36-9330c45e22c8"
}

# Response
# HTTP/1.1 200 OK
# Connection: close
# Content-Type: application/json; charset=utf-8
# Date: Thu, 10 Oct 2024 08:35:19 GMT
# Server: Kestrel
# Transfer-Encoding: chunked

# {
#   "id": "3c609cbe-9a54-4e95-af36-9330c45e22c8",
#   "listId": "929556e7-5414-46c2-809c-7fae000270c5",
#   "content": "Good Job",
#   "status": "Finished",
#   "color": "#008000"
# }

### TodoItemController - Remove Todo Item
DELETE {{HostAddress}}/api/TodoItem/remove/3c609cbe-9a54-4e95-af36-9330c45e22c8
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0b2RvLWF1ZGllbmNlIiwiaXNzIjoidG9kby1pc3N1ZXIiLCJleHAiOjE3Mjg1NDk0NTAsInN1YiI6IjlkYmVkMTFlLWQzNTAtNDMzNC05MWVmLWI5ODJhZGZiM2RjOCIsImdpdmVuX25hbWUiOiJBbGljZSIsImZhbWlseV9uYW1lIjoiU21pdGgiLCJqdGkiOiJiMmE5ZGM2My1lNjA0LTQ5MmQtOTA2Mi05YWU4MjFmYTRjN2EiLCJpYXQiOjE3Mjg1NDkxNTAsIm5iZiI6MTcyODU0OTE1MH0.GQGNeLzh-zxbTR5cxBhH4cSfWRfCE-KJxsHF5C_ixfY

# Response
# HTTP/1.1 200 OK
# Connection: close
# Content-Type: application/json; charset=utf-8
# Date: Thu, 10 Oct 2024 08:36:33 GMT
# Server: Kestrel
# Transfer-Encoding: chunked

# {
#   "id": "3c609cbe-9a54-4e95-af36-9330c45e22c8",
#   "listId": "929556e7-5414-46c2-809c-7fae000270c5",
#   "content": "Good Job",
#   "status": "Removed",
#   "color": "#808080"
# }

### GraphQLController - Query User
POST {{HostAddress}}/api/GraphQL/query
Content-Type: application/json

{
  "query": "query ($userId: String!) { userById(userId: $userId) { id firstName lastName email todoListDtos { id name description status todoItemDtos { id content state color } } }}",
  "variables": {
    "userId": "9dbed11e-d350-4334-91ef-b982adfb3dc8"
  }
}

# Response
# HTTP/1.1 200 OK
# Connection: close
# Content-Type: application/json; charset=utf-8
# Date: Thu, 10 Oct 2024 08:38:52 GMT
# Server: Kestrel
# Transfer-Encoding: chunked

# {
#   "userById": {
#     "id": "9dbed11e-d350-4334-91ef-b982adfb3dc8",
#     "firstName": "Alice",
#     "lastName": "Smith",
#     "email": "alice.smith@mail.com",
#     "todoListDtos": [
#       {
#         "id": "54efc5a9-191c-4828-bbcd-177b1f351afa",
#         "name": "Default",
#         "description": "Default",
#         "status": "ACTIVE",
#         "todoItemDtos": []
#       },
#       {
#         "id": "929556e7-5414-46c2-809c-7fae000270c5",
#         "name": "Work",
#         "description": "Work notes",
#         "status": "REMOVED",
#         "todoItemDtos": [
#           {
#             "id": "3c609cbe-9a54-4e95-af36-9330c45e22c8",
#             "content": "Good Job",
#             "state": "REMOVED",
#             "color": "#808080"
#           }
#         ]
#       }
#     ]
#   }
# }

### GraphQLController - Query Todo List
POST {{HostAddress}}/api/GraphQL/query
Content-Type: application/json

{
  "query": "query ($listId: String!) { todoListById(listId: $listId) { id name description todoItemDtos { id content state color } }}",
  "variables": {
    "listId": "929556e7-5414-46c2-809c-7fae000270c5"
  }
}

# Response
# HTTP/1.1 200 OK
# Connection: close
# Content-Type: application/json; charset=utf-8
# Date: Thu, 10 Oct 2024 08:40:25 GMT
# Server: Kestrel
# Transfer-Encoding: chunked

# {
#   "todoListById": {
#     "id": "929556e7-5414-46c2-809c-7fae000270c5",
#     "name": "Work",
#     "description": "Work notes",
#     "todoItemDtos": [
#       {
#         "id": "3c609cbe-9a54-4e95-af36-9330c45e22c8",
#         "content": "Good Job",
#         "state": "REMOVED",
#         "color": "#808080"
#       }
#     ]
#   }
# }

### GraphQLController - Query Todo Item
POST {{HostAddress}}/api/GraphQL/query
Content-Type: application/json

{
  "query": "query ($itemId: String!) { todoItemById(itemId: $itemId) { id content state color }}",
  "variables": {
    "itemId": "3c609cbe-9a54-4e95-af36-9330c45e22c8"
  }
}

# Response
# HTTP/1.1 200 OK
# Connection: close
# Content-Type: application/json; charset=utf-8
# Date: Thu, 10 Oct 2024 08:41:37 GMT
# Server: Kestrel
# Transfer-Encoding: chunked

# {
#   "todoItemById": {
#     "id": "3c609cbe-9a54-4e95-af36-9330c45e22c8",
#     "content": "Good Job",
#     "state": "REMOVED",
#     "color": "#808080"
#   }
# }

體驗 Cursor 黑魔法

接下來按下 ctrl + L 來開啟 Cursor 的對話視窗。

加入相關要給 Cursor 參考的檔案如下:

https://ithelp.ithome.com.tw/upload/images/20241010/20168953BmgFZPGYU2.png

然後用自然語言跟他說一些細節,自身經驗來說,跟 AI 講話細節最好條列式,所以我這樣跟他說我的需求

請在 WebApp 資料夾內開發一個使用 HTML5 的 Todo List 前端應用程式,功能包含:
註冊/登入/登出、建立/刪除 Todo List、新增/完成/刪除 Todo Item,請依照以下需求進行開發:
1. 使用我提供的 Gateway 可參考 swagger.json,網址為 http://localhost:5282,API 文件請完全按照 sample.http 中的 Request/Response 範例格式。
2. 請完全基於 HTML5 開發,不使用 Node.js。
3. 設計需具備現代感,可使用 BootStrap 框架。
4. 網站分為兩個主要頁面:  
   - index.html:用於註冊和登入頁面。  
   - dashboard.html:主控台頁面,登入後顯示 Todo List。
5. 在登入後的 Dashboard 頁面,右上角需顯示登出按鈕及當前登入使用者名稱。
6. 在使用者成功登入或註冊後,伺服器會回傳 JWT,請參照 sample.http,並將 JWT 存儲,後續請求(如建立 TodoList、TodoItem 等操作)需附帶 `Authorization: Bearer JWT`。
7. Todo List 有兩種狀態:ACTIVE 和 REMOVED。
8. 使用 GraphQL Query 的查詢不需要 Authorization,並嚴格按照 sample.http 的 body 來帶入 Variable。
9. Todo Item 的 Response 會包含顏色屬性,請將此顏色套用至每個 Todo Item 上,並使用卡片(Card)風格設計顯示。
10. Todo Item 有三種狀態
    - TODO :可以完成或刪除。  
    - FINISHED:只能刪除。  
    - REMOVED:無法進行任何操作。
11. 刪除操作後,對應的 Todo List 項目應直接隱藏,Todo Item 則是留在畫面上但無法操作之。
12. 請處理好所有的 Error Handling。

黑魔法,啟動

身為一個初次使用的小白,聽了這麼多 Cursor 的神話故事,我無條件相信它會做好的,所以不管結果如何,我接下來都會無條件的按下 Accept 跟 Apply。

程式碼的部分按下 Accept 後 Apply 就會自動幫你逐行改 Code。

https://ithelp.ithome.com.tw/upload/images/20241010/201689533uswrvBH29.png

這樣 index 就自己做好了。接下來換 dashboard。

https://ithelp.ithome.com.tw/upload/images/20241010/201689538XB7sYge0A.png

檢查結果

用 VS Code 的 Live Preview 來看結果。

https://ithelp.ithome.com.tw/upload/images/20241010/20168953MdJZQACN6b.png

看起來成果不錯!登入看看。

https://ithelp.ithome.com.tw/upload/images/20241010/20168953o0esgBfvXH.png

成功了。

https://ithelp.ithome.com.tw/upload/images/20241010/20168953nbfBoFJ6Z2.png

https://ithelp.ithome.com.tw/upload/images/20241010/20168953LwYnPWHe1S.png

嘗試做一些操作看來都沒有問題。

https://ithelp.ithome.com.tw/upload/images/20241010/20168953MEz6cxxx60.png

登出註冊看看。

https://ithelp.ithome.com.tw/upload/images/20241010/20168953UxgmvqfiIG.png

沒意外的也都通過測試。

https://ithelp.ithome.com.tw/upload/images/20241010/201689532qCKMKQEyu.png

甚至連防呆都有做!

https://ithelp.ithome.com.tw/upload/images/20241010/20168953Sde1EqCOLj.png

互動

我們針對不滿意的地方繼續與它互動,順便給它一點挑戰。在程式碼內按下 ctrl + k 就可以叫出互動對話。

像這樣,會給你一個前後對比,確認無誤後確定修改即可。

https://ithelp.ithome.com.tw/upload/images/20241010/20168953VXNHLKVuLV.png

結果蠻讓我驚豔的。

https://ithelp.ithome.com.tw/upload/images/20241010/20168953XJVrqff6PW.png

結語

我後續還請 Cursor 做了一些小改進:

  1. 新增 Brand Image,並讓 Todo List Description 顯示出來。

  2. JWT 過期自動登出。
    https://ithelp.ithome.com.tw/upload/images/20241010/20168953wmLo4eDbKh.png

  3. 特別跑去 Todo.Domain 改了顏色。

TodoItemState.Todo => "#FFDD00", // 黃色
TodoItemState.Finished => "#30CC30", // 綠色
TodoItemState.Removed => "#666666", // 灰色

成果圖。

https://ithelp.ithome.com.tw/upload/images/20241010/201689537NxJKvIsN5.png

https://ithelp.ithome.com.tw/upload/images/20241010/201689531GMSAgeG6z.png

這篇就先到這邊吧。我自己試玩後都想繳月費了!我應該會找時間測試更多種技術來決定要不要花這錢,真心不錯!


上一篇
Day 27 - Backend 的最後一哩路:Gateway 與 GraphQL Stitching
下一篇
Day 29 - 收官之戰:端口梳理、容器部署與架構總覽
系列文
DDD? Clean Architecture? Microservices? 帶你用.NET實作打造一個現代化微服務!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言