1. 项目概述:JSP二手交易平台全栈开发实录
去年接手这个二手交易平台项目时,客户要求用最经典的JSP+Servlet组合实现。这种技术栈在当今Spring Boot大行其道的环境下看似过时,但对于教学演示和传统企业环境仍具有特殊价值。整个项目包含87个JSP页面、32个Servlet控制器,采用MVC分层架构,数据库使用MySQL 5.7,前端混用Bootstrap 3和原生jQuery。
这个平台最核心的交易流程涉及商品发布、在线沟通、订单生成、支付对接(模拟)和评价系统五个关键环节。在调试部署阶段,我们遇到Tomcat 9与JDK 1.8的兼容性问题,以及MySQL连接池配置不当导致的高并发崩溃,这些实战经验都会在后续章节详细拆解。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构解析
2.1 前端技术选型
采用JSP作为视图层主要基于三个考量:
- 项目方明确要求保留传统JSP特性
- 需要快速实现服务端渲染的动态页面
- 与现有企业系统保持技术栈统一
关键页面技术实现:
jsp复制<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="item-list">
<c:forEach items="${products}" var="product">
<div class="col-md-4">
<img src="${product.coverUrl}" class="thumbnail">
<h5>${product.name}</h5>
<p>价格:¥${product.price}</p>
</div>
</c:forEach>
</div>
注意:JSP中避免直接编写Java代码,使用JSTL和EL表达式实现逻辑控制,这是很多初学者容易犯的错误。
2.2 后端架构设计
Servlet控制器采用命令模式设计,通过前端传入的action参数动态路由:
java复制public class DispatcherServlet extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response) {
String action = request.getParameter("action");
switch(action) {
case "add_product":
new AddProductCommand().execute(request, response);
break;
case "place_order":
new PlaceOrderCommand().execute(request, response);
break;
// 其他命令分支...
}
}
}
数据库访问层采用DAO模式,通过DBUtils工具简化JDBC操作:
java复制public class ProductDaoImpl implements ProductDao {
private QueryRunner runner = new QueryRunner(DbUtil.getDataSource());
public int addProduct(Product product) throws SQLException {
String sql = "INSERT INTO products(name,price,user_id) VALUES(?,?,?)";
return runner.update(sql, product.getName(),
product.getPrice(), product.getUserId());
}
}
3. 核心业务模块实现
3.1 商品发布系统
采用多图片上传+富文本编辑的组合方案:
- 使用jQuery File Upload插件实现异步上传
- 百度UEditor作为富文本编辑器
- 图片存储采用本地目录+nginx静态资源映射
关键数据库表设计:
sql复制CREATE TABLE `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`price` decimal(10,2) NOT NULL,
`description` text,
`user_id` int(11) NOT NULL,
`category_id` int(11) DEFAULT NULL,
`status` tinyint(4) DEFAULT '1' COMMENT '1-在售 2-已售',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 交易流程实现
订单状态机设计:
code复制待支付 -> 已支付 -> 待发货 -> 已发货 -> 已完成
↘ 已取消 ↗
支付对接采用模拟流程:
- 生成支付流水号
- 跳转模拟支付页面
- 接收支付回调验证签名
重要:真实环境务必接入支付宝/微信官方SDK,此处仅为演示
4. 部署与性能优化
4.1 Tomcat生产环境配置
server.xml关键配置项:
xml复制<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
maxThreads="500"
minSpareThreads="30"
acceptCount="100"
URIEncoding="UTF-8"/>
4.2 MySQL性能调优
my.cnf核心参数:
ini复制[mysqld]
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
max_connections = 300
query_cache_size = 64M
连接池配置建议:
properties复制# Druid连接池配置
druid.initialSize=5
druid.maxActive=50
druid.maxWait=60000
druid.timeBetweenEvictionRunsMillis=60000
5. 常见问题解决方案
5.1 中文乱码问题
统一编码方案:
- JSP页面头部设置:
jsp复制<%@ page pageEncoding="UTF-8"%>
- web.xml添加过滤器:
xml复制<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
5.2 并发修改冲突
采用乐观锁机制:
sql复制UPDATE products SET stock=stock-1, version=version+1
WHERE id=100 AND version=5
6. 开发环境搭建指南
6.1 基础软件清单
- JDK 1.8
- Eclipse IDE for Java EE
- Tomcat 9.x
- MySQL 5.7
- Navicat Premium
6.2 项目导入步骤
- 创建数据库:执行docs/sql/init.sql
- Eclipse导入:File > Import > Existing Projects into Workspace
- 配置Tomcat:Server runtime environment选择Tomcat 9
- 修改数据库连接:src/main/resources/jdbc.properties
7. 源码结构解析
code复制src/
├── main/
│ ├── java/
│ │ ├── com.woe47.controller/ # Servlet控制器
│ │ ├── com.woe47.dao/ # 数据访问层
│ │ ├── com.woe47.model/ # 实体类
│ │ └── com.woe47.util/ # 工具类
│ ├── resources/
│ │ ├── jdbc.properties # 数据库配置
│ │ └── log4j.properties # 日志配置
│ └── webapp/
│ ├── WEB-INF/
│ │ ├── lib/ # 依赖库
│ │ └── web.xml # 部署描述符
│ ├── css/ # 样式文件
│ ├── js/ # 脚本文件
│ └── upload/ # 上传目录
└── test/ # 测试代码
在项目开发过程中,我们发现JSP的taglib机制对复用公共页面片段特别有效。比如创建/WEB-INF/tags/pagination.tag实现分页组件:
jsp复制<%@ tag description="分页组件" pageEncoding="UTF-8"%>
<%@ attribute name="page" required="true" type="com.woe47.model.Page"%>
<div class="pagination">
<c:if test="${page.current > 1}">
<a href="?pageNo=${page.current-1}">上一页</a>
</c:if>
<c:forEach begin="1" end="${page.total}" var="i">
<a href="?pageNo=${i}" <c:if test="${i==page.current}">class="active"</c:if>>${i}</a>
</c:forEach>
<c:if test="${page.current < page.total}">
<a href="?pageNo=${page.current+1}">下一页</a>
</c:if>
</div>
数据库连接管理采用双重校验锁实现单例模式:
java复制public class DbUtil {
private static volatile DataSource dataSource;
public static DataSource getDataSource() {
if(dataSource == null) {
synchronized(DbUtil.class) {
if(dataSource == null) {
Properties props = new Properties();
// 加载配置...
dataSource = DruidDataSourceFactory.createDataSource(props);
}
}
}
return dataSource;
}
}
项目部署时遇到的一个典型问题是静态资源缓存。我们在nginx配置中添加了以下规则:
nginx复制location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
对于高并发场景下的商品详情页,我们实现了两级缓存策略:
- 使用Redis缓存热点商品数据
- 本地使用Caffeine做JVM级缓存
缓存更新采用"先更新数据库再删除缓存"的策略:
java复制public void updateProduct(Product product) {
// 1. 更新数据库
productDao.update(product);
// 2. 删除缓存
redis.del("product:"+product.getId());
// 3. 发送消息更新搜索索引
mq.sendUpdateMessage(product);
}
在开发支付模块时,安全性是需要重点考虑的。我们实现了以下防护措施:
- 所有敏感请求必须带CSRF Token
- 支付回调验证签名
- 金额使用BigDecimal避免浮点误差
- 关键操作记录审计日志
审计日志表设计示例:
sql复制CREATE TABLE `audit_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`operation` varchar(50) NOT NULL,
`method` varchar(100) DEFAULT NULL,
`params` text,
`ip` varchar(50) DEFAULT NULL,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
项目中的异常处理采用统一拦截方式:
java复制public class GlobalExceptionHandler implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
if(ex instanceof BusinessException) {
return new ModelAndView("error/business", "message", ex.getMessage());
}
logger.error("系统异常", ex);
return new ModelAndView("error/500", "message", "系统繁忙");
}
}
对于文件上传功能,我们做了以下安全限制:
- 限制文件类型:只允许jpg,png,pdf等
- 限制文件大小:单文件不超过5MB
- 重命名存储:避免原始文件名注入
- 病毒扫描:调用ClamAV进行检测
上传Servlet核心代码:
java复制DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(5 * 1024 * 1024); // 5MB
List<FileItem> items = upload.parseRequest(request);
for(FileItem item : items) {
if(!item.isFormField()) {
String ext = FilenameUtils.getExtension(item.getName());
if(!ALLOWED_EXTS.contains(ext)) {
throw new Exception("不支持的文件类型");
}
String saveName = UUID.randomUUID() + "." + ext;
File uploadedFile = new File(UPLOAD_DIR, saveName);
item.write(uploadedFile);
}
}
