4.20课后作业1
科技政策查询手机端系统设计与实现
系统设计思想
-
整体架构设计(MVC模式)
系统采用经典的MVC分层架构,职责清晰:
Model层:Policy(政策实体)、PolicyKind(政策分类实体)封装数据,对应数据库表字段;
Controller层:MobileServlet(处理手机端查询请求)、MobileDetailServlet(处理政策详情请求)接收前端请求,调用Dao层完成业务逻辑,转发数据到视图;
View层:mobile.jsp(政策列表查询页)、mobileDetail.jsp(政策详情页)采用响应式布局适配手机端;
Dao层:PolicyDao(政策数据操作)、PolicyKindDao(政策分类操作)封装数据库CRUD逻辑;
工具层:DBUtil(数据库连接池)基于Druid实现连接复用,提升性能。 -
核心功能设计
(1)关键字模糊查询
- 支持多维度条件筛选:政策名称、文号、发布机构、全文关键字、政策类型;
- 全文模糊匹配:关键字同时匹配政策名称和政策正文(
text字段); - 分页查询:默认每页10条数据,计算总页数,避免移动端数据加载过多。
(2)政策分类统计
查询政策分类(type字段)及对应政策数量,展示在查询页侧边/顶部,方便用户按分类快速筛选。
(3)政策详情查看
点击政策名称跳转新窗口(移动端适配的详情页),展示政策全文、发布机构、发布日期等完整信息。
- 数据库设计
核心表为policy,存储政策全量信息,关键字段包括:
| 字段名 | 类型 | 说明 |
| id | bigint | 主键(自增)|
| name | varchar(255)| 政策名称 |
| type | varchar(255)| 政策分类 |
| organ | varchar(255)| 发布机构 |
| document | varchar(255)| 政策文号 |
| viadata | date | 发布日期 |
| text | longtext | 政策全文 |
三、核心源代码实现
- 数据库连接工具(DBUtil.java)
java
package com.hebei.tech.util;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DBUtil {
private static final DruidDataSource dataSource;
static {
dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/febs?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&useUnicode=true");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setInitialSize(5); // 初始连接数
dataSource.setMaxActive(20); // 最大活跃连接数
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void close(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- 手机端查询请求处理(MobileServlet.java)
java
package com.hebei.tech.servlet;
import com.hebei.tech.dao.PolicyDao;
import com.hebei.tech.dao.PolicyKindDao;
import com.hebei.tech.model.Policy;
import com.hebei.tech.model.PolicyKind;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/mobile")
public class MobileServlet extends HttpServlet {
private final PolicyDao policyDao = new PolicyDao();
private final PolicyKindDao kindDao = new PolicyKindDao();
private static final int PAGE_SIZE = 10; // 移动端每页展示10条
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// 1. 获取前端查询参数
String keyword = request.getParameter("keyword");
String docNumber = request.getParameter("docNumber");
String issuingAgency = request.getParameter("issuingAgency");
String typeId = request.getParameter("typeId");
String pageNumStr = request.getParameter("pageNum");
// 2. 分页参数处理
int pageNum = 1;
if (pageNumStr != null && !pageNumStr.isBlank()) {
try {
pageNum = Integer.parseInt(pageNumStr);
} catch (NumberFormatException e) {
pageNum = 1;
}
}
// 3. 数据查询:分类列表、政策列表、总数计算
List<PolicyKind> kindList = new ArrayList<>();
List<Policy> policyList = new ArrayList<>();
int totalCount = 0;
int totalPage = 0;
try {
kindList = kindDao.findAllWithCount(); // 分类及数量
policyList = policyDao.findByCondition(
keyword, docNumber, issuingAgency, keyword, typeId, pageNum, PAGE_SIZE
); // 多条件查询
totalCount = policyDao.countByCondition(
keyword, docNumber, issuingAgency, keyword, typeId
); // 总条数
totalPage = (totalCount + PAGE_SIZE - 1) / PAGE_SIZE; // 总页数
} catch (Exception e) {
e.printStackTrace();
}
// 4. 绑定数据到请求域
request.setAttribute("kindList", kindList);
request.setAttribute("policyList", policyList);
request.setAttribute("totalCount", totalCount);
request.setAttribute("totalPage", totalPage);
request.setAttribute("currentPage", pageNum);
request.setAttribute("keyword", keyword);
request.setAttribute("docNumber", docNumber);
request.setAttribute("issuingAgency", issuingAgency);
request.setAttribute("selectedTypeId", typeId);
// 5. 转发到移动端列表页
request.getRequestDispatcher("/mobile.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("<html><body><h1>系统错误</h1><pre>" + e.toString() + "</pre></body></html>");
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response); // Post请求复用Get逻辑
}
}
- 政策详情请求处理(MobileDetailServlet.java)
java
package com.hebei.tech.servlet;
import com.hebei.tech.dao.PolicyDao;
import com.hebei.tech.model.Policy;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/mobile/detail")
public class MobileDetailServlet extends HttpServlet {
private final PolicyDao policyDao = new PolicyDao();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
String id = request.getParameter("id");
Policy policy = null;
// 根据ID查询政策详情
if (id != null && !id.isBlank()) {
try {
policy = policyDao.findById(id);
} catch (Exception e) {
e.printStackTrace();
}
}
request.setAttribute("policy", policy);
// 转发到移动端详情页
request.getRequestDispatcher("/mobileDetail.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("<html><body><h1>错误</h1><pre>" + e.toString() + "</pre></body></html>");
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
- 移动端政策列表页(mobile.jsp)
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!-- 政策分类 -->
<div class="kind-list">
<h4>政策分类</h4>
<c:forEach items="${kindList}" var="kind">
<a href="${pageContext.request.contextPath}/mobile?typeId=${kind.typeId}" class="item">
${kind.type}(${kind.policyCount})
</a>
</c:forEach>
</div>
<!-- 政策列表 -->
<div class="policy-list">
<h4>政策列表(共${totalCount}条)</h4>
<c:choose>
<c:when test="${empty policyList}">
<p style="text-align: center; color: #999; margin: 20px 0;">暂无符合条件的政策</p>
</c:when>
<c:otherwise>
<c:forEach items="${policyList}" var="policy">
<div class="policy-item">
<!-- 点击政策名称打开新窗口查看详情 -->
<a href="${pageContext.request.contextPath}/mobile/detail?id=${policy.id}" target="_blank">
${policy.policyName}
</a>
<div class="info">
文号:${policy.documentNumber} | 发布机构:${policy.issuingAgency} | 发布日期:${policy.issueDate}
</div>
</div>
</c:forEach>
</c:otherwise>
</c:choose>
<!-- 分页栏 -->
<div class="page-bar">
<c:if test="${currentPage > 1}">
<a href="${pageContext.request.contextPath}/mobile?keyword=${keyword}&docNumber=${docNumber}&issuingAgency=${issuingAgency}&typeId=${selectedTypeId}&pageNum=${currentPage-1}">上一页</a>
</c:if>
<a href="#" class="current">第${currentPage}页/共${totalPage}页</a>
<c:if test="${currentPage < totalPage}">
<a href="${pageContext.request.contextPath}/mobile?keyword=${keyword}&docNumber=${docNumber}&issuingAgency=${issuingAgency}&typeId=${selectedTypeId}&pageNum=${currentPage+1}">下一页</a>
</c:if>
</div>
</div>
- 移动端政策详情页(mobileDetail.jsp)
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
政策文号:${policy.documentNumber}
政策类型:${policy.policyTypeName}
发布机构:${policy.issuingAgency}
发布日期:${policy.issueDate}
四、运行结果截图说明
-
手机端查询页
界面:顶部为搜索区(关键字、文号、发布机构输入框+类型下拉框),中间为政策分类标签(带数量统计),底部为政策列表(含政策名称、文号、发布机构、日期),最下方是分页栏;
交互:输入关键字“科技创新”,点击查询,列表展示匹配的政策;点击分类标签“综合”,筛选出该类型所有政策。 -
政策详情页
交互:点击政策名称(如“关于促进科技创新的若干政策”),打开新窗口;
界面:顶部显示政策标题,中间为基础信息(文号、类型、发布机构、日期),底部为政策全文,文字自适应手机屏幕宽度,支持滑动查看。 -
空数据场景
输入无匹配的关键字(如“测试123456”),列表显示“暂无符合条件的政策”,界面无报错。
五、编程总结分析
-
开发亮点
性能优化:使用Druid连接池复用数据库连接,避免频繁创建/关闭连接;分页查询减少移动端数据加载量,提升响应速度;
用户体验:移动端响应式布局,适配不同屏幕尺寸;分类标签带数量统计,分页栏简洁易用;
容错处理:参数格式校验(如页码非数字默认置1)、异常捕获(数据库操作异常打印日志,前端友好提示)。 -
问题与解决方案
| 问题 | 解决方案 |
| 中文乱码 | 数据库连接串指定characterEncoding=UTF-8,JSP设置charset=UTF-8,响应设置UTF-8|
| 分页计算错误 | 总页数公式:(totalCount + PAGE_SIZE - 1) / PAGE_SIZE,避免整除丢失页数 |
| 移动端适配失效 | 添加<meta name="viewport" content="width=device-width, initial-scale=1.0">| -
可优化方向
增加关键字高亮:查询结果中高亮显示匹配的关键字;
缓存优化:对政策分类、热门政策做Redis缓存,减少数据库查询;
前端美化:引入移动端UI框架(如Vant),提升界面美观度;
全文检索:替换为Elasticsearch,提升海量数据下的查询效率。
六、PSP0级时间记录日志
PSP(Personal Software Process)0级聚焦基础的过程记录,本次开发时间日志如下(单位:分钟):
| 阶段 | 预估时间 | 实际时间 | 说明 |
| Planning(计划) | 30 | 25 | 确定需求、拆分开发任务、预估各阶段时间 |
| Requirements(需求分析) | 40 | 45 | 梳理核心功能(模糊查询、详情查看)、技术栈选型 |
| Design(设计) | 60 | 70 | 架构设计(MVC)、数据库表设计、页面原型设计 |
| Coding(编码) | 240 | 270 | 编写Dao、Servlet、JSP代码,调试数据库连接 |
| Testing(测试) | 60 | 80 | 功能测试(查询、详情、分页)、兼容性测试(不同手机浏览器)、异常场景测试 |
| Postmortem(总结) | 30 | 35 | 整理问题、总结优化点、编写文档 |
| 总计 | 460 | 525 | 实际耗时增加主要因调试数据库编码和分页逻辑 |
时间分析
测试阶段耗时超预估:因新增了“空数据”“非法参数”等异常场景测试,覆盖更全面;
编码阶段耗时超预估:JSP响应式样式调试需适配不同手机尺寸,花费额外时间。
浙公网安备 33010602011771号