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

JavaWeb 圖書商城實戰(一):用戶模塊完整實現(註冊、激活、登錄、退出)

     編輯:Java 相關知識

專案背景

本系列構建一個完整的 JavaWeb 圖書商城,採用分層架構:Domain → DAO → Service → Servlet → JSP。本文聚焦用戶模塊,實現註冊(郵件激活)、登錄、退出功能。

一、領域模型

public class User {
    private String uid;       // UUID 主鍵
    private String username;  // 用戶名
    private String password;  // 密碼 (建議加密存儲)
    private String email;     // 郵箱
    private String code;      // 激活碼 (UUID)
    private boolean state;    // 激活狀態 false=未激活 true=已激活
    // getter/setter/toString 省略
}

二、基礎 Servlet 封裝

通過反射根據 method 參數分發請求,減少 Servlet 數量。

public class BaseServlet extends HttpServlet {
    protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        res.setContentType("text/html;charset=utf-8");
        String method = req.getParameter("method");
        try {
            Method m = this.getClass().getMethod(method, HttpServletRequest.class, HttpServletResponse.class);
            String result = (String) m.invoke(this, req, res);
            if (result != null && !result.isEmpty()) {
                if (result.startsWith("r:")) res.sendRedirect(req.getContextPath() + result.substring(2));
                else req.getRequestDispatcher(result.substring(2)).forward(req, res);
            }
        } catch (Exception e) { throw new ServletException(e); }
    }
}

三、用戶 Servlet (UserServlet)

public class UserServlet extends BaseServlet {
    private UserService userService = new UserService();

    // 退出登錄
    public String quit(HttpServletRequest req, HttpServletResponse resp) {
        req.getSession().invalidate();
        return "r:/index.jsp";
    }

    // 登錄
    public String login(HttpServletRequest req, HttpServletResponse resp) {
        User form = CommonUtils.toBean(req.getParameterMap(), User.class);
        try {
            User user = userService.login(form);
            req.getSession().setAttribute("session_user", user);
            req.getSession().setAttribute("cart", new Cart()); // 初始化購物車
            return "r:/index.jsp";
        } catch (UserException e) {
            req.setAttribute("msg", e.getMessage());
            req.setAttribute("form", form);
            return "f:/jsps/user/login.jsp";
        }
    }

    // 激活
    public String active(HttpServletRequest req, HttpServletResponse resp) {
        String code = req.getParameter("code");
        try {
            userService.active(code);
            req.setAttribute("msg", "激活成功!請登錄");
        } catch (UserException e) {
            req.setAttribute("msg", e.getMessage());
        }
        return "f:/jsps/msg.jsp";
    }

    // 註冊
    public String regist(HttpServletRequest req, HttpServletResponse resp) {
        User form = CommonUtils.toBean(req.getParameterMap(), User.class);
        form.setUid(CommonUtils.uuid());
        form.setCode(CommonUtils.uuid() + CommonUtils.uuid());

        // 1. 服務端校驗
        Map errors = validate(form);
        if (!errors.isEmpty()) {
            req.setAttribute("errors", errors);
            req.setAttribute("form", form);
            return "f:/jsps/user/regist.jsp";
        }

        // 2. 業務處理
        try {
            userService.regist(form);
        } catch (UserException e) {
            req.setAttribute("msg", e.getMessage());
            req.setAttribute("form", form);
            return "f:/jsps/user/regist.jsp";
        }

        // 3. 發送激活郵件
        sendActivationEmail(form);

        req.setAttribute("msg", "註冊成功!請查收郵件激活");
        return "f:/jsps/msg.jsp";
    }

    private Map validate(User form) {
        Map errors = new HashMap<>();
        if (form.getUsername() == null || form.getUsername().trim().isEmpty()) errors.put("username", "用戶名不能為空");
        else if (form.getUsername().length() < 3 || form.getUsername().length() > 10) errors.put("username", "用戶名長度 3-10");
        if (form.getPassword() == null || form.getPassword().trim().isEmpty()) errors.put("password", "密碼不能為空");
        else if (form.getPassword().length() < 3 || form.getPassword().length() > 10) errors.put("password", "密碼長度 3-10");
        if (form.getEmail() == null || form.getEmail().trim().isEmpty()) errors.put("email", "Email不能為空");
        else if (!form.getEmail().matches("\\w+@\\w+\\.\\w+")) errors.put("email", "Email格式錯誤");
        return errors;
    }

    private void sendActivationEmail(User user) {
        Properties props = new Properties();
        try {
            props.load(this.getClass().getClassLoader().getResourceAsStream("email_template.properties"));
            String host = props.getProperty("host");
            String uname = props.getProperty("uname");
            String pwd = props.getProperty("pwd");
            String from = props.getProperty("from");
            String to = user.getEmail();
            String subject = props.getProperty("subject");
            String content = MessageFormat.format(props.getProperty("content"), user.getCode());

            Session session = MailUtils.createSession(host, uname, pwd);
            Mail mail = new Mail(from, to, subject, content);
            MailUtils.send(session, mail);
        } catch (Exception e) { e.printStackTrace(); }
    }
}

四、業務層 (UserService)

public class UserService {
    private UserDao userDao = new UserDao();

    public void regist(User form) throws UserException {
        if (userDao.findByUsername(form.getUsername()) != null) throw new UserException("用戶名已存在");
        if (userDao.findByEmail(form.getEmail()) != null) throw new UserException("Email已被註冊");
        userDao.add(form);
    }

    public void active(String code) throws UserException {
        User user = userDao.findByCode(code);
        if (user == null) throw new UserException("激活碼無效");
        if (user.isState()) throw new UserException("已激活,請勿重複操作");
        userDao.updateState(user.getUid(), true);
    }

    public User login(User form) throws UserException {
        User user = userDao.findByUsername(form.getUsername());
        if (user == null) throw new UserException("用戶名不存在");
        if (!user.getPassword().equals(form.getPassword())) throw new UserException("密碼錯誤"); // 實際應比對加密值
        if (!user.isState()) throw new UserException("賬號未激活");
        return user;
    }
}

五、持久層 (UserDao - 基於 Apache DbUtils)

public class UserDao {
    private QueryRunner qr = new TxQueryRunner(); // 事務 QueryRunner

    public User findByUsername(String username) {
        try { return qr.query("select * from tb_user where username=?", new BeanHandler<>(User.class), username); }
        catch (SQLException e) { throw new RuntimeException(e); }
    }

    public User findByEmail(String email) {
        try { return qr.query("select * from tb_user where email=?", new BeanHandler<>(User.class), email); }
        catch (SQLException e) { throw new RuntimeException(e); }
    }

    public void add(User user) {
        try {
            qr.update("insert into tb_user values(?,?,?,?,?,?)",
                user.getUid(), user.getUsername(), user.getPassword(), user.getEmail(), user.getCode(), user.isState());
        } catch (SQLException e) { throw new RuntimeException(e); }
    }

    public User findByCode(String code) {
        try { return qr.query("select * from tb_user where code=?", new BeanHandler<>(User.class), code); }
        catch (SQLException e) { throw new RuntimeException(e); }
    }

    public void updateState(String uid, boolean state) {
        try { qr.update("update tb_user set state=? where uid=?", state, uid); }
        catch (SQLException e) { throw new RuntimeException(e); }
    }
}

六、JSP 頁面關鍵片段

regist.jsp - 註冊頁(回顯 + 錯誤提示)

<form action="<c:url value='/UserServlet'>" method="post">
    <input type="hidden" name="method" value="regist"/>
    用戶名:<input type="text" name="username" value="${form.username}"/> <span>${errors.username}</span><br/>
    密碼:<input type="password" name="password" value="${form.password}"/> <span>${errors.password}</span><br/>
    郵箱:<input type="text" name="email" value="${form.email}"/> <span>${errors.email}</span><br/>
    <input type="submit" value="註冊"/>
</form>

login.jsp - 登錄頁

<form action="<c:url value='/UserServlet'>" method="post">
    <input type="hidden" name="method" value="login"/>
    用戶名:<input type="text" name="username" value="${form.username}"/><br/>
    密碼:<input type="password" name="password"/><br/>
    <input type="submit" value="登錄"/>
</form>
<c:if test="${not empty msg}"><div style="color:red">${msg}</div></c:if>

七、數據庫表結構

CREATE TABLE tb_user (
    uid VARCHAR(32) PRIMARY KEY,
    username VARCHAR(20) UNIQUE NOT NULL,
    password VARCHAR(32) NOT NULL,
    email VARCHAR(50) UNIQUE NOT NULL,
    code VARCHAR(64) NOT NULL,
    state BOOLEAN DEFAULT FALSE
);

八、郵件模板配置 (email_template.properties)

host=smtp.163.com
uname=your_email@163.com
pwd=your_auth_code
from=your_email@163.com
subject=圖書商城賬號激活
content=親,點擊鏈接激活賬號:https://localhost:8080/bookstore/UserServlet?method=active&code={0}

九、流程圖總結

  1. 註冊:regist.jsp → UserServlet#regist() → 驗證 → 入庫 → 發郵件 → msg.jsp
  2. 激活:郵件鏈接 → UserServlet#active() → 校驗碼/狀態 → 更新狀態 → msg.jsp
  3. 登錄:login.jsp → UserServlet#login() → 校驗用戶/密碼/狀態 → 存 Session → index.jsp
  4. 退出:top.jsp → UserServlet#quit() → Session.invalidate() → login.jsp

十、安全與擴展建議

  • 密碼必須使用 BCrypt/SCrypt 加密存儲,絕不存明文。
  • 防止 CSRF:表單添加隱藏 Token,登錄/註冊驗證。
  • 限制註冊頻率:IP + 郵箱雙重限流(Redis 計數器)。
  • 激活鏈接有效期:code 表添加 create_time,超時失效。
  • 登錄失敗鎖定:連續 5 次失敗鎖定 15 分鐘。
  • 引入 Spring Security 替換手工認證邏輯。

用戶模塊是系統安全的第一道防線,代碼需經得起推敲,細節決定成敗。

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