經過了前五個模組在提示詞工程、演算法思維、代碼重構、API/SQL 整合以及測試與資安防護的歷練,今天我們迎來了倒數第二天的壓軸挑戰——「綜合實戰」。
在傳統軟體開發中,要從零開始搭建一個新工具或微服務原型(PoC),通常需要經歷冗長的時間:畫架構草圖、設計資料模型、手刻 DTO、編寫核心演算法、處理防呆與邊界,最後補上單元測試。
今天我們就來完整展示人機協同工作流(Human-AI Collaborative Workflow)的威力:在 30 分鐘內,引導 AI 一步步拆解需求,共同設計並實作一個高效、具備邊界防禦的經典工程專案——「高效短網址轉發服務(URL Shortener Service)」!
我們不直接叫 AI「寫一個短網址系統」,而是先以架構師的身分引導它進行模組劃分:
【系統規劃提示詞範本】
請扮演資深後端系統架構師。我想使用 Java 17 與 Spring Boot 打造一個輕量級高效短網址(URL Shortener)微服務。
請依序完成以下規劃:
AI 迅速給出分析:
確定演算法後,我們要求 AI 產出純度極高、無外部相依、執行緒安全的 Base62 工具類別:
[Java 核心 Base62 編碼工具]
public final class Base62Utils {
private static final String BASE62_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final int BASE = BASE62_CHARS.length();
private Base62Utils() {
// 工具類別防止實例化
}
/**
* 將正整數 ID 編碼為 Base62 短碼
*/
public static String encode(long id) {
if (id <= 0) {
throw new IllegalArgumentException("ID 必須為正整數");
}
StringBuilder sb = new StringBuilder();
while (id > 0) {
int remainder = (int) (id % BASE);
sb.append(BASE62_CHARS.charAt(remainder));
id /= BASE;
}
return sb.reverse().toString();
}
/**
* 將 Base62 短碼解碼回原始數字 ID
*/
public static long decode(String shortCode) {
if (shortCode == null || shortCode.isBlank()) {
throw new IllegalArgumentException("短碼不可為空");
}
long id = 0;
for (int i = 0; i < shortCode.length(); i++) {
int index = BASE62_CHARS.indexOf(shortCode.charAt(i));
if (index == -1) {
throw new IllegalArgumentException("短碼包含非法字元: " + shortCode.charAt(i));
}
id = id * BASE + index;
}
return id;
}
}
接下來,我們請 AI 產出短網址的核心業務層,同時融入先前學過的「防禦性編程」與「Java Record」:
[1. 請求與回應 DTO 定義]
public record ShortenUrlRequest(
String originalUrl,
Long customExpirationSeconds
) {}
public record ShortenUrlResponse(
String shortCode,
String shortUrl,
String originalUrl
) {}
[2. 核心業務邏輯類別]
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class UrlShortenerService {
private static final String DOMAIN = "https://sho.rt/";
private final AtomicLong idGenerator = new AtomicLong(100000L); // 從大基數起算,維持短碼長度
private final Map<String, String> codeToUrlMap = new ConcurrentHashMap<>();
private final Map<String, String> urlToCodeMap = new ConcurrentHashMap<>();
/**
* 縮短網址核心邏輯
*/
public ShortenUrlResponse shorten(ShortenUrlRequest request) {
// 邊界防禦:驗證網址格式
if (request.originalUrl() == null || !request.originalUrl().matches("^https?://.+")) {
throw new IllegalArgumentException("非法的網址格式,必須以 http:// 或 https:// 開頭");
}
// 若先前已生成過,直接回傳舊短碼(避免浪費儲存空間)
String existingCode = urlToCodeMap.get(request.originalUrl());
if (existingCode != null) {
return new ShortenUrlResponse(existingCode, DOMAIN + existingCode, request.originalUrl());
}
// 產生全局唯一 ID 並轉換為 Base62 短碼
long nextId = idGenerator.incrementAndGet();
String shortCode = Base62Utils.encode(nextId);
// 寫入儲存庫(實務上可對接 Redis 或 PostgreSQL)
codeToUrlMap.put(shortCode, request.originalUrl());
urlToCodeMap.put(request.originalUrl(), shortCode);
return new ShortenUrlResponse(shortCode, DOMAIN + shortCode, request.originalUrl());
}
/**
* 還原長網址邏輯
*/
public Optional<String> getOriginalUrl(String shortCode) {
if (shortCode == null || shortCode.isBlank()) {
return Optional.empty();
}
return Optional.ofNullable(codeToUrlMap.get(shortCode));
}
}
最後,我們利用先前掌握的 JUnit 5 模板,讓 AI 在 10 秒內生成覆蓋 Happy Path 與邊界例外的單元測試:
[JUnit 5 驗證測試]
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class UrlShortenerServiceTest {
private final UrlShortenerService service = new UrlShortenerService();
@Test
@DisplayName("縮短網址成功:能正確生成 Base62 短碼並還原原始網址")
void shortenAndResolve_Success() {
String original = "https://ithome.com.tw/ironman/2026";
var response = service.shorten(new ShortenUrlRequest(original, null));
assertThat(response.shortCode()).isNotBlank();
assertThat(response.shortUrl()).startsWith("https://sho.rt/");
var resolved = service.getOriginalUrl(response.shortCode());
assertThat(resolved).isPresent().contains(original);
}
@Test
@DisplayName("網址格式錯誤防禦:未以 http/https 開頭應拋出 IllegalArgumentException")
void shorten_InvalidUrl_ThrowsException() {
assertThatThrownBy(() -> service.shorten(new ShortenUrlRequest("ftp://malicious.site", null)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("非法的網址格式");
}
}
透過今天的完整閉環演練,我們驗證了一套高產出的開發節奏:
ConcurrentHashMap 與 AtomicLong 確保併發安全,確保系統具備上線生產的體質。今天我們透過短網址微服務的端到端實作,將提示詞規格、演算法、Clean Code、例外防衛與單元測試完美串聯,體驗了現代 AI 輔助開發的極致效率。