1. 项目概述与核心架构设计
这个在线奶茶店系统是一个典型的JavaWeb应用,采用经典的JSP+Servlet技术栈实现。系统整体遵循MVC模式,Servlet作为控制器处理业务逻辑,JSP负责视图展示,MySQL作为数据持久层。这种架构在中小型电商系统中非常常见,特别适合需要快速开发且对性能要求不高的场景。
我选择这个技术组合主要基于几个实际考量:首先,JSP+Servlet是JavaWeb开发的基础技术栈,学习曲线平缓;其次,这套架构对服务器资源要求不高,1核2G的云服务器就能流畅运行;最重要的是,这种模式的分层清晰,特别适合教学演示和中小型项目开发。
系统主要包含以下几个核心模块:
- 用户模块(注册/登录/个人中心)
- 商品展示与分类模块
- 购物车与订单模块
- 后台管理模块
2. 开发环境搭建与项目初始化
2.1 基础环境配置
开发这个项目需要准备以下环境:
- JDK 1.8+(推荐JDK11,长期支持版本)
- Apache Tomcat 9.x(与JavaEE 8规范兼容)
- MySQL 5.7+(社区版即可)
- IntelliJ IDEA Ultimate版(社区版对JSP支持有限)
提示:MySQL 8.x版本在连接驱动和密码加密方式上与5.x有差异,如果使用8.x版本,需要在连接字符串中添加
useSSL=false&allowPublicKeyRetrieval=true参数
2.2 项目创建与结构
在IDEA中创建Maven项目时,选择maven-archetype-webapp原型。标准项目结构如下:
code复制src/
├── main/
│ ├── java/ # Servlet和Java类
│ ├── resources/ # 配置文件和静态资源
│ └── webapp/ # JSP和前端资源
│ ├── WEB-INF/
│ │ └── web.xml
│ └── index.jsp
pom.xml
关键pom依赖包括:
xml复制<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- JSP API -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- JSTL 标签库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
3. 数据库设计与实现
3.1 数据表结构设计
奶茶店系统的核心表包括:
sql复制CREATE TABLE `users` (
`user_id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`password` VARCHAR(100) NOT NULL,
`phone` VARCHAR(20),
`address` VARCHAR(200),
`reg_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`),
UNIQUE KEY `username_UNIQUE` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `products` (
`product_id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`category` ENUM('奶茶','果茶','咖啡','甜品') NOT NULL,
`price` DECIMAL(10,2) NOT NULL,
`stock` INT NOT NULL DEFAULT 0,
`description` TEXT,
`image_url` VARCHAR(255),
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `orders` (
`order_id` VARCHAR(20) NOT NULL,
`user_id` INT NOT NULL,
`total_amount` DECIMAL(10,2) NOT NULL,
`status` ENUM('待支付','已支付','制作中','已发货','已完成','已取消') NOT NULL DEFAULT '待支付',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`pay_time` DATETIME,
PRIMARY KEY (`order_id`),
FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `order_items` (
`item_id` INT NOT NULL AUTO_INCREMENT,
`order_id` VARCHAR(20) NOT NULL,
`product_id` INT NOT NULL,
`quantity` INT NOT NULL,
`price` DECIMAL(10,2) NOT NULL,
PRIMARY KEY (`item_id`),
FOREIGN KEY (`order_id`) REFERENCES `orders` (`order_id`),
FOREIGN KEY (`product_id`) REFERENCES `products` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 数据库连接池配置
在生产环境中,直接使用DriverManager获取连接效率很低。推荐使用DBCP2连接池:
java复制// 在ServletContextListener中初始化连接池
public class DBCPInitListener implements ServletContextListener {
private static BasicDataSource dataSource;
@Override
public void contextInitialized(ServletContextEvent sce) {
dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/bubble_tea?useUnicode=true&characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setInitialSize(5);
dataSource.setMaxTotal(20);
sce.getServletContext().setAttribute("dataSource", dataSource);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
在web.xml中配置监听器:
xml复制<listener>
<listener-class>com.bubbletea.listener.DBCPInitListener</listener-class>
</listener>
4. 核心功能实现
4.1 用户认证与会话管理
用户登录Servlet示例:
java复制@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
try (Connection conn = DBCPInitListener.getConnection()) {
String sql = "SELECT * FROM users WHERE username=? AND password=?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, username);
stmt.setString(2, DigestUtils.md5Hex(password)); // 密码MD5加密
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
HttpSession session = request.getSession();
session.setAttribute("user", new User(
rs.getInt("user_id"),
rs.getString("username"),
rs.getString("phone")
));
response.sendRedirect("index.jsp");
} else {
request.setAttribute("error", "用户名或密码错误");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
} catch (SQLException e) {
throw new ServletException("数据库错误", e);
}
}
}
注意:实际项目中密码应该加盐哈希,不要直接存储明文或简单MD5
4.2 商品展示与分页
商品列表Servlet实现分页查询:
java复制@WebServlet("/products")
public class ProductServlet extends HttpServlet {
private static final int PAGE_SIZE = 8;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int page = 1;
try {
page = Integer.parseInt(request.getParameter("page"));
} catch (NumberFormatException e) {
// 使用默认值
}
String category = request.getParameter("category");
try (Connection conn = DBCPInitListener.getConnection()) {
// 查询商品总数
String countSql = "SELECT COUNT(*) FROM products";
if (category != null) {
countSql += " WHERE category=?";
}
PreparedStatement countStmt = conn.prepareStatement(countSql);
if (category != null) {
countStmt.setString(1, category);
}
ResultSet countRs = countStmt.executeQuery();
countRs.next();
int total = countRs.getInt(1);
int totalPages = (int) Math.ceil((double)total / PAGE_SIZE);
// 查询当前页数据
String dataSql = "SELECT * FROM products";
if (category != null) {
dataSql += " WHERE category=?";
}
dataSql += " LIMIT ?,?";
PreparedStatement dataStmt = conn.prepareStatement(dataSql);
int paramIndex = 1;
if (category != null) {
dataStmt.setString(paramIndex++, category);
}
dataStmt.setInt(paramIndex++, (page-1)*PAGE_SIZE);
dataStmt.setInt(paramIndex, PAGE_SIZE);
ResultSet dataRs = dataStmt.executeQuery();
List<Product> products = new ArrayList<>();
while (dataRs.next()) {
products.add(new Product(
dataRs.getInt("product_id"),
dataRs.getString("name"),
dataRs.getString("category"),
dataRs.getBigDecimal("price"),
dataRs.getInt("stock"),
dataRs.getString("description"),
dataRs.getString("image_url")
));
}
request.setAttribute("products", products);
request.setAttribute("currentPage", page);
request.setAttribute("totalPages", totalPages);
request.setAttribute("category", category);
request.getRequestDispatcher("products.jsp").forward(request, response);
} catch (SQLException e) {
throw new ServletException("数据库错误", e);
}
}
}
对应的JSP页面使用JSTL展示数据:
jsp复制<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="product-list">
<c:forEach items="${products}" var="product">
<div class="product-item">
<img src="${product.imageUrl}" alt="${product.name}">
<h3>${product.name}</h3>
<p class="price">¥${product.price}</p>
<c:if test="${product.stock > 0}">
<button onclick="addToCart(${product.productId})">加入购物车</button>
</c:if>
<c:if test="${product.stock <= 0}">
<button disabled>已售罄</button>
</c:if>
</div>
</c:forEach>
</div>
<div class="pagination">
<c:if test="${currentPage > 1}">
<a href="products?page=${currentPage-1}&category=${category}">上一页</a>
</c:if>
<c:forEach begin="1" end="${totalPages}" var="i">
<c:choose>
<c:when test="${i == currentPage}">
<span class="current">${i}</span>
</c:when>
<c:otherwise>
<a href="products?page=${i}&category=${category}">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<c:if test="${currentPage < totalPages}">
<a href="products?page=${currentPage+1}&category=${category}">下一页</a>
</c:if>
</div>
4.3 购物车与订单系统
购物车功能通常使用Session存储临时数据:
java复制@WebServlet("/cart")
public class CartServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
HttpSession session = request.getSession();
Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart");
if (cart == null) {
cart = new HashMap<>();
session.setAttribute("cart", cart);
}
if ("add".equals(action)) {
int productId = Integer.parseInt(request.getParameter("productId"));
int quantity = Integer.parseInt(request.getParameter("quantity"));
try (Connection conn = DBCPInitListener.getConnection()) {
String sql = "SELECT * FROM products WHERE product_id=?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, productId);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
Product product = new Product(
rs.getInt("product_id"),
rs.getString("name"),
rs.getString("category"),
rs.getBigDecimal("price"),
rs.getInt("stock"),
rs.getString("description"),
rs.getString("image_url")
);
CartItem item = cart.get(productId);
if (item != null) {
item.setQuantity(item.getQuantity() + quantity);
} else {
cart.put(productId, new CartItem(product, quantity));
}
}
} catch (SQLException e) {
throw new ServletException("数据库错误", e);
}
} else if ("remove".equals(action)) {
int productId = Integer.parseInt(request.getParameter("productId"));
cart.remove(productId);
}
response.sendRedirect("cart.jsp");
}
}
订单提交时的事务处理:
java复制@WebServlet("/order/submit")
public class OrderSubmitServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart");
if (cart == null || cart.isEmpty()) {
response.sendRedirect("cart.jsp");
return;
}
Connection conn = null;
try {
conn = DBCPInitListener.getConnection();
conn.setAutoCommit(false); // 开启事务
// 1. 创建订单主表
String orderId = generateOrderId();
BigDecimal totalAmount = calculateTotal(cart);
String orderSql = "INSERT INTO orders(order_id, user_id, total_amount, status) VALUES(?,?,?,?)";
PreparedStatement orderStmt = conn.prepareStatement(orderSql);
orderStmt.setString(1, orderId);
orderStmt.setInt(2, user.getUserId());
orderStmt.setBigDecimal(3, totalAmount);
orderStmt.setString(4, "待支付");
orderStmt.executeUpdate();
// 2. 创建订单明细并扣减库存
String itemSql = "INSERT INTO order_items(order_id, product_id, quantity, price) VALUES(?,?,?,?)";
String stockSql = "UPDATE products SET stock=stock-? WHERE product_id=? AND stock>=?";
PreparedStatement itemStmt = conn.prepareStatement(itemSql);
PreparedStatement stockStmt = conn.prepareStatement(stockSql);
for (CartItem item : cart.values()) {
// 插入订单明细
itemStmt.setString(1, orderId);
itemStmt.setInt(2, item.getProduct().getProductId());
itemStmt.setInt(3, item.getQuantity());
itemStmt.setBigDecimal(4, item.getProduct().getPrice());
itemStmt.addBatch();
// 扣减库存
stockStmt.setInt(1, item.getQuantity());
stockStmt.setInt(2, item.getProduct().getProductId());
stockStmt.setInt(3, item.getQuantity());
stockStmt.addBatch();
}
int[] itemResults = itemStmt.executeBatch();
int[] stockResults = stockStmt.executeBatch();
// 检查所有操作是否成功
for (int result : stockResults) {
if (result == 0) {
conn.rollback();
request.setAttribute("error", "库存不足,请重新确认");
request.getRequestDispatcher("/cart.jsp").forward(request, response);
return;
}
}
conn.commit();
session.removeAttribute("cart"); // 清空购物车
response.sendRedirect("order_detail.jsp?orderId=" + orderId);
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
throw new ServletException("下单失败", e);
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true);
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
private String generateOrderId() {
return "O" + System.currentTimeMillis() + (int)(Math.random()*1000);
}
private BigDecimal calculateTotal(Map<Integer, CartItem> cart) {
BigDecimal total = BigDecimal.ZERO;
for (CartItem item : cart.values()) {
total = total.add(item.getProduct().getPrice().multiply(
BigDecimal.valueOf(item.getQuantity())));
}
return total;
}
}
5. 前端交互与优化
5.1 AJAX实现动态交互
使用jQuery实现购物车添加功能:
javascript复制function addToCart(productId) {
let quantity = 1; // 默认数量1,可以从页面获取
$.ajax({
url: 'cart',
type: 'POST',
data: {
action: 'add',
productId: productId,
quantity: quantity
},
success: function(response) {
updateCartCount();
showMessage('已添加到购物车');
},
error: function(xhr) {
showMessage('添加失败: ' + xhr.responseText, 'error');
}
});
}
function updateCartCount() {
$.get('cart/count', function(count) {
$('#cart-count').text(count || 0);
});
}
对应的Servlet:
java复制@WebServlet("/cart/count")
public class CartCountServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart");
int count = 0;
if (cart != null) {
count = cart.values().stream().mapToInt(CartItem::getQuantity).sum();
}
response.setContentType("text/plain");
response.getWriter().print(count);
}
}
5.2 表单验证与用户体验
使用JavaScript增强表单验证:
javascript复制$(document).ready(function() {
$('#register-form').submit(function(e) {
let valid = true;
// 用户名验证
const username = $('#username').val().trim();
if (username.length < 4 || username.length > 20) {
showError('username', '用户名长度需在4-20个字符之间');
valid = false;
} else {
clearError('username');
}
// 密码强度验证
const password = $('#password').val();
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}/.test(password)) {
showError('password', '密码需包含大小写字母和数字,至少8位');
valid = false;
} else {
clearError('password');
}
// 确认密码
if ($('#confirm-password').val() !== password) {
showError('confirm-password', '两次输入的密码不一致');
valid = false;
} else {
clearError('confirm-password');
}
if (!valid) {
e.preventDefault();
}
});
function showError(field, message) {
$(`#${field}`).addClass('is-invalid');
$(`#${field}-error`).text(message).show();
}
function clearError(field) {
$(`#${field}`).removeClass('is-invalid');
$(`#${field}-error`).hide();
}
});
6. 安全防护与性能优化
6.1 常见Web安全防护
-
SQL注入防护:
- 始终使用PreparedStatement
- 对用户输入进行白名单验证
- 示例:
java复制// 错误做法 - 有SQL注入风险 String sql = "SELECT * FROM products WHERE name LIKE '%" + search + "%'"; // 正确做法 String sql = "SELECT * FROM products WHERE name LIKE ?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, "%" + search + "%");
-
XSS防护:
- JSP中使用JSTL的
<c:out>自动转义 - 或者使用ESAPI等库:
jsp复制<%@ taglib prefix="esapi" uri="http://www.owasp.org/index.php/Category:OWASP_Enterprise_Security_API" %> <p><esapi:encodeForHTML>${userInput}</esapi:encodeForHTML></p>
- JSP中使用JSTL的
-
CSRF防护:
-
在表单中添加CSRF Token:
java复制public class CSRFTokenManager { public static String generateToken(HttpSession session) { String token = UUID.randomUUID().toString(); session.setAttribute("csrfToken", token); return token; } public static boolean isValidToken(HttpServletRequest request) { String sessionToken = (String) request.getSession().getAttribute("csrfToken"); String requestToken = request.getParameter("csrfToken"); return sessionToken != null && sessionToken.equals(requestToken); } }在JSP中:
jsp复制<input type="hidden" name="csrfToken" value="${csrfToken}">
-
6.2 性能优化技巧
-
JSP预编译:
在Tomcat的context.xml中添加:xml复制<Context> <JarScanner> <JarScanFilter defaultPluggabilityScan="false"/> </JarScanner> <Manager pathname="" /> <Resources cachingAllowed="true" cacheMaxSize="100000" /> </Context> -
静态资源缓存:
配置Filter处理静态资源缓存头:java复制@WebFilter("/*") public class CacheControlFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; String path = httpRequest.getRequestURI(); if (path.startsWith("/static/")) { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setHeader("Cache-Control", "public, max-age=31536000"); } chain.doFilter(request, response); } } -
数据库查询优化:
- 为常用查询字段添加索引
- 使用连接池并合理配置参数
- 批量操作使用
addBatch()和executeBatch()
7. 项目部署与运维
7.1 生产环境部署
-
Tomcat优化配置(conf/server.xml):
xml复制<Connector port="8080" protocol="HTTP/1.1" maxThreads="200" minSpareThreads="25" maxConnections="1000" connectionTimeout="20000" redirectPort="8443" compression="on" compressionMinSize="2048" compressableMimeType="text/html,text/xml,text/css,text/javascript,application/json" /> -
MySQL生产配置(my.cnf):
ini复制[mysqld] innodb_buffer_pool_size = 1G # 根据服务器内存调整 innodb_log_file_size = 256M max_connections = 200 query_cache_size = 64M tmp_table_size = 64M max_heap_table_size = 64M -
使用Nginx反向代理:
nginx复制upstream tomcat { server 127.0.0.1:8080; keepalive 32; } server { listen 80; server_name yourdomain.com; location / { proxy_pass http://tomcat; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 365d; root /path/to/webapp/static; } }
7.2 日志与监控
-
日志配置:
使用log4j2记录应用日志(pom.xml添加依赖):xml复制<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.17.1</version> </dependency>log4j2.xml配置示例:
xml复制<Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> <RollingFile name="File" fileName="logs/app.log" filePattern="logs/app-%d{yyyy-MM-dd}.log.gz"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true"/> </Policies> </RollingFile> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Console"/> <AppenderRef ref="File"/> </Root> <Logger name="com.bubbletea" level="debug"/> </Loggers> </Configuration> -
异常邮件通知:
使用JavaMail发送异常通知:java复制public class ErrorMailer { private static final String SMTP_HOST = "smtp.yourmail.com"; private static final String SMTP_USER = "alerts@yourmail.com"; private static final String SMTP_PASS = "password"; private static final String TO_EMAIL = "admin@yourmail.com"; public static void sendErrorEmail(String subject, String content) { Properties props = new Properties(); props.put("mail.smtp.host", SMTP_HOST); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.port", "587"); props.put("mail.smtp.starttls.enable", "true"); Session session = Session.getInstance(props, new javax.mail.Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(SMTP_USER, SMTP_PASS); } }); try { Message message = new MimeMessage(session); message.setFrom(new InternetAddress(SMTP_USER)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(TO_EMAIL)); message.setSubject("[ERROR] " + subject); message.setText(content); Transport.send(message); } catch (MessagingException e) { Logger.getLogger(ErrorMailer.class).error("发送错误邮件失败", e); } } }在全局异常处理器中调用:
java复制@WebServlet("/errorHandler") public class ErrorHandlerServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception"); Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); String errorMessage = "状态码: " + statusCode + "\n"; errorMessage += "请求URI: " + request.getAttribute("javax.servlet.error.request_uri") + "\n"; if (throwable != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); errorMessage += "异常堆栈:\n" + sw.toString(); } // 记录日志 Logger.getLogger(ErrorHandlerServlet.class).error(errorMessage); // 发送邮件通知 ErrorMailer.sendErrorEmail("系统异常", errorMessage); // 显示错误页面 request.getRequestDispatcher("/error.jsp").forward(request, response); } }
8. 项目扩展与进阶
8.1 添加支付功能
集成支付宝支付接口示例:
- 添加支付宝SDK依赖:
xml复制<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.10.109.ALL</version>
</dependency>
- 支付Servlet实现:
java复制@WebServlet("/payment/alipay")
public class AlipayServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
String orderId = request.getParameter("orderId");
if (orderId == null || orderId.isEmpty()) {
response.sendRedirect("orders.jsp");
return;
}
try (Connection conn = DBCPInitListener.getConnection()) {
// 查询订单信息
String sql = "SELECT total_amount FROM orders WHERE order_id=? AND user_id=? AND status='待支付'";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, orderId);
stmt.setInt(2, user.getUserId());
ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
response.sendRedirect("orders.jsp");
return;
}
BigDecimal amount = rs.getBigDecimal("total_amount");
// 创建支付宝客户端
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
"your_app_id",
"your_private_key",
"json",
"UTF-8",
"alipay_public_key",
"RSA2");
// 创建支付请求
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl("http://yourdomain.com/payment/return");
alipayRequest.setNotifyUrl("http://yourdomain.com/payment/notify");
// 业务参数
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", orderId);
bizContent.put("total_amount", amount.toString());
bizContent.put("subject", "奶茶店订单支付");
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
alipayRequest.setBizContent(bizContent.toString());
// 调用支付接口
String form = alipayClient.pageExecute(alipayRequest).getBody();
// 返回支付页面
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(form);
} catch (Exception e) {
throw new ServletException("支付处理失败", e);
}
}
}
8.2 添加后台管理功能
使用AdminLTE构建后台管理界面:
- 管理员登录Filter:
java复制@WebFilter("/admin/*")
public class AdminFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
if (session == null || session.getAttribute("admin") == null) {
httpResponse.sendRedirect(httpRequest.getContextPath() + "/admin/login.jsp");
return;
}
chain.doFilter(request, response);
}
}
- 订单管理Servlet示例:
java复制@WebServlet("/admin/orders")
public class AdminOrderServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int page = 1;
try {
page = Integer.parseInt(request.getParameter("page"));
} catch (NumberFormatException e) {
// 使用默认值
}
String status = request.getParameter("status");
try (Connection conn = DBCPInitListener.getConnection()) {
// 查询订单总数
String countSql = "SELECT COUNT(*) FROM orders";
if (status != null && !status.isEmpty()) {
countSql += " WHERE status=?";
}
PreparedStatement countStmt = conn.prepareStatement(countSql);
if (status != null && !status.isEmpty()) {
countStmt.setString(1, status);
}
ResultSet countRs = countStmt.executeQuery();
countRs.next();
int total = countRs.getInt(1);
int totalPages = (int) Math.ceil((double)total / 10);
// 查询订单数据
String dataSql = "SELECT o.*, u.username FROM orders o JOIN users u ON o.user_id=u.user_id";
if (status != null && !status.isEmpty()) {
dataSql += " WHERE o.status=?";
}
dataSql += " ORDER BY o.create_time DESC LIMIT ?,10";
PreparedStatement dataStmt = conn.prepareStatement(dataSql);
int paramIndex = 1;
if (status != null && !status.isEmpty()) {
dataStmt.setString(paramIndex++, status);
}
dataStmt.setInt(paramIndex, (page-1)*10);
ResultSet dataRs = dataStmt.executeQuery();
List<Order> orders = new ArrayList<>();
while (dataRs.next()) {
Order order = new Order();
order.setOrderId(dataRs.getString("order_id"));
order.setUsername(dataRs.getString("username"));
order.setTotalAmount(dataRs.getBigDecimal("total_amount"));
order.setStatus(dataRs.getString("status"));
order.setCreateTime(dataRs.getTimestamp("create_time"));
order.setPayTime(dataRs.getTimestamp("pay_time"));
orders.add(order);
}
request.setAttribute("orders", orders);
request.setAttribute("currentPage", page);
request.setAttribute("totalPages", totalPages);
request.setAttribute("status",
