河北省科技政策查询系统2

本系统面向河北省科技政策的浏览与管理需求,主要服务于两类用户:
普通用户/访客:可在首页查看热门政策,通过关键字、类型、年份、发布机关等多维度组合查询,并查看政策详情。
管理员:在登录之后,可对政策数据进行新增、编辑、删除等维护操作。
1.2 架构设计
系统采用经典的 MVC 三层架构,职责清晰、层次分明:
① 表现层(View)
JSP 页面(index.jsp、list.jsp、detail.jsp、login.jsp、admin.jsp、edit.jsp)
② 控制层(Controller)
Servlet(IndexServlet、PolicyListServlet、PolicyDetailServlet、LoginServlet、AdminServlet、PolicyEditServlet、PolicyDeleteServlet、LogoutServlet、EncodingFilter)
③ 业务/数据访问层(Model/DAO)
PolicyDao、UserDao + Entity(Policy、User)+ 工具类 DBUtil
1.3 模块划分与核心流程
模块 对应文件 功能说明
首页展示 IndexServlet + index.jsp 加载热门政策、类型/年份统计,提供搜索入口
政策列表与检索 PolicyListServlet + list.jsp 支持关键字 + 类型 + 年份 + 机关的分页查询
政策详情 PolicyDetailServlet + detail.jsp 展示单条政策的全部字段
管理员登录 LoginServlet + login.jsp 账号密码验证,Session 保存登录状态
后台管理 AdminServlet + admin.jsp 管理员进入列表管理界面
新增 / 编辑 PolicyEditServlet + edit.jsp 处理政策的新增与编辑提交
删除 PolicyDeleteServlet 按 ID 删除指定政策
编码过滤器 EncodingFilter 统一请求/响应字符集,防止中文乱码
数据库工具 DBUtil 封装 JDBC 连接与资源释放逻辑
1.4 数据库设计
系统以核心表 policy 存储政策数据,表字段涵盖政策名称、类型、分类、范围、文档号、发布机关、通过/发布/实施日期、领域、主题、关键词、上级政策、前后续政策、状态、正文、PDF 链接、冗余度、层级、政策关键词、新层级、年份、新关键词、二级主题以及总和等丰富信息。同时创建 user 表存储管理员账号信息。
1.5 关键设计要点
动态条件构造:在 PolicyDao 中通过 StringBuilder 动态拼接 WHERE 子句,实现可选多字段组合查询,避免 SQL 硬编码冗余。
PreparedStatement 参数化:所有 SQL 均采用占位符,既防止 SQL 注入,又提高批量执行效率。
分页查询:利用 MySQL 的 LIMIT start, size 实现列表分页,并在 Servlet 中计算 totalPage 供 JSP 渲染页码导航。
统一编码处理:通过 EncodingFilter 对所有请求与响应设置 UTF-8,解决中文参数传递乱码。
Session 鉴权:通过 Session 中的 user 对象识别管理员身份,避免越权访问后台。
二、源程序代码
2.1 实体类 —— Policy.java
src/entity/Policy.java
package entity;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Policy implements Serializable {
private Long id;
private String name;
private String type;
private String category;
private String range;
private String document;
private String form;
private String organ;
private Date viadata;
private Date pubdata;
private Date perdata;
private String field;
private String theme;
private String keyword;
private String superior;
private String precursor;
private String succeed;
private String state;
private String text;
private String pdf;
private String redundancy;
private String rank;
private String policykey;
private String newrank;
private String year;
private String newkey;
private String secondtheme;
private Integer allsum;

public Policy() {}

// getter / setter 省略(完整见项目源文件)...

public String formatDate(Date d) {
if (d == null) return "";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(d);
}
}
2.2 实体类 —— User.java
src/entity/User.java
package entity;

import java.io.Serializable;

public class User implements Serializable {
private Integer id;
private String username;
private String password;
private String role;

public User() {}

public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
}
2.3 数据库工具类 —— DBUtil.java
src/util/DBUtil.java
package util;

import java.sql.*;

public class DBUtil {
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3306/febs?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false";
private static final String USER = "root";
private static final String PASSWORD = "D123";

static {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASSWORD);
}

public static void close(ResultSet rs, Statement stmt, Connection conn) {
try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if (stmt != null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); }
}

public static void close(PreparedStatement pstmt, Connection conn) {
close(null, pstmt, conn);
}
}
2.4 数据访问层 —— PolicyDao.java(核心查询方法)
src/dao/PolicyDao.java
package dao;

import entity.Policy;
import util.DBUtil;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class PolicyDao {

public int countByConditions(String keyword, String type, String year, String organ) throws SQLException {
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM policy WHERE 1=1");
List params = new ArrayList<>();
buildCondition(sql, params, keyword, type, year, organ);
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
pstmt = conn.prepareStatement(sql.toString());
setParams(pstmt, params);
rs = pstmt.executeQuery();
if (rs.next()) return rs.getInt(1);
} finally {
DBUtil.close(rs, pstmt, conn);
}
return 0;
}

public List findByConditions(String keyword, String type, String year, String organ, int pageNo, int pageSize) throws SQLException {
StringBuilder sql = new StringBuilder("SELECT * FROM policy WHERE 1=1");
List params = new ArrayList<>();
buildCondition(sql, params, keyword, type, year, organ);
sql.append(" ORDER BY id DESC LIMIT ?,?");
params.add((pageNo - 1) * pageSize);
params.add(pageSize);
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List list = new ArrayList<>();
try {
conn = DBUtil.getConnection();
pstmt = conn.prepareStatement(sql.toString());
setParams(pstmt, params);
rs = pstmt.executeQuery();
while (rs.next()) list.add(mapRow(rs));
} finally {
DBUtil.close(rs, pstmt, conn);
}
return list;
}

public Policy findById(Long id) throws SQLException {
String sql = "SELECT * FROM policy WHERE id=?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setLong(1, id);
rs = pstmt.executeQuery();
if (rs.next()) return mapRow(rs);
} finally {
DBUtil.close(rs, pstmt, conn);
}
return null;
}

// ======== 新增 / 更新 / 删除 方法(完整见源文件)========
public boolean save(Policy policy) throws SQLException { ... }
public boolean update(Policy policy) throws SQLException { ... }
public boolean delete(Long id) throws SQLException { ... }

// ======== 工具方法 ========
private void buildCondition(StringBuilder sql, List params,
String keyword, String type, String year, String organ) {
if (keyword != null && !keyword.trim().isEmpty()) {
sql.append(" AND (name LIKE ? OR keyword LIKE ? OR policykey LIKE ?)");
String kw = "%" + keyword.trim() + "%";
params.add(kw); params.add(kw); params.add(kw);
}
if (type != null && !type.trim().isEmpty()) {
sql.append(" AND type=?");
params.add(type);
}
if (year != null && !year.trim().isEmpty()) {
sql.append(" AND year=?");
params.add(year);
}
if (organ != null && !organ.trim().isEmpty()) {
sql.append(" AND organ LIKE ?");
params.add("%" + organ.trim() + "%");
}
}

private void setParams(PreparedStatement pstmt, List params) throws SQLException {
for (int i = 0; i < params.size(); i++) {
Object p = params.get(i);
if (p instanceof Integer) {
pstmt.setInt(i + 1, (Integer) p);
} else {
pstmt.setString(i + 1, String.valueOf(p));
}
}
}

private Policy mapRow(ResultSet rs) throws SQLException {
Policy p = new Policy();
p.setId(rs.getLong("id"));
p.setName(rs.getString("name"));
p.setType(rs.getString("type"));
p.setCategory(rs.getString("category"));
// ... 其余字段映射完整见源文件 ...
return p;
}
}

2.5 控制层 —— PolicyListServlet.java

src/servlet/PolicyListServlet.java

package servlet;

import dao.PolicyDao;
import entity.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;
import java.sql.SQLException;
import java.util.List;

@WebServlet("/policyList")
public class PolicyListServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
process(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
process(req, resp);
}

private void process(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");

String keyword = req.getParameter("keyword");
String type = req.getParameter("type");
String year = req.getParameter("year");
String organ = req.getParameter("organ");

int pageNo = 1;
int pageSize = 10;
try {
String p = req.getParameter("pageNo");
if (p != null && !p.trim().isEmpty()) pageNo = Integer.parseInt(p.trim());
} catch (NumberFormatException e) {
pageNo = 1;
}
if (pageNo < 1) pageNo = 1;

PolicyDao dao = new PolicyDao();
try {
int totalCount = dao.countByConditions(keyword, type, year, organ);
int totalPage = (totalCount + pageSize - 1) / pageSize;
if (pageNo > totalPage && totalPage > 0) pageNo = totalPage;

List policies = dao.findByConditions(keyword, type, year, organ, pageNo, pageSize);
req.setAttribute("policies", policies);
req.setAttribute("keyword", keyword);
req.setAttribute("type", type);
req.setAttribute("year", year);
req.setAttribute("organ", organ);
req.setAttribute("pageNo", pageNo);
req.setAttribute("totalCount", totalCount);
req.setAttribute("totalPage", totalPage);
} catch (SQLException e) {
e.printStackTrace();
req.setAttribute("error", "查询失败:" + e.getMessage());
}
req.getRequestDispatcher("/list.jsp").forward(req, resp);
}
}

2.6 登录控制 —— LoginServlet.java

src/servlet/LoginServlet.java

package servlet;

import dao.UserDao;
import entity.User;
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.sql.SQLException;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
if (req.getSession().getAttribute("user") != null) {
resp.sendRedirect(req.getContextPath() + "/admin");
return;
}
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
if (username == null || password == null || username.trim().isEmpty() || password.trim().isEmpty()) {
req.setAttribute("error", "请输入用户名和密码");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
return;
}
UserDao dao = new UserDao();
try {
User user = dao.login(username.trim(), password.trim());
if (user != null) {
req.getSession().setAttribute("user", user);
resp.sendRedirect(req.getContextPath() + "/admin");
} else {
req.setAttribute("error", "用户名或密码错误");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
} catch (SQLException e) {
e.printStackTrace();
req.setAttribute("error", "登录失败:" + e.getMessage());
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
}

2.7 编码过滤器 —— EncodingFilter.java

src/servlet/EncodingFilter.java

package servlet;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/*")
public class EncodingFilter implements Filter {

@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
chain.doFilter(request, response);
}
}

2.8 前端 JSP 示意(list.jsp 列表页)

web/list.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

政策列表 - 河北省科技政策查询系统

政策查询

关键字:类型:年份:



<c:forEach var="p" items="${policies}">








</c:forEach>
ID政策名称类型年份发布机关操作
${p.id} ${p.name} ${p.type} ${p.year} ${p.organ} 查看详情

共 ${totalCount} 条,当前第 ${pageNo} / ${totalPage} 页

项目亮点

① 清晰的 MVC 三层架构:View(JSP)、Controller(Servlet)、Model(DAO+Entity)职责隔离,易维护、易扩展。

② 灵活的多条件组合查询:通过 StringBuilder 动态拼接 SQL,加上 PreparedStatement 参数化查询,既避免了 SQL 注入,又让代码逻辑清晰。

③ 完善的分页与导航设计:在 DAO 层同时提供 countByConditions 与 findByConditions 方法,Servlet 计算 totalPage,JSP 层直接渲染分页链接。

④ 全站 UTF-8 编码处理:通过 EncodingFilter 统一拦截设置字符集,从源头避免中文乱码问题。

⑤ 基于 Session 的登录机制:登录成功后将 user 对象存入 Session,后台管理页以此鉴权,支持退出登录。

4.2 遇到的问题与解决方法

问题 1:MySQL 8.x 的驱动与时区问题

解决:使用
com.mysql.cj.jdbc.Driver(旧版为 com.mysql.jdbc.Driver),并在 URL 上加入 serverTimezone=Asia/Shanghai 参数,避免 Invalid Connection 属性异常。

问题 2:JSP 提交表单中文参数乱码

解决:在每个 Servlet 中显式调用
request.setCharacterEncoding("UTF-8"),并引入 EncodingFilter 统一拦截,同时确保 JSP 页面与 HTML meta 均声明为 UTF-8。

问题 3:LIMIT 子句参数不能直接拼接

解决:将 LIMIT 的起始位置与条数也作为 PreparedStatement 的
? 占位符参数传入,避免字符串拼接导致 SQL 注入风险。

问题 4:日期字段的存储与展示

解决:数据库使用 DATE 类型,Java 实体使用 java.util.Date;在插入时通过
new java.sql.Date(date.getTime()) 转换,展示时在 Policy 内封装 formatDate(Date) 方法统一输出 yyyy-MM-dd。

问题 5:Servlet 3.0 注解与 web.xml 的共存

解决:项目采用
@WebServlet 与 @WebFilter 注解方式注册,减少 web.xml 配置冗余;Tomcat 部署时需确保 metadata-complete 未设为 true,以支持注解扫描。

4.3 不足与改进方向

• 密码明文存储:当前 user 表的 password 字段直接存储明文,生产环境应改为 BCrypt / MD5 加盐存储。

• 缺少前台 / 后台权限细化:目前仅简单判断 Session 是否有 user,未来可加入 role 字段区分普通用户与管理员,并在 Filter 中进行权限拦截。

• JDBC 直接连接,未使用连接池:后续可引入 Druid / HikariCP 连接池以提高数据库访问性能。

• 前端样式较基础:可引入 Bootstrap 框架,或使用 Vue + ElementUI 改造为前后端分离架构。

• 缺少日志框架:可引入 SLF4J + Logback,替换现有 e.printStackTrace() 的粗放异常处理。

• 缺少前端参数校验:可以在 JSP 页面加入 JavaScript 非空校验,减轻服务端压力并提升用户体验。

4.4 个人收获

本项目是一次较完整的 Java Web 实战练习。通过从需求分析 → 架构设计 → 数据库建模 → 代码实现 → 部署运行的全流程实践,
不仅巩固了
Servlet、JSP、JDBC、JSTL 等核心知识点,也加深了对 MVC 架构思想、SQL 参数化查询、分页算法、
编码规范、异常处理
等工程实践的理解。特别是在多条件查询与 UTF-8 编码问题上,经历了"出现问题 → 定位原因 → 设计修复 → 验证回归"
的完整闭环,这为后续更复杂的项目开发打下了坚实基础。