本系統為教學級 Java 桌面應用,採用 **XML 檔案持久化** (DOM 解析)、**兩層架構** (UI + DAO)、**命令列介面 (CLI)** 互動,實現用戶認證、通訊錄 CRUD 功能。適合學習 DOM XML 操作、分層設計、工廠模式、序列化替代方案。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<contacts>
<user name="Jack" pwd="1234">
<contact id="707dede609dd4a2990f7cfa4cd5233f9">
<name>xiaoming</name>
<sex>male</sex>
<tel>123456</tel>
</contact>
<contact id="80983802eaa6402d8bac8bb39e71c48f">
<name>12</name>
<sex>12</sex>
<tel>12</tel>
</contact>
</user>
<user name="Rose" pwd="4321">
<contact id="eedb795b97194c3aaa9bacda7e2948e9">
<name>xiaoming</name>
<sex>female</sex>
<tel>123</tel>
</contact>
</user>
</contacts>
<contacts> 包含多個 <user><user> 屬性:name (用戶名)、pwd (密碼)<contact> 屬性:id (UUID),子元素:name, sex, tel
cn.hncu.contact
├── util/
│ ├── IDGenerate.java // UUID 生成器
│ └── myDocumentFactory.java // DOM Document 單例工廠 (載入/儲存 XML)
├── dao/
│ ├── contactDAO.java // DAO 介面
│ ├── contactImpl.java // DOM 實作
│ └── contactDAOFactory.java // 工廠
└── cmd/
└── contanctAction.java // CLI 互動邏輯 (主程式入口)
package cn.hncu.contact.util;
import java.util.UUID;
public class IDGenerate {
private IDGenerate() {}
public static String getID() {
return UUID.randomUUID().toString().replace("-", ""); // 32 位純字串
}
}
package cn.hncu.contact.util;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
public class myDocumentFactory {
private static final String FILE_NAME = "./xml/users.xml";
private static Document dom = null;
static {
try {
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
dom = db.parse(FILE_NAME); // 啟動時載入整個 XML 到記憶體
} catch (Exception e) {
throw new RuntimeException("XML 文檔解析失敗", e);
}
}
public static Document getDocument() {
return dom;
}
public static void save() {
try {
Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.transform(new DOMSource(dom), new StreamResult(FILE_NAME));
} catch (Exception e) {
throw new RuntimeException("XML 文檔儲存失敗", e);
}
}
}
package cn.hncu.contact.dao;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Element;
public interface contactDAO {
boolean login(String name, String pwd);
List
package cn.hncu.contact.dao;
import java.util.*;
import org.w3c.dom.*;
import cn.hncu.contact.util.*;
public class contactImpl implements contactDAO {
private Element currentUser; // 當前登入用戶節點 (記憶體引用)
private Document dom;
public contactImpl() {
this.dom = myDocumentFactory.getDocument();
}
@Override
public boolean login(String name, String pwd) {
dom = myDocumentFactory.getDocument(); // 重新獲取最新 DOM
Element root = (Element) dom.getFirstChild(); //
NodeList users = root.getElementsByTagName("user");
for (int i = 0; i < users.getLength(); i++) {
Element user = (Element) users.item(i);
if (user.getAttribute("name").equalsIgnoreCase(name)
&& user.getAttribute("pwd").equalsIgnoreCase(pwd)) {
currentUser = user; // 緩存當前用戶節點
return true;
}
}
currentUser = null;
return false;
}
@Override
public List
package cn.hncu.contact.cmd;
import java.util.*;
import org.w3c.dom.Element;
import cn.hncu.contact.dao.*;
public class contanctAction {
private contactDAO dao = contactDAOFactory.getContactDAO();
private Scanner sc = new Scanner(System.in);
private String[] delIds; // 當前查詢結果 ID 陣列 (對應顯示順序)
public contanctAction() {
while (true) {
printMainMenu();
String op = sc.nextLine();
if ("1".equals(op)) login();
else if ("2".equals(op)) reg();
else break; // 0 退出
}
}
private void printMainMenu() {
System.out.println("\n=== 通訊錄系統 ===");
System.out.println("1: 登入");
System.out.println("2: 註冊");
System.out.println("0: 退出");
System.out.print("請選擇: ");
}
private void login() {
System.out.print("用戶名: "); String name = sc.nextLine();
System.out.print("密碼: "); String pwd = sc.nextLine();
if (dao.login(name, pwd)) {
System.out.println("✅ 登入成功");
operationMenu();
} else {
System.out.println("❌ 登入失敗");
}
}
private void reg() {
System.out.print("用戶名: "); String name = sc.nextLine();
System.out.print("密碼: "); String pwd = sc.nextLine();
System.out.print("確認密碼: "); String pwd2 = sc.nextLine();
if (pwd.equals(pwd2)) {
dao.reg(name, pwd);
System.out.println("✅ 註冊成功");
} else {
System.out.println("❌ 兩次密碼不一致");
}
}
private void operationMenu() {
while (true) {
List> list = dao.queryAll();
delIds = new String[list.size()];
System.out.println("\n=== 功能選單 ===");
System.out.println("1: 顯示聯繫人");
System.out.println("2: 新增聯繫人");
System.out.println("3: 刪除聯繫人");
System.out.println("4: 修改聯繫人");
System.out.println("0: 登出");
System.out.print("請選擇: ");
String sel = sc.nextLine();
if ("1".equals(sel)) displayContacts(list);
else if ("2".equals(sel)) addContact();
else if ("3".equals(sel)) deleteContact(list);
else if ("4".equals(sel)) changeContact(list);
else if ("0".equals(sel)) break;
}
}
private void displayContacts(List> list) {
System.out.println("\n編號\t姓名\t性別\t電話");
System.out.println("------------------------------");
for (int i = 0; i < list.size(); i++) {
Map m = list.get(i);
delIds[i] = m.get("id");
System.out.printf("%d\t%s\t%s\t%s%n", i+1, m.get("name"), m.get("sex"), m.get("tel"));
}
}
private void addContact() {
System.out.print("姓名: "); String name = sc.nextLine();
System.out.print("性別: "); String sex = sc.nextLine();
System.out.print("電話: "); String tel = sc.nextLine();
Element e = dao.add(name, sex, tel);
System.out.println("✅ 新增成功: " + e.getElementsByTagName("name").item(0).getTextContent());
}
private void deleteContact(List> list) {
if (list.isEmpty()) { System.out.println("無聯繫人可刪除"); return; }
displayContacts(list);
System.out.print("請輸入刪除編號: ");
try {
int num = Integer.parseInt(sc.nextLine());
if (num < 1 || num > delIds.length) { System.out.println("編號無效"); return; }
Element e = dao.delete(delIds[num-1]);
if (e != null) System.out.println("✅ 刪除成功: " + getName(e));
else System.out.println("❌ 刪除失敗");
} catch (NumberFormatException e) { System.out.println("請輸入數字"); }
}
private void changeContact(List> list) {
if (list.isEmpty()) { System.out.println("無聯繫人可修改"); return; }
displayContacts(list);
System.out.print("請輸入修改編號: ");
try {
int num = Integer.parseInt(sc.nextLine());
if (num < 1 || num > delIds.length) { System.out.println("編號無效"); return; }
System.out.print("新姓名: "); String name = sc.nextLine();
System.out.print("新性別: "); String sex = sc.nextLine();
System.out.print("新電話: "); String tel = sc.nextLine();
Element e = dao.change(delIds[num-1], name, sex, tel);
if (e != null) System.out.println("✅ 修改成功: " + getName(e));
else System.out.println("❌ 修改失敗");
} catch (NumberFormatException e) { System.out.println("請輸入數字"); }
}
private String getName(Element contact) {
return contact.getElementsByTagName("name").item(0).getTextContent();
}
public static void main(String[] args) {
new contanctAction();
}
}
| 技術點 | 實作方式 | 優點 | 缺點/限制 |
|---|---|---|---|
| XML 持久化 | DOM 解析 (整樹載入記憶體) | 結構清晰、可讀性強、支援複雜查詢、標準 API | 記憶體佔用大、不適合大數據量 (>10MB) |
| 單例 Document | myDocumentFactory 靜態載入/儲存 | 避免重複解析、操作統一記憶體模型 | 多執行緒不安全、需外部同步 |
| UUID 主鍵 | UUID.randomUUID() 去連字號 | 全域唯一、無需中心協調、分散式友好 | 長度 32 字符、非人類可讀 |
| 分層架構 | DAO 介面 + 實作 + 工廠 | 關注點分離、可測試、易替換儲存引擎 | 樣板代碼稍多 |
| CLI 互動 | Scanner + 狀態機選單 | 零依賴、易理解、適合教學演示 | 無圖形介面、輸入驗證較弱 |
contactDAO 的 JDBC/JPA 版本,XML 版本無需修改上層邏輯。本系統展示了 **Java 標準 DOM API 操作 XML**、**分層架構 (UI/DAO/Util)**、**工廠模式解耦**、**UUID 主鍵設計**、**CLI 狀態機互動** 等核心技能。雖為教學簡化版,但架構清晰、邏輯完整,是進階學習資料庫應用、Web 開發、企業級架構的優秀基石。