1. 项目概述与核心价值
这个基于JavaWeb和MySQL的JSP+Servlet活动管理系统,是一个典型的Web应用开发实战案例。作为一名有多年JavaWeb开发经验的工程师,我认为这类系统特别适合中小型组织管理内部活动,比如企业部门活动、学校社团活动或社区公益活动。
系统采用经典的MVC分层架构:
- 前端使用Bootstrap框架确保响应式布局
- 中间层通过Servlet处理业务逻辑
- 数据持久层采用JDBC连接MySQL数据库
这种技术组合的优势在于:
- 开发效率高:JSP可以快速构建动态页面
- 维护成本低:Servlet使业务逻辑集中管理
- 兼容性强:Bootstrap适配各种终端设备
- 性能稳定:MySQL提供可靠的数据存储
提示:虽然现在流行Spring框架,但掌握原生Servlet开发仍是JavaWeb工程师的基本功,能帮助理解底层原理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境搭建
2.1 基础软件准备
我推荐使用以下开发环境组合:
- IDE:IntelliJ IDEA Ultimate(学生可申请免费许可)
- JDK:Java 8或11(LTS版本)
- 服务器:Tomcat 9.x
- 数据库:MySQL 8.0
- 构建工具:Maven 3.6+
安装时特别注意:
- MySQL安装时要选择"Legacy Authentication"选项
- Tomcat配置中需要设置URIEncoding="UTF-8"
- IDEA中需安装Lombok插件
2.2 项目结构设计
标准的Maven项目结构如下:
code复制src/
├── main/
│ ├── java/ # Java源代码
│ │ └── com/
│ │ └── example/
│ │ ├── controller/ # Servlet
│ │ ├── dao/ # 数据访问
│ │ ├── model/ # 实体类
│ │ └── util/ # 工具类
│ ├── resources/ # 配置文件
│ └── webapp/ # Web资源
│ ├── WEB-INF/
│ │ ├── lib/ # 依赖jar包
│ │ └── web.xml # 部署描述符
│ ├── css/ # 样式表
│ ├── js/ # JavaScript
│ └── jsp/ # JSP页面
└── test/ # 测试代码
3. 数据库设计与实现
3.1 数据表结构
核心表设计示例:
sql复制CREATE TABLE `activity` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`content` text,
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL,
`location` varchar(200) DEFAULT NULL,
`max_people` int(11) DEFAULT NULL,
`current_people` int(11) DEFAULT '0',
`status` tinyint(4) DEFAULT '0' COMMENT '0-未开始 1-进行中 2-已结束',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 数据库连接池配置
推荐使用Druid连接池,在web.xml中配置:
xml复制<context-param>
<param-name>jdbcUrl</param-name>
<param-value>jdbc:mysql://localhost:3306/activity_db?useSSL=false&serverTimezone=UTC</param-value>
</context-param>
<context-param>
<param-name>username</param-name>
<param-value>root</param-value>
</context-param>
然后在ServletContextListener中初始化:
java复制public class DBInitListener implements ServletContextListener {
private DataSource dataSource;
@Override
public void contextInitialized(ServletContextEvent sce) {
// 初始化连接池
DruidDataSource ds = new DruidDataSource();
ds.setUrl(sce.getServletContext().getInitParameter("jdbcUrl"));
// ...其他参数配置
this.dataSource = ds;
sce.getServletContext().setAttribute("dataSource", ds);
}
}
4. 核心功能实现
4.1 Servlet控制器设计
活动列表查询的Servlet示例:
java复制@WebServlet("/activity/list")
public class ActivityListServlet extends HttpServlet {
private ActivityDao activityDao;
@Override
public void init() throws ServletException {
this.activityDao = new ActivityDaoImpl(
(DataSource) getServletContext().getAttribute("dataSource")
);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int page = Integer.parseInt(request.getParameter("page") == null ? "1" : request.getParameter("page"));
int size = 10;
List<Activity> activities = activityDao.findByPage((page-1)*size, size);
int total = activityDao.count();
request.setAttribute("activities", activities);
request.setAttribute("totalPages", (int) Math.ceil(total/(double)size));
request.getRequestDispatcher("/WEB-INF/jsp/activity/list.jsp").forward(request, response);
}
}
4.2 JSP页面开发
使用Bootstrap的活动列表页示例:
jsp复制<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="container mt-3">
<table class="table table-hover">
<thead class="thead-dark">
<tr>
<th>活动名称</th>
<th>开始时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<c:forEach items="${activities}" var="act">
<tr>
<td>${act.title}</td>
<td><fmt:formatDate value="${act.startTime}" pattern="yyyy-MM-dd HH:mm"/></td>
<td>
<c:choose>
<c:when test="${act.status == 0}">
<span class="badge badge-secondary">未开始</span>
</c:when>
<c:when test="${act.status == 1}">
<span class="badge badge-success">进行中</span>
</c:when>
<c:otherwise>
<span class="badge badge-danger">已结束</span>
</c:otherwise>
</c:choose>
</td>
<td>
<a href="detail?id=${act.id}" class="btn btn-sm btn-info">详情</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
<nav>
<ul class="pagination">
<c:forEach begin="1" end="${totalPages}" var="i">
<li class="page-item ${i == param.page ? 'active' : ''}">
<a class="page-link" href="?page=${i}">${i}</a>
</li>
</c:forEach>
</ul>
</nav>
</div>
5. 常见问题与优化建议
5.1 中文乱码问题解决方案
在开发过程中最常见的三个乱码场景及解决方法:
- 表单提交乱码:
java复制// 在Servlet的doPost方法开始处添加
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
- 数据库乱码:
确保连接URL包含字符集参数:
code复制jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=UTF-8
- JSP页面乱码:
在页面顶部添加:
jsp复制<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
5.2 性能优化建议
- 连接池配置优化:
java复制ds.setInitialSize(5); // 初始连接数
ds.setMinIdle(5); // 最小空闲连接
ds.setMaxActive(20); // 最大连接数
ds.setMaxWait(60000); // 获取连接超时时间(毫秒)
ds.setTimeBetweenEvictionRunsMillis(60000); // 检测间隔
- JSP预编译:
在web.xml中添加:
xml复制<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>development</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
- 静态资源缓存:
配置Tomcat的DefaultServlet:
xml复制<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/static/*</url-pattern>
</servlet-mapping>
6. 项目部署与运维
6.1 生产环境部署
推荐部署方案:
- 服务器:CentOS 7+
- JDK:OpenJDK 11
- Tomcat配置:
- 设置JVM参数:-Xms512m -Xmx1024m
- 配置server.xml的Connector:
xml复制<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="200" minSpareThreads="10" enableLookups="false" URIEncoding="UTF-8" />
6.2 MySQL优化配置
my.cnf关键配置:
ini复制[mysqld]
innodb_buffer_pool_size = 256M
innodb_log_file_size = 64M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
max_connections = 100
query_cache_size = 32M
7. 扩展功能建议
7.1 添加用户权限控制
基于Filter实现简单的权限控制:
java复制@WebFilter("/*")
public class AuthFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String uri = request.getRequestURI();
if(uri.endsWith(".css") || uri.endsWith(".js") || uri.endsWith(".jpg")) {
chain.doFilter(req, resp);
return;
}
if(uri.endsWith("login.jsp") || uri.endsWith("login")) {
chain.doFilter(req, resp);
return;
}
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute("user") == null) {
response.sendRedirect(request.getContextPath() + "/login.jsp");
return;
}
chain.doFilter(req, resp);
}
}
7.2 添加活动报名功能
数据库新增报名表:
sql复制CREATE TABLE `activity_signup` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`activity_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`signup_time` datetime DEFAULT CURRENT_TIMESTAMP,
`status` tinyint(4) DEFAULT '0' COMMENT '0-待审核 1-已通过 2-已拒绝',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_activity_user` (`activity_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Servlet处理报名逻辑:
java复制@WebServlet("/activity/signup")
public class ActivitySignupServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int activityId = Integer.parseInt(request.getParameter("activityId"));
User user = (User) request.getSession().getAttribute("user");
try {
ActivityDao activityDao = new ActivityDaoImpl(dataSource);
Activity activity = activityDao.findById(activityId);
if(activity.getCurrentPeople() >= activity.getMaxPeople()) {
request.setAttribute("error", "该活动报名人数已满");
request.getRequestDispatcher("/activity/detail?id=" + activityId).forward(request, response);
return;
}
SignupDao signupDao = new SignupDaoImpl(dataSource);
if(signupDao.exists(activityId, user.getId())) {
request.setAttribute("error", "您已经报名过该活动");
request.getRequestDispatcher("/activity/detail?id=" + activityId).forward(request, response);
return;
}
signupDao.create(activityId, user.getId());
activityDao.incrementCurrentPeople(activityId);
response.sendRedirect(request.getContextPath() + "/activity/detail?id=" + activityId);
} catch (SQLException e) {
throw new ServletException(e);
}
}
}
在实际开发中,我发现使用原生Servlet开发虽然代码量比Spring Boot多,但对理解Web开发底层原理非常有帮助。建议初学者先掌握这种基础开发方式,再过渡到框架开发。
