首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Swing 實戰:從零構建完整登錄註冊模組

     編輯:Java 相關知識

專案概覽與技術棧

本專案展示如何使用 Java Swing 構建一個功能完整的桌面應用登錄註冊系統,包含用戶註冊、登錄驗證、圖形驗證碼、輸入限制與實時提示等核心功能。技術棧包括:

  • GUI 框架:Swing(BoxLayout、BorderLayout 佈局管理)
  • 數據持久化:MySQL + JDBC(Apache Commons DbUtils 簡化操作)
  • 工具庫:Commons Lang(SQL 轉義)、Commons Codec(密碼處理)
  • 設計模式:DAO 模式、JavaBean 封裝、DocumentFilter/DocumentListener 監聽

數據庫設計

CREATE DATABASE db_database01 CHARACTER SET utf8mb4;
USE db_database01;

CREATE TABLE tb_user (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(15) NOT NULL UNIQUE,
    password VARCHAR(20) NOT NULL,
    email VARCHAR(45) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 初始測試數據
INSERT INTO tb_user (username, password, email) VALUES ('demo', 'demo123', 'demo@example.com');

核心模塊實現

1. 實體類與數據訪問層

// User.java - 實體類
public class User {
    private int id;
    private String username;
    private String password;
    private String email;
    // getter/setter 省略
}

// DBConfig.java - 數據庫配置接口
public interface DBConfig {
    String URL = "jdbc:mysql://localhost:3306/db_database01?useSSL=false&serverTimezone=UTC";
    String USER = "root";
    String PASSWORD = "your_password";
}

// DBHelper.java - 核心數據操作類
public class DBHelper implements DBConfig {
    private static final QueryRunner RUNNER = new QueryRunner();
    private static final MysqlDataSource DATA_SOURCE = new MysqlDataSource();

    static {
        DATA_SOURCE.setURL(URL);
        DATA_SOURCE.setUser(USER);
        DATA_SOURCE.setPassword(PASSWORD);
    }

    public static Connection getConnection() throws SQLException {
        return DATA_SOURCE.getConnection();
    }

    // 用戶名唯一性檢查
    public static boolean exists(String username) {
        String sql = "SELECT 1 FROM tb_user WHERE username = ?";
        try (Connection conn = getConnection()) {
            Long count = RUNNER.query(conn, sql, new ScalarHandler<Long>(), username);
            return count != null && count > 0;
        } catch (SQLException e) {
            throw new RuntimeException("檢查用戶名失敗", e);
        }
    }

    // 登錄驗證
    public static boolean check(String username, char[] password) {
        String sql = "SELECT password FROM tb_user WHERE username = ?";
        try (Connection conn = getConnection()) {
            String dbPwd = RUNNER.query(conn, sql, new ScalarHandler<String>(), username);
            if (dbPwd == null) return false;
            boolean match = Arrays.equals(password, dbPwd.toCharArray());
            Arrays.fill(password, '0'); // 清除內存中的明文密碼
            return match;
        } catch (SQLException e) {
            throw new RuntimeException("驗證失敗", e);
        }
    }

    // 註冊保存
    public static boolean save(User user) {
        String sql = "INSERT INTO tb_user (username, password, email) VALUES (?, ?, ?)";
        try (Connection conn = getConnection()) {
            return RUNNER.update(conn, sql, user.getUsername(), user.getPassword(), user.getEmail()) > 0;
        } catch (SQLException e) {
            throw new RuntimeException("保存用戶失敗", e);
        }
    }
}

2. 圖形驗證碼組件

自定義 JLabel 子類實現驗證碼繪製,支援單色與彩色兩種模式:

// 基礎驗證碼標籤
public class CaptchaLabel extends JLabel {
    private final String text;

    public CaptchaLabel(String text) {
        this.text = text;
        setPreferredSize(new Dimension(80, 36));
        setFont(new Font("微軟雅黑", Font.BOLD, 20));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.drawString(text, 10, 26);
    }
}

// 彩色干擾線驗證碼
public class ColorfulCaptchaLabel extends JLabel {
    private final String text;
    private static final Color[] COLORS = {Color.RED, Color.BLUE, Color.GREEN, Color.MAGENTA, Color.ORANGE};

    public ColorfulCaptchaLabel(String text) {
        this.text = text;
        setPreferredSize(new Dimension(100, 40));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        
        // 繪製干擾線
        Random rand = new Random();
        for (int i = 0; i < 10; i++) {
            g2d.setColor(COLORS[rand.nextInt(COLORS.length)]);
            g2d.drawLine(rand.nextInt(getWidth()), rand.nextInt(getHeight()),
                         rand.nextInt(getWidth()), rand.nextInt(getHeight()));
        }
        
        // 逐字符繪製,顏色隨機
        for (int i = 0; i < text.length(); i++) {
            g2d.setColor(COLORS[rand.nextInt(COLORS.length)]);
            g2d.setFont(new Font("微軟雅黑", Font.BOLD, 24));
            g2d.drawString(String.valueOf(text.charAt(i)), 15 + i * 20, 30);
        }
    }
}

3. 輸入限制與實時反饋

使用 DocumentFilter 限制輸入長度,DocumentListener 實時顯示字符計數:

// 長度限制過濾器
public class LengthFilter extends DocumentFilter {
    private final int maxLength;

    public LengthFilter(int maxLength) {
        this.maxLength = maxLength;
    }

    @Override
    public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
            throws BadLocationException {
        if (fb.getDocument().getLength() + string.length() <= maxLength) {
            super.insertString(fb, offset, string, attr);
        } else {
            Toolkit.getDefaultToolkit().beep();
        }
    }

    @Override
    public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
            throws BadLocationException {
        if (fb.getDocument().getLength() + text.length() - length <= maxLength) {
            super.replace(fb, offset, length, text, attrs);
        } else {
            Toolkit.getDefaultToolkit().beep();
        }
    }
}

// 實時提示監聽器
public class LengthListener implements DocumentListener {
    private final JLabel tipLabel;
    private final int maxLength;

    public LengthListener(JLabel tipLabel, int maxLength) {
        this.tipLabel = tipLabel;
        this.maxLength = maxLength;
    }

    @Override
    public void insertUpdate(DocumentEvent e) { updateTip(e); }
    @Override
    public void removeUpdate(DocumentEvent e) { updateTip(e); }
    @Override
    public void changedUpdate(DocumentEvent e) { updateTip(e); }

    private void updateTip(DocumentEvent e) {
        int len = e.getDocument().getLength();
        tipLabel.setText(len + "/" + maxLength);
        tipLabel.setForeground(len > maxLength * 0.8 ? Color.RED : Color.BLACK);
    }
}

4. 登錄窗體實現

public class LoginFrame extends JFrame {
    private JTextField usernameField;
    private JPasswordField passwordField;
    private JTextField captchaField;
    private final String captchaText = RandomStringUtils.randomAlphanumeric(4);

    public LoginFrame() {
        setTitle("系統登錄");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        // 用戶名面板
        add(createLabeledPanel("用戶名:", usernameField = new JTextField(15)));

        // 密碼面板
        add(createLabeledPanel("密碼:", passwordField = new JPasswordField(15)));

        // 驗證碼面板
        JPanel captchaPanel = new JPanel();
        captchaPanel.add(new JLabel("驗證碼:"));
        captchaPanel.add(captchaField = new JTextField(5));
        captchaPanel.add(new ColorfulCaptchaLabel(captchaText));
        add(captchaPanel);

        // 按鈕面板
        JPanel btnPanel = new JPanel();
        JButton loginBtn = new JButton("登錄");
        loginBtn.addActionListener(this::doLogin);
        JButton cancelBtn = new JButton("取消");
        cancelBtn.addActionListener(e -> System.exit(0));
        btnPanel.add(loginBtn);
        btnPanel.add(cancelBtn);
        add(btnPanel);

        pack();
        setLocationRelativeTo(null);
    }

    private void doLogin(ActionEvent e) {
        String username = usernameField.getText().trim();
        char[] password = passwordField.getPassword();
        String captcha = captchaField.getText().trim();

        // 非空校驗
        if (username.isEmpty() || password.length == 0 || captcha.isEmpty()) {
            showWarning("所有字段均不能為空");
            return;
        }

        // 驗證碼校驗
        if (!captcha.equalsIgnoreCase(captchaText)) {
            showWarning("驗證碼錯誤");
            return;
        }

        // 數據庫驗證
        if (!DBHelper.exists(username)) {
            showWarning("用戶不存在");
            return;
        }
        if (!DBHelper.check(username, password)) {
            showWarning("密碼錯誤");
            return;
        }

        // 登錄成功,打開主窗體
        SwingUtilities.invokeLater(() -> {
            new MainFrame().setVisible(true);
            dispose();
        });
    }

    private JPanel createLabeledPanel(String labelText, JComponent field) {
        JPanel panel = new JPanel();
        JLabel label = new JLabel(labelText);
        label.setFont(new Font("微軟雅黑", Font.PLAIN, 14));
        field.setFont(new Font("微軟雅黑", Font.PLAIN, 14));
        panel.add(label);
        panel.add(field);
        return panel;
    }

    private void showWarning(String msg) {
        JOptionPane.showMessageDialog(this, msg, "提示", JOptionPane.WARNING_MESSAGE);
    }
}

5. 註冊窗體與正則驗證

public class RegisterFrame extends JFrame {
    // 組件聲明...
    
    private void doRegister(ActionEvent e) {
        String username = usernameField.getText().trim();
        char[] pwd1 = passwordField1.getPassword();
        char[] pwd2 = passwordField2.getPassword();
        String email = emailField.getText().trim();

        // 非空校驗
        if (username.isEmpty() || pwd1.length == 0 || pwd2.length == 0 || email.isEmpty()) {
            showWarning("所有字段均不能為空");
            return;
        }

        // 正則驗證
        if (!username.matches("\\w{5,15}")) {
            showWarning("用戶名需 5-15 位字母數字下劃線");
            return;
        }
        if (!Arrays.equals(pwd1, pwd2)) {
            showWarning("兩次密碼不一致");
            return;
        }
        if (!email.matches("^[\\w.-]+@[\\w.-]+\\.\\w+$")) {
            showWarning("郵箱格式不正確");
            return;
        }
        if (DBHelper.exists(username)) {
            showWarning("用戶名已被佔用");
            return;
        }

        // 保存用戶
        User user = new User();
        user.setUsername(username);
        user.setPassword(new String(pwd1));
        user.setEmail(email);
        Arrays.fill(pwd1, '0');
        Arrays.fill(pwd2, '0');

        if (DBHelper.save(user)) {
            JOptionPane.showMessageDialog(this, "註冊成功!", "提示", JOptionPane.INFORMATION_MESSAGE);
            dispose();
        } else {
            showWarning("註冊失敗,請重試");
        }
    }
}

運行與測試

  1. 確保 MySQL 服務運行,執行上方建表腳本
  2. 修改 DBConfig 中的數據庫連接參數
  3. 添加依賴:mysql-connector-java、commons-dbutils、commons-lang3、commons-codec
  4. 運行 LoginFrame.main() 啟動應用

擴展建議

  • 密碼加密:引入 BCrypt 或 Argon2 替代明文存儲
  • 記住我:使用 Preferences API 存儲加密後的憑證
  • 第三方登錄:整合 OAuth2 支援微信、QQ、GitHub 登錄
  • 響應式佈局:遷移至 JavaFX 或使用 MigLayout 改善跨平台適配
  • 國際化:使用 ResourceBundle 管理多語言資源

這個登錄註冊模組雖然基於 Swing 這一經典框架,但涵蓋了桌面應用開發的核心要素:分層架構、輸入校驗、安全處理、用戶體驗細節。理解並掌握這些基礎,對於後續學習現代 GUI 框架或全棧開發都大有裨益。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved