在開始之前,請確保已完成以下基礎環境配置:
| 組件 | 推薦版本 | 下載地址 |
|---|---|---|
| JDK | 17 LTS / 21 LTS | Adoptium Temurin |
| Eclipse IDE | 2023-12 / 2024-03 | Eclipse Downloads (選 Eclipse IDE for Enterprise Java Developers) |
| Apache Tomcat | 10.1.x (Servlet 6.0) / 9.0.x (Servlet 4.0) | Tomcat Downloads |
| MySQL (可選) | 8.0 LTS | MySQL Downloads |
D:\Java\jdk-21JAVA_HOME = D:\Java\jdk-21CLASSPATH = .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jarPath,添加 %JAVA_HOME%\bin 和 %JAVA_HOME%\jre\binjava -version、javac -versionD:\Java\apache-tomcat-10.1.20),JRE 選擇已配置的 JDKLoginDemo右鍵專案 → Properties → Web Project Settings → 將 Default encoding 改為 UTF-8。
若 web.xml 報錯,右鍵專案 → Build Path → Configure Build Path → Libraries → Add Library → Server Runtime → 選擇 Tomcat → Apply and Close。
在 WebContent 下創建目錄結構:
WebContent/
├── css/
│ └── login.css
├── images/
│ ├── logo.png
│ └── bg.jpg
└── js/
└── validate.js
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用戶登錄</title>
<link rel="stylesheet" href="css/login.css">
</head>
<body>
<div class="login-container">
<div class="login-box">
<h2>系統登錄</h2>
<form action="LoginServlet" method="post" id="loginForm">
<div class="form-group">
<label>用戶名</label>
<input type="text" name="username" id="username" required autocomplete="username">
</div>
<div class="form-group">
<label>密碼</label>
<input type="password" name="password" id="password" required autocomplete="current-password">
</div>
<div class="form-group captcha-group">
<label>驗證碼</label>
<div class="captcha-input">
<input type="text" name="captcha" id="captcha" maxlength="4" required>
<img src="CaptchaServlet" id="captchaImg" alt="點擊刷新" title="點擊刷新驗證碼">
</div>
</div>
<button type="submit" class="btn-login">登錄</button>
</form>
</div>
</div>
<script src="js/validate.js"></script>
</body>
</html>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-container { width: 100%; max-width: 400px; padding: 20px; }
.login-box {
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
padding: 40px;
}
.login-box h2 { text-align: center; color: #333; margin-bottom: 30px; font-weight: 600; }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 8px; color: #666; font-size: 14px; }
.form-group input {
width: 100%;
padding: 12px 15px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 16px;
transition: border-color 0.3s, box-shadow 0.3s;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
}
.captcha-group .captcha-input { display: flex; gap: 10px; }
.captcha-group input { flex: 1; }
.captcha-group img { height: 46px; cursor: pointer; border-radius: 6px; border: 1px solid #eee; }
.btn-login {
width: 100%;
padding: 14px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn-login:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4); }
.btn-login:active { transform: translateY(0); }
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('loginForm');
const captchaImg = document.getElementById('captchaImg');
// 點擊驗證碼圖片刷新
captchaImg.addEventListener('click', () => {
captchaImg.src = 'CaptchaServlet?' + Date.now();
});
// 表單提交前驗證
form.addEventListener('submit', (e) => {
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value;
const captcha = document.getElementById('captcha').value.trim();
if (!username || !password || !captcha) {
e.preventDefault();
alert('請填寫所有字段');
return;
}
if (captcha.length !== 4) {
e.preventDefault();
alert('驗證碼為 4 位字符');
}
});
});
@WebServlet("/CaptchaServlet")
public class CaptchaServlet extends HttpServlet {
private static final String CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
private static final int WIDTH = 100, HEIGHT = 40, LENGTH = 4;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 生成驗證碼
StringBuilder sb = new StringBuilder();
Random rand = new Random();
for (int i = 0; i < LENGTH; i++) {
sb.append(CHARS.charAt(rand.nextInt(CHARS.length())));
}
String code = sb.toString();
// 存入 Session
req.getSession().setAttribute("captcha", code.toLowerCase());
// 繪製圖片
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(new Color(240, 240, 240));
g2d.fillRect(0, 0, WIDTH, HEIGHT);
// 干擾線
g2d.setColor(new Color(200, 200, 200));
for (int i = 0; i < 5; i++) {
g2d.drawLine(rand.nextInt(WIDTH), rand.nextInt(HEIGHT), rand.nextInt(WIDTH), rand.nextInt(HEIGHT));
}
// 繪製字符
g2d.setFont(new Font("Arial", Font.BOLD, 28));
for (int i = 0; i < LENGTH; i++) {
g2d.setColor(new Color(rand.nextInt(100), rand.nextInt(100), rand.nextInt(100)));
g2d.drawString(String.valueOf(code.charAt(i)), 15 + i * 20, 30);
}
g2d.dispose();
// 輸出
resp.setContentType("image/jpeg");
resp.setHeader("Pragma", "no-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
ImageIO.write(image, "JPEG", resp.getOutputStream());
}
}
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
String captcha = req.getParameter("captcha");
// 驗證碼校驗
String sessionCaptcha = (String) req.getSession().getAttribute("captcha");
if (sessionCaptcha == null || !sessionCaptcha.equals(captcha.toLowerCase())) {
req.setAttribute("error", "驗證碼錯誤或已過期");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
return;
}
// 這裡對接真實業務邏輯 (DAO、Service 層)
// 假設驗證通過
if ("admin".equals(username) && "123456".equals(password)) {
req.getSession().setAttribute("user", username);
resp.sendRedirect(req.getContextPath() + "/index.jsp");
} else {
req.setAttribute("error", "用戶名或密碼錯誤");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
String user = (String) session.getAttribute("user");
if (user == null) { response.sendRedirect("login.jsp"); return; }
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>歡迎頁面</title>
<style>
body { font-family: sans-serif; text-align: center; padding-top: 100px; background: #f5f5f5; }
.card { background: white; display: inline-block; padding: 40px; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); }
.btn { display: inline-block; margin-top: 20px; padding: 10px 30px; background: #667eea; color: white; text-decoration: none; border-radius: 6px; }
</style>
</head>
<body>
<div class="card">
<h1>登錄成功!</h1>
<p>歡迎,<strong><%= user %></strong></p>
<a href="LogoutServlet" class="btn">退出登錄</a>
</div>
</body>
</html>
@WebServlet("/LogoutServlet")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
req.getSession().invalidate();
resp.sendRedirect(req.getContextPath() + "/login.jsp");
}
}
https://localhost:8080/LoginDemo/login.jspadmin / 123456web.xml 中的 welcome-file-list、Servlet 映射路徑、專案部署路徑CaptchaServlet 映射路徑、JAI/ImageIO 库是否可用context.xml 未禁用 Session通過這個完整教程,你不僅掌握了 Eclipse + Tomcat 的經典開發流程,更建立了一個可擴展的 Web 應用基礎骨架。建議在掌握基礎後,逐步引入 Spring Boot 等現代框架提升開發效率。