你提供的代码是一个使用 JSP(JavaServer Pages)实现的简单 **图书管理系统** 的前端页面,包含以下功能:
- 添加新图书
- 编辑现有图书
- 删除图书
- 搜索图书(通过名称或作者)
- 使用内联 Java 代码模拟数据存储(`List<Map<String, String>>`)
但目前这个系统存在几个问题和改进空间。下面我将:
1. **分析当前代码的问题**
2. **给出完整的修复与优化方案**
3. **提供可运行的完整代码结构**
4. **提出相关后续问题**
---
## ✅ 当前代码存在的主要问题
| 问题 | 描述 |
|------|------|
| ❌ 数据未持久化 | 所有书籍都在内存中创建,刷新即丢失 |
| ❌ XSS 安全漏洞 | `request.getParameter()` 直接输出到 HTML,可能被注入脚本 |
| ❌ URL 传递敏感参数 | 编辑信息通过 URL 参数传递,易被篡改 |
| ❌ 表单重复提交 | 没有防止表单重复提交机制 |
| ❌ 搜索未过滤数据 | 搜索逻辑缺失,只是显示了所有书 |
| ❌ 删除操作无后端验证 | 删除直接由链接触发,不安全 |
---
## ✅ 改进后的解决方案:完整图书管理系统(JSP + Servlet + 内存存储)
我们将分两个文件来重构它:
### 文件1: `BookServlet.java` —— 控制器处理增删改查
```java
// BookServlet.java
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet("/books")
public class BookServlet extends HttpServlet {
private static final Map<String, Map<String, String>> BOOKS = new ConcurrentHashMap<>();
private static int ID_COUNTER = 3; // 初始ID
// 初始化测试数据
static {
Map<String, String> book1 = new HashMap<>();
book1.put("id", "1");
book1.put("title", "Java编程思想");
book1.put("author", "Bruce Eckel");
book1.put("isbn", "9787111213826");
book1.put("price", "108.00");
Map<String, String> book2 = new HashMap<>();
book2.put("id", "2");
book2.put("title", "深入理解计算机系统");
book2.put("author", "Randal E. Bryant");
book2.put("isbn", "9787111544937");
book2.put("price", "139.00");
BOOKS.put("1", book1);
BOOKS.put("2", book2);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
String search = request.getParameter("search");
List<Map<String, String>> filteredBooks = new ArrayList<>(BOOKS.values());
// 搜索功能
if (search != null && !search.trim().isEmpty()) {
String lowerSearch = search.toLowerCase();
filteredBooks.removeIf(book ->
!book.get("title").toLowerCase().contains(lowerSearch) &&
!book.get("author").toLowerCase().contains(lowerSearch)
);
}
request.setAttribute("books", filteredBooks);
// 转发到 index.jsp 显示页面
RequestDispatcher dispatcher = request.getRequestDispatcher("index.jsp");
dispatcher.forward(request, response);
}
}
```
---
### 文件2: `BookActionServlet.java` —— 处理添加、更新、删除动作
```java
// BookActionServlet.java
import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet("/bookAction")
public class BookActionServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String action = request.getParameter("action");
String id = request.getParameter("id");
String title = request.getParameter("title");
String author = request.getParameter("author");
String isbn = request.getParameter("isbn");
String price = request.getParameter("price");
String message;
String messageType;
try {
if ("add".equals(action)) {
String newId = String.valueOf(System.currentTimeMillis() % 100000); // 简易ID
Map<String, String> book = new HashMap<>();
book.put("id", newId);
book.put("title", escapeHtml(title));
book.put("author", escapeHtml(author));
book.put("isbn", escapeHtml(isbn));
book.put("price", price);
BookServlet.BOOKS.put(newId, book);
message = "✅ 图书《" + book.get("title") + "》添加成功!";
messageType = "success";
} else if ("update".equals(action) && id != null) {
Map<String, String> book = BookServlet.BOOKS.get(id);
if (book != null) {
book.put("title", escapeHtml(title));
book.put("author", escapeHtml(author));
book.put("isbn", escapeHtml(isbn));
book.put("price", price);
message = "✏️ 图书《" + title + "》已更新!";
messageType = "success";
} else {
message = "❌ 找不到要更新的图书!";
messageType = "error";
}
} else if ("delete".equals(action) && id != null) {
Map<String, String> book = BookServlet.BOOKS.remove(id);
if (book != null) {
message = "🗑️ 图书《" + book.get("title") + "》已删除!";
messageType = "success";
} else {
message = "❌ 删除失败,图书不存在!";
messageType = "error";
}
} else {
message = "⚠️ 无效的操作请求!";
messageType = "error";
}
} catch (Exception e) {
message = "❌ 操作出现异常:" + e.getMessage();
messageType = "error";
}
// 重定向回主页面并携带消息
response.sendRedirect("books?message=" + java.net.URLEncoder.encode(message, "UTF-8") +
"&messageType=" + messageType);
}
// 防止XSS攻击:对HTML特殊字符进行转义
private String escapeHtml(String input) {
if (input == null) return null;
return input.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
}
```
---
### 文件3: 修改后的 `index.jsp`(更安全 & 功能完整)
```jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图书管理系统</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 15px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #2c3e50, #34495e);
color: white;
padding: 30px;
text-align: center;
}
.header h1 { font-size: 2.5em; margin-bottom: 10px; }
.main-content { padding: 30px; }
.form-section {
background: #f8f9fa;
padding: 25px;
border-radius: 10px;
margin-bottom: 30px;
border-left: 5px solid #3498db;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #2c3e50;
}
.form-control {
width: 100%;
padding: 12px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 16px;
transition: all 0.3s ease;
}
.form-control:focus {
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
outline: none;
}
.btn {
padding: 12px 25px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: all 0.3s ease;
}
.btn-primary {
background: linear-gradient(135deg, #3498db, #2980b9);
color: white;
}
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4); }
.btn-danger { background: linear-gradient(135deg, #e74c3c, #c0392b); color: white; }
.btn-success { background: linear-gradient(135deg, #27ae60, #219a52); color: white; }
.books-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.book-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
border: 1px solid #e9ecef;
transition: all 0.3s ease;
}
.book-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(0,0,0,0.15); }
.book-title { font-size: 1.3em; font-weight: bold; color: #2c3e50; margin-bottom: 10px; }
.book-info { color: #7f8c8d; margin-bottom: 8px; }
.book-actions { margin-top: 15px; display: flex; gap: 10px; }
.search-section { margin-bottom: 25px; }
.search-box { display: flex; gap: 10px; }
.alert-success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.alert-error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.alert { padding: 15px; border-radius: 8px; margin-bottom: 20px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📚 图书管理系统</h1>
<p>轻松管理您的图书收藏</p>
</div>
<div class="main-content">
<!-- 消息提示 -->
<%
String message = request.getParameter("message");
String messageType = request.getParameter("messageType");
if (message != null) {
String alertClass = "alert-success".equals(messageType) ? "alert-success" : "alert-error";
%>
<div class="alert <%= alertClass %>"><%= message %></div>
<%
}
%>
<!-- 添加/编辑表单 -->
<%
String editId = request.getParameter("editId");
Map<String, String> editBook = null;
if (editId != null) {
editBook = BookServlet.BOOKS.get(editId);
}
%>
<% if (editBook != null) { %>
<div class="form-section">
<h2>✏️ 编辑图书</h2>
<form action="bookAction" method="post">
<input type="hidden" name="action" value="update">
<input type="hidden" name="id" value="<%= editBook.get("id") %>">
<div class="form-group">
<label for="title">图书名称</label>
<input type="text" id="title" name="title" class="form-control"
value="<%= editBook.get("title") %>" required>
</div>
<div class="form-group">
<label for="author">作者</label>
<input type="text" id="author" name="author" class="form-control"
value="<%= editBook.get("author") %>" required>
</div>
<div class="form-group">
<label for="isbn">ISBN</label>
<input type="text" id="isbn" name="isbn" class="form-control"
value="<%= editBook.get("isbn") %>" required>
</div>
<div class="form-group">
<label for="price">价格</label>
<input type="number" id="price" name="price" step="0.01" class="form-control"
value="<%= editBook.get("price") %>" required>
</div>
<button type="submit" class="btn btn-primary">更新图书</button>
<a href="books" class="btn" style="background: #95a5a6;">取消</a>
</form>
</div>
<% } else { %>
<div class="form-section">
<h2>➕ 添加新图书</h2>
<form action="bookAction" method="post">
<input type="hidden" name="action" value="add">
<div class="form-group">
<label for="title">图书名称</label>
<input type="text" id="title" name="title" class="form-control" required>
</div>
<div class="form-group">
<label for="author">作者</label>
<input type="text" id="author" name="author" class="form-control" required>
</div>
<div class="form-group">
<label for="isbn">ISBN</label>
<input type="text" id="isbn" name="isbn" class="form-control" required>
</div>
<div class="form-group">
<label for="price">价格</label>
<input type="number" id="price" name="price" step="0.01" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">添加图书</button>
</form>
</div>
<% } %>
<!-- 搜索区域 -->
<div class="search-section">
<form action="books" method="get">
<div class="search-box">
<input type="text" name="search" class="form-control"
placeholder="搜索图书名称或作者..."
value="<%= request.getParameter("search") != null ? request.getParameter("search") : "" %>">
<button type="submit" class="btn btn-primary">搜索</button>
<a href="books" class="btn" style="background: #95a5a6;">重置</a>
</div>
</form>
</div>
<!-- 图书列表 -->
<div class="books-section">
<h2>📖 图书列表 (<%= ((List<?>) request.getAttribute("books")).size() %> 本)</h2>
<div class="books-grid">
<%
List<Map<String, String>> books = (List<Map<String, String>>) request.getAttribute("books");
if (books != null && !books.isEmpty()) {
for (Map<String, String> book : books) {
%>
<div class="book-card">
<div class="book-title"><%= book.get("title") %></div>
<div class="book-info"><strong>作者:</strong> <%= book.get("author") %></div>
<div class="book-info"><strong>ISBN:</strong> <%= book.get("isbn") %></div>
<div class="book-info"><strong>价格:</strong> ¥<%= book.get("price") %></div>
<div class="book-actions">
<a href="books?editId=<%= book.get("id") %>" class="btn btn-success">编辑</a>
<a href="bookAction?action=delete&id=<%= book.get("id") %>"
class="btn btn-danger"
onclick="return confirm('确定要删除这本书吗?')">删除</a>
</div>
</div>
<%
}
} else {
%>
<p>没有找到匹配的图书。</p>
<%
}
%>
</div>
</div>
</div>
</div>
<script>
document.querySelector('form').addEventListener('submit', function(e) {
const price = document.getElementById('price').value;
if (isNaN(price) || parseFloat(price) <= 0) {
e.preventDefault();
alert('请输入有效价格!');
}
});
</script>
</body>
</html>
```
---
## ✅ 解释说明
| 组件 | 作用 |
|------|------|
| `BookServlet.java` | 处理 GET 请求,加载图书列表并支持搜索,转发给 JSP 展示 |
| `BookActionServlet.java` | 处理 POST 请求,执行添加、更新、删除操作,并跳转回主页带提示 |
| `index.jsp` | 视图层,展示表单、图书卡片、搜索框;从 request 获取数据渲染 |
| `escapeHtml()` 方法 | 防止 XSS 注入攻击 |
| `ConcurrentHashMap` | 线程安全地保存图书数据(生产环境应替换为数据库) |
---
## ✅ 如何部署运行?
1. 使用 IDE(如 IntelliJ IDEA 或 Eclipse)创建一个 Dynamic Web Project。
2. 将 `.java` 文件放入 `src/` 目录下。
3. 将 `index.jsp` 放入 `WebContent/` 根目录。
4. 配置好 Tomcat 服务器。
5. 启动服务,访问:`http://localhost:8080/your-project-name/books`
---
## ✅ 进一步优化建议
- ✅ 使用 MySQL 数据库存储图书(JDBC / MyBatis / Hibernate)
- ✅ 添加用户登录认证(Session / Cookie / JWT)
- ✅ 引入 Bootstrap 或 Layui 提升 UI
- ✅ 使用 DAO 模式分离业务逻辑
- ✅ 添加分页功能
- ✅ 使用 Filter 实现全局编码设置和 XSS 过滤
---