Login extends JFrame implements ActionListener
- 組件字段: JLabel, JTextField, JPasswordField, JButton, PanelDemo
- init(): 佈局初始化 (null 佈局 + setBounds)
- actionPerformed(): 統一事件分發
PanelDemo extends Panel
- paint(Graphics): 繪製驗證碼 (背景噪點 + 4位隨機字符)
- 靜態字段 randomcaptcha 供主窗口驗證
public class Login extends JFrame implements ActionListener {
// 組件聲明...
public Login() {
setTitle("用戶登錄");
setSize(400, 300);
setLocationRelativeTo(null); // 居中
setLayout(null); // 絕對佈局
init();
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
}
void init() {
// Logo
JLabel logo = new JLabel(new ImageIcon("welcome.gif"));
logo.setBounds(125, 10, 150, 70); add(logo);
// 用戶名
addLabelAndField("用戶名:", 90, 100, userNameInput = new JTextField(), 150, 100);
// 密碼
addLabelAndField("密碼:", 90, 130, passwordInput = new JPasswordField(), 150, 130);
// 驗證碼區域
JLabel capLabel = new JLabel("驗證碼:"); capLabel.setBounds(90, 160, 60, 40); add(capLabel);
captchaInput = new JTextField(); captchaInput.setBounds(150, 160, 70, 20); add(captchaInput);
// 圖形驗證碼面板
PanelDemo capPanel = new PanelDemo();
capPanel.setBounds(220, 160, 80, 20); add(capPanel);
// 換一張按鈕
JButton change = new JButton("換一張");
change.setBounds(300, 160, 80, 20);
change.setContentAreaFilled(false); change.setBorderPainted(false);
change.addActionListener(this); add(change);
// 登錄/退出按鈕
addButton("登錄", "login.gif", 70, 200, KeyEvent.VK_L);
addButton("退出", "exit.gif", 210, 200, KeyEvent.VK_X);
}
void addLabelAndField(String text, int lx, int ly, JTextField field, int fx, int fy) {
JLabel l = new JLabel(text); l.setBounds(lx, ly, 60, 40); add(l);
field.setBounds(fx, fy, 150, 20); field.addActionListener(this); add(field);
}
void addButton(String text, String icon, int x, int y, int mnemonic) {
JButton b = new JButton(text, new ImageIcon(icon));
b.setBounds(x, y, 120, 30); b.setMnemonic(mnemonic); b.addActionListener(this); add(b);
}
}
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if (src == changeBtn) { captchaPanel.repaint(); return; }
if (src == logoutBtn) {
JOptionPane.showMessageDialog(this, "感謝使用,再見!");
dispose(); return;
}
if (src != loginBtn) return; // 只有登錄按鈕/回車進入驗證
String user = userNameInput.getText();
String pwd = new String(passwordInput.getPassword());
String code = captchaInput.getText().trim().toUpperCase(); // 不區分大小寫
if ("admin".equals(user) && "123456".equals(pwd)) {
if (code.equals(PanelDemo.randomcaptcha)) {
JOptionPane.showMessageDialog(this, "登錄成功!");
// 這裡打開主系統窗口
} else {
JOptionPane.showMessageDialog(this, "驗證碼錯誤!");
captchaPanel.repaint();
}
} else {
JOptionPane.showMessageDialog(this, "用戶名或密碼錯誤!");
}
}
class PanelDemo extends Panel {
public void paint(Graphics g) {
int w = 80, h = 20;
g.setColor(Color.LIGHT_GRAY); g.fillRect(0, 0, w, h);
g.setColor(Color.BLACK); g.drawRect(0, 0, w, h);
Random rd = new Random();
// 噪點
for (int i=0; i<100; i++) {
g.setColor(Color.RED);
g.drawOval(rd.nextInt(w)-2, rd.nextInt(h)-2, 2, 2);
}
// 4 位隨機字符
g.setFont(new Font("微軟雅黑", Font.BOLD, 18));
g.setColor(Color.BLUE);
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder sb = new StringBuilder();
for (int i=0; i<4; i++) sb.append(chars.charAt(rd.nextInt(chars.length()))).append(' ');
g.drawString(sb.toString(), 0, 18);
// 供驗證用 (去除空格)
randomcaptcha = sb.toString().replaceAll(" ", "").toUpperCase();
}
public static String randomcaptcha;
}
| 痛點 | 優化方案 |
|---|---|
| 絕對佈局不適應 DPI/分辨率變化 | 改用 GridBagLayout / MigLayout / FormLayout |
| 驗證碼安全性低 (客戶端生成) | 後端生成、存 Redis、前端請求圖片 URL、限制嘗試次數 |
| 密碼明文傳輸/存儲 | HTTPS + BCrypt/Argon2 加鹽哈希 |
| 無「記住我」/自動登錄 | Token + Cookie (HttpOnly, Secure, SameSite) |
| 圖片資源依賴文件系統 | 打包進 JAR: getClass().getResource("/images/welcome.gif") |
| 單線程阻塞 UI (網絡請求) | SwingWorker 異步執行登錄邏輯 |
這個 Swing 登錄界面雖簡單,但涵蓋了容器佈局、自定義繪製、事件監聽、組件狀態管理等 GUI 編程核心要素。理解其機制有助於維護舊系統、快速原型開發,也為遷移至現代技術棧打下基礎。