diff --git a/Reader_注释 b/Reader_注释 new file mode 100644 index 0000000..6c2dfa5 --- /dev/null +++ b/Reader_注释 @@ -0,0 +1,4243 @@ +// 声明该类所在的包名为 javabean +package javabean; + +// 导入// 声明该类所在的包名为 javabean + package javabean; + + // 导入 java.sql 包中的 Connection 类,用于建立与数据库的连接 + import java.sql.Connection; + // 导入 java.sql 包中的 PreparedStatement 类,用于执行预编译的 SQL 语句 + import java.sql.PreparedStatement; + // 导入 java.sql 包中的 ResultSet 类,用于存储数据库查询结果 + import java.sql.ResultSet; + // 导入 java.sql 包中的 SQLException 类,用于处理 SQL 操作中可能出现的异常 + import java.sql.SQLException; + + // 定义一个名为 Reader 的公共类,用于处理读者登录相关操作 + public class Reader { + // 抑制“可能存在空指针引用”的警告 + @SuppressWarnings("null") + /** + * 该方法用于处理读者的登录逻辑 + * @param user 读者的账号 + * @param psw 读者的密码 + * @return 返回登录结果的提示信息 + * @throws ClassNotFoundException 如果在加载数据库驱动类时出现问题,抛出该异常 + * @throws SQLException 如果在执行 SQL 操作时出现问题,抛出该异常 + */ + public String login(String user, String psw) throws ClassNotFoundException, SQLException { + + // 检查账号是否为空或仅包含空白字符 + if (user == null || user.trim().equals("")) { + // 如果账号为空,返回提示信息 + return "账号不能为空"; + } + // 检查密码是否为空或仅包含空白字符 + else if (psw == null || psw.trim().equals("")) { + // 如果密码为空,返回提示信息 + return "密码不能为空"; + } + + // 声明一个 Connection 对象,用于存储与数据库的连接,初始值为 null + Connection connection = null; + // 声明一个 PreparedStatement 对象,用于执行预编译的 SQL 语句,初始值为 null + PreparedStatement pstmt = null; + // 声明一个 ResultSet 对象,用于存储数据库查询结果,初始值为 null + ResultSet resultSet = null; + + // 定义一个 SQL 查询语句,使用占位符 ? 来防止 SQL 注入 + String sql = "select * from borrow_card where ID=? and PASSWORD=?"; + + // 调用 Base 类的 getConnection 方法获取数据库连接,并将其赋值给 connection 对象 + connection = Base.getConnection(); + + // 使用 connection 对象创建一个 PreparedStatement 对象,用于执行预编译的 SQL 语句 + pstmt = (PreparedStatement) connection.prepareStatement(sql); + + // 为 SQL 语句中的第一个占位符(即 ID)设置值,值为传入的 user 参数 + pstmt.setString(1, user); + // 为 SQL 语句中的第二个占位符(即 PASSWORD)设置值,值为传入的 psw 参数 + pstmt.setString(2, psw); + + // 执行 SQL 查询语句,并将查询结果存储在 resultSet 对象中 + resultSet = pstmt.executeQuery(); + + // 检查结果集中是否有下一行记录 + if (resultSet.next()) { + // 如果有记录,说明账号和密码匹配,返回 "1" 表示登录成功 + return "1"; + } + + // 如果结果集中没有记录,说明账号或密码错误,返回提示信息 + return "账号或密码错误"; + } + } + // 声明该 Servlet 类所在的包名为 servlet.reader + package servlet.reader; + + import java.io.IOException; + import java.io.PrintWriter; + import java.sql.Connection; + import java.sql.PreparedStatement; + import java.sql.ResultSet; + import java.sql.SQLException; + + import javax.servlet.ServletException; + import javax.servlet.annotation.WebServlet; + import javax.servlet.http.HttpServlet; + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + + import javabean.Base; + import net.sf.json.JSONArray; + import net.sf.json.JSONObject; + + /** + * Servlet implementation class Book + * 该 Servlet 用于处理读者相关的书籍查询请求,返回符合条件的书籍信息的 JSON 数据 + */ + @WebServlet("/reader/book") + public class Book extends HttpServlet { + // 重写 doGet 方法,处理 HTTP GET 请求 + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // 设置响应的内容类型为 JSON 格式,并指定字符编码为 UTF-8 + resp.setContentType("application/json; charset=utf8"); + + // 接收客户端传递的参数 + // limit 表示每页显示的记录数 + String limit = req.getParameter("limit"); + // page 表示当前页码 + String page = req.getParameter("page"); + // condition 表示查询的条件字段 + String condition = (String) req.getParameter("condition"); + // conditionValue 表示查询条件字段的值 + String conditionValue = (String) req.getParameter("conditionValue"); + + // 初始化 SQL 查询的 WHERE 子句为空,表示无限制条件 + String where = ""; + + // 如果 page 参数为空,默认设置为第 1 页 + if (page == null) { + page = "1"; + } + // 如果 limit 参数为空,默认设置每页显示 10 条记录 + if (limit == null) { + limit = "10"; + } + + // 准备数据库操作所需的对象 + // 数据库连接对象 + Connection connection = null; + // 用于执行查询书籍信息的预编译 SQL 语句对象 + PreparedStatement pstmt = null; + // 用于执行查询书籍总数的预编译 SQL 语句对象 + PreparedStatement countPstmt = null; + // 存储查询书籍信息的结果集 + ResultSet resultSet = null; + // 存储查询书籍总数的结果集 + ResultSet countSet = null; + // 存储查询书籍信息的 SQL 语句 + String sql = ""; + // 存储查询书籍总数的 SQL 语句 + String countSql = ""; + + // 准备返回给客户端的参数 + // 状态码,1 表示无数据,0 表示查询成功 + int code = 1; + // 提示信息,默认提示无数据 + String msg = "无数据"; + // 符合条件的书籍总数,初始化为 0 + int count = 0; + + // 用于存储单条书籍信息的 JSON 对象 + JSONObject jsonData = new JSONObject(); + // 用于存储所有书籍信息的 JSON 数组 + JSONArray jsonArray = new JSONArray(); + // 用于存储最终返回给客户端的 JSON 数据,包含状态码、总数、提示信息和书籍信息数组 + JSONObject jsonResult = new JSONObject(); + + // 进行数据库查询操作 + try { + // 通过 Base 类的 getConnection 方法获取数据库连接 + connection = (Connection) Base.getConnection(); + // 初始化查询书籍信息的 SQL 语句,从 books 表中查询所有字段 + sql = "select * from books "; + // 如果查询条件字段和条件值都不为空,则添加 WHERE 子句到 SQL 语句中 + if (condition != null && conditionValue != null && !condition.equals("") && !conditionValue.equals("")) { + where = " where " + condition + " like '%" + conditionValue + "%' "; + sql += where; + } + // 添加分页查询的 LIMIT 子句到 SQL 语句中 + sql += " limit ?,?"; + // 预编译 SQL 语句 + pstmt = connection.prepareStatement(sql); + // 设置 LIMIT 子句的第一个参数,计算当前页的起始记录索引 + pstmt.setInt(1, (Integer.parseInt(page) - 1) * Integer.parseInt(limit)); + // 设置 LIMIT 子句的第二个参数,即每页显示的记录数 + pstmt.setInt(2, Integer.parseInt(limit)); + // 执行查询操作,获取结果集 + resultSet = pstmt.executeQuery(); + + // 遍历结果集 + while (resultSet.next()) { + // 获取书籍所在图书馆的 ID + String library = resultSet.getString("library_id"); + // 构建查询图书馆名称的 SQL 语句 + String sql1 = "select * from library where ID =" + library; + // 预编译该 SQL 语句 + PreparedStatement pstmt1 = connection.prepareStatement(sql1); + // 执行查询操作,获取结果集 + ResultSet rs1 = pstmt1.executeQuery(); + // 存储图书馆名称,初始为空 + String lib = ""; + // 遍历结果集,获取图书馆名称 + while (rs1.next()) { + lib = rs1.getString("name"); + } + + // 获取书籍的分类 ID + String sortid = resultSet.getString("sort_id"); + // 构建查询书籍分类名称的 SQL 语句 + String sql2 = "select * from book_sort where ID =" + sortid; + // 预编译该 SQL 语句 + PreparedStatement pstmt2 = connection.prepareStatement(sql2); + // 执行查询操作,获取结果集 + ResultSet rs2 = pstmt2.executeQuery(); + // 存储书籍分类名称,初始为空 + String sort = ""; + // 遍历结果集,获取书籍分类名称 + while (rs2.next()) { + sort = rs2.getString("name"); + } + + // 将书籍信息添加到 JSON 对象中 + jsonData.put("id", resultSet.getString("id")); + jsonData.put("name", resultSet.getString("name")); + jsonData.put("author", resultSet.getString("author")); + jsonData.put("library_id", lib); + jsonData.put("sort_id", sort); + jsonData.put("position", resultSet.getString("position")); + jsonData.put("status", resultSet.getString("status")); + jsonData.put("description", resultSet.getString("description")); + // 将单条书籍信息的 JSON 对象添加到 JSON 数组中 + jsonArray.add(jsonData); + // 清空 JSON 对象,以便存储下一条书籍信息 + jsonData = new JSONObject(); + } + + // 构建查询书籍总数的 SQL 语句 + countSql = "select count(*) as count from books "; + // 添加 WHERE 子句到总数查询的 SQL 语句中 + countSql += where; + // 预编译该 SQL 语句 + countPstmt = connection.prepareStatement(countSql); + // 执行查询操作,获取结果集 + countSet = countPstmt.executeQuery(); + // 从结果集中获取书籍总数 + if (countSet.next()) { + count = countSet.getInt("count"); + } + + // 如果 JSON 数组不为空,说明查询到了数据,将状态码设置为 0,提示信息设置为查询成功 + if (!jsonArray.isEmpty()) { + code = 0; + msg = "查询成功"; + } + + } catch (ClassNotFoundException e) { + // 如果在加载数据库驱动类时出现异常,将提示信息设置为 class 没找到 + msg = "class没找到"; + } catch (SQLException e) { + // 如果在执行 SQL 语句时出现异常,将提示信息设置为 sql 错误 + msg = "sql错误"; + } finally { + try { + // 关闭数据库资源,包括 PreparedStatement 和 ResultSet + Base.closeResource(null, pstmt, resultSet); + Base.closeResource(connection, countPstmt, countSet); + } catch (SQLException e) { + // 如果在关闭资源时出现异常,将提示信息设置为关闭资源失败 + msg = "关闭资源失败"; + } + } + + // 将状态码、总数、提示信息和书籍信息数组添加到最终的 JSON 对象中 + jsonResult.put("code", code); + jsonResult.put("count", count); + jsonResult.put("msg", msg); + jsonResult.put("data", jsonArray.toArray()); + // 获取响应的输出流 + PrintWriter out = resp.getWriter(); + // 将最终的 JSON 对象转换为字符串并输出到客户端 + out.print(jsonResult.toString()); + } + } + // 包声明,表明该类属于servlet.reader包 + package servlet.reader; + + // 导入处理输入输出异常的类 + import java.io.IOException; + // 导入用于向客户端发送字符文本的类 + import java.io.PrintWriter; + // 导入表示数据库连接的类 + import java.sql.Connection; + // 导入用于执行预编译SQL语句的类 + import java.sql.PreparedStatement; + // 导入用于存储数据库查询结果的类 + import java.sql.ResultSet; + // 导入处理SQL操作异常的类 + import java.sql.SQLException; + + // 导入处理Servlet异常的类 + import javax.servlet.ServletException; + // 导入用于注解Servlet映射路径的类 + import javax.servlet.annotation.WebServlet; + // 导入HttpServlet类,用于创建Servlet + import javax.servlet.http.HttpServlet; + // 导入表示HTTP请求的类 + import javax.servlet.http.HttpServletRequest; + // 导入表示HTTP响应的类 + import javax.servlet.http.HttpServletResponse; + // 导入用于管理用户会话的类 + import javax.servlet.http.HttpSession; + + // 导入自定义的数据库连接管理类 + import javabean.Base; + // 导入用于处理JSON数组的类 + import net.sf.json.JSONArray; + // 导入用于处理JSON对象的类 + import net.sf.json.JSONObject; + + /** + * Servlet实现类Borrow + * 该Servlet用于处理读者借阅记录的查询请求,返回符合条件的借阅记录的JSON数据 + */ + @WebServlet("/reader/borrow") + public class Borrow extends HttpServlet { + // 重写doGet方法,用于处理HTTP GET请求 + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // 设置响应的内容类型为JSON格式,并指定字符编码为UTF-8 + resp.setContentType("application/json; charset=utf8"); + + // 接收客户端传递的参数 + // limit表示每页显示的记录数 + String limit = req.getParameter("limit"); + // page表示当前页码 + String page = req.getParameter("page"); + // condition表示查询的条件字段 + String condition = (String) req.getParameter("condition"); + // conditionValue表示查询条件字段的值 + String conditionValue = (String) req.getParameter("conditionValue"); + + // 初始化SQL查询的WHERE子句为空,表示无限制条件 + String where = ""; + + // 如果page参数为空,默认设置为第1页 + if (page == null) { + page = "1"; + } + // 如果limit参数为空,默认设置每页显示10条记录 + if (limit == null) { + limit = "10"; + } + + // 准备数据库操作所需的对象 + // 数据库连接对象 + Connection connection = null; + // 用于执行查询借阅记录的预编译SQL语句对象 + PreparedStatement pstmt = null; + // 用于执行查询借阅记录总数的预编译SQL语句对象 + PreparedStatement countPstmt = null; + // 存储查询借阅记录的结果集 + ResultSet resultSet = null; + // 存储查询借阅记录总数的结果集 + ResultSet countSet = null; + // 存储查询借阅记录的SQL语句 + String sql = ""; + // 存储查询借阅记录总数的SQL语句 + String countSql = ""; + + // 准备返回给客户端的参数 + // 状态码,1表示无数据,0表示查询成功 + int code = 1; + // 提示信息,默认提示无数据 + String msg = "无数据"; + // 符合条件的借阅记录总数,初始化为0 + int count = 0; + + // 获取当前请求的会话对象 + HttpSession session = req.getSession(); + + // 用于存储单条借阅记录信息的JSON对象 + JSONObject jsonData = new JSONObject(); + // 用于存储所有借阅记录信息的JSON数组 + JSONArray jsonArray = new JSONArray(); + // 用于存储最终返回给客户端的JSON数据,包含状态码、总数、提示信息和借阅记录信息数组 + JSONObject jsonResult = new JSONObject(); + + // 进行数据库查询操作 + try { + // 通过Base类的getConnection方法获取数据库连接 + connection = (Connection) Base.getConnection(); + // 初始化查询借阅记录的SQL语句,从borrow_books表中查询所有字段,并且只查询当前读者的记录 + sql = "select * from borrow_books where card_id = " + session.getAttribute("reader"); + // 如果查询条件字段和条件值都不为空,则添加额外的WHERE子句到SQL语句中 + if (condition != null && conditionValue != null && !condition.equals("") && !conditionValue.equals("")) { + where = " and " + condition + " like '%" + conditionValue + "%' "; + sql += where; + } + // 添加分页查询的LIMIT子句到SQL语句中 + sql += " limit ?,?"; + // 打印生成的SQL语句,方便调试 + System.out.println("???" + sql); + // 预编译SQL语句 + pstmt = connection.prepareStatement(sql); + // 设置LIMIT子句的第一个参数,计算当前页的起始记录索引 + pstmt.setInt(1, (Integer.parseInt(page) - 1) * Integer.parseInt(limit)); + // 设置LIMIT子句的第二个参数,即每页显示的记录数 + pstmt.setInt(2, Integer.parseInt(limit)); + // 执行查询操作,获取结果集 + resultSet = pstmt.executeQuery(); + // 遍历结果集 + while (resultSet.next()) { + // 将借阅记录的各个字段信息添加到JSON对象中 + jsonData.put("id", resultSet.getString("id")); + jsonData.put("card_id", resultSet.getString("card_id")); + jsonData.put("book_id", resultSet.getString("book_id")); + jsonData.put("borrow_date", resultSet.getString("borrow_date")); + jsonData.put("end_date", resultSet.getString("end_date")); + jsonData.put("return_date", resultSet.getString("return_date")); + // 将单条借阅记录信息的JSON对象添加到JSON数组中 + jsonArray.add(jsonData); + // 清空JSON对象,以便存储下一条借阅记录信息 + jsonData = new JSONObject(); + } + // 构建查询借阅记录总数的SQL语句,同样只查询当前读者的记录 + countSql = "select count(*) as count from borrow_books where card_id = " + + req.getSession().getAttribute("reader"); + // 添加额外的WHERE子句到总数查询的SQL语句中 + countSql += where; + // 预编译该SQL语句 + countPstmt = connection.prepareStatement(countSql); + // 执行查询操作,获取结果集 + countSet = countPstmt.executeQuery(); + // 从结果集中获取借阅记录总数 + if (countSet.next()) { + count = countSet.getInt("count"); + } + // 如果JSON数组不为空,说明查询到了数据,将状态码设置为0,提示信息设置为查询成功 + if (!jsonArray.isEmpty()) { + code = 0; + msg = "查询成功"; + } + } catch (ClassNotFoundException e) { + // 如果在加载数据库驱动类时出现异常,将提示信息设置为class没找到 + msg = "class没找到"; + } catch (SQLException e) { + // 如果在执行SQL语句时出现异常,将提示信息设置为sql错误 + msg = "sql错误"; + } finally { + try { + // 关闭数据库资源,包括PreparedStatement和ResultSet + Base.closeResource(null, pstmt, resultSet); + Base.closeResource(connection, countPstmt, countSet); + } catch (SQLException e) { + // 如果在关闭资源时出现异常,将提示信息设置为关闭资源失败 + msg = "关闭资源失败"; + } + } + // 将状态码、总数、提示信息和借阅记录信息数组添加到最终的JSON对象中 + jsonResult.put("code", code); + jsonResult.put("count", count); + jsonResult.put("msg", msg); + jsonResult.put("data", jsonArray.toArray()); + // 获取响应的输出流 + PrintWriter out = resp.getWriter(); + // 将最终的JSON对象转换为字符串并输出到客户端 + out.print(jsonResult.toString()); + } + } + // 包声明:该Servlet属于servlet.reader包(读者相关Servlet) + package servlet.reader; + + import java.io.IOException; + + import javax.servlet.ServletException; + import javax.servlet.annotation.WebServlet; + import javax.servlet.http.HttpServlet; + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + import javax.servlet.http.HttpSession; + + /** + * Servlet实现类:退出登录处理 + * 功能:处理读者退出登录请求,销毁会话信息并跳转回登录页面 + */ + @WebServlet("/reader/exit") // 声明Servlet映射路径:/reader/exit + public class Exit extends HttpServlet { + // 序列化版本号(IDE自动生成,用于保证反序列化兼容性) + private static final long serialVersionUID = 1L; + + // 处理HTTP GET请求(退出操作一般通过GET发起) + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + // 获取当前用户会话 + HttpSession session = req.getSession(); + + // 检查会话中是否存在读者登录信息("reader"为登录时存储的会话属性) + if (session.getAttribute("reader") != null) { + // 移除会话中的读者登录信息,实现退出 + session.removeAttribute("reader"); + } + + // 重定向回读者登录页面(使用动态上下文路径保证部署灵活性) + // req.getContextPath() 获取当前应用的上下文路径(如:/LibrarySystem) + // 跳转目标:/reader/04readerFrame.jsp(读者登录页面) + resp.sendRedirect(req.getContextPath() + "/reader/04readerFrame.jsp"); + } + } + // 声明该Servlet类所在的包,用于处理读者相关的Servlet操作 + package servlet.reader; + + // 导入处理输入输出异常的类 + import java.io.IOException; + // 导入用于向客户端发送字符文本的类 + import java.io.PrintWriter; + // 导入表示数据库连接的类 + import java.sql.Connection; + // 导入用于执行预编译SQL语句的类 + import java.sql.PreparedStatement; + // 导入用于存储数据库查询结果的类 + import java.sql.ResultSet; + // 导入处理SQL操作异常的类 + import java.sql.SQLException; + + // 导入处理Servlet异常的类 + import javax.servlet.ServletException; + // 导入用于注解Servlet映射路径的类 + import javax.servlet.annotation.WebServlet; + // 导入HttpServlet类,用于创建Servlet + import javax.servlet.http.HttpServlet; + // 导入表示HTTP请求的类 + import javax.servlet.http.HttpServletRequest; + // 导入表示HTTP响应的类 + import javax.servlet.http.HttpServletResponse; + // 导入用于管理用户会话的类 + import javax.servlet.http.HttpSession; + + // 导入自定义的数据库连接管理类 + import javabean.Base; + // 导入用于处理JSON数组的类 + import net.sf.json.JSONArray; + // 导入用于处理JSON对象的类 + import net.sf.json.JSONObject; + + /** + * Servlet实现类Illegal + * 该Servlet用于处理读者违规借阅记录的查询请求,返回符合条件的违规借阅记录的JSON数据 + */ + @WebServlet("/reader/illegal") + public class Illegal extends HttpServlet { + // 重写doGet方法,用于处理HTTP GET请求 + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // 设置响应的内容类型为JSON格式,并指定字符编码为UTF-8 + resp.setContentType("application/json; charset=utf8"); + + // 接收客户端传递的参数 + // limit表示每页显示的记录数 + String limit = req.getParameter("limit"); + // page表示当前页码 + String page = req.getParameter("page"); + // condition表示查询的条件字段 + String condition = (String) req.getParameter("condition"); + // conditionValue表示查询条件字段的值 + String conditionValue = (String) req.getParameter("conditionValue"); + + // 初始化SQL查询的WHERE子句为空,表示无限制条件 + String where = ""; + + // 如果page参数为空,默认设置为第1页 + if (page == null) { + page = "1"; + } + // 如果limit参数为空,默认设置每页显示10条记录 + if (limit == null) { + limit = "10"; + } + + // 准备数据库操作所需的对象 + // 数据库连接对象 + Connection connection = null; + // 用于执行查询违规借阅记录的预编译SQL语句对象 + PreparedStatement pstmt = null; + // 用于执行查询违规借阅记录总数的预编译SQL语句对象 + PreparedStatement countPstmt = null; + // 存储查询违规借阅记录的结果集 + ResultSet resultSet = null; + // 存储查询违规借阅记录总数的结果集 + ResultSet countSet = null; + // 存储查询违规借阅记录的SQL语句 + String sql = ""; + // 存储查询违规借阅记录总数的SQL语句 + String countSql = ""; + + // 准备返回给客户端的参数 + // 状态码,1表示无数据,0表示查询成功 + int code = 1; + // 提示信息,默认提示无数据 + String msg = "无数据"; + // 符合条件的违规借阅记录总数,初始化为0 + int count = 0; + + // 获取当前请求的会话对象 + HttpSession session = req.getSession(); + + // 用于存储单条违规借阅记录信息的JSON对象 + JSONObject jsonData = new JSONObject(); + // 用于存储所有违规借阅记录信息的JSON数组 + JSONArray jsonArray = new JSONArray(); + // 用于存储最终返回给客户端的JSON数据,包含状态码、总数、提示信息和违规借阅记录信息数组 + JSONObject jsonResult = new JSONObject(); + + // 进行数据库查询操作 + try { + // 通过Base类的getConnection方法获取数据库连接 + connection = (Connection) Base.getConnection(); + // 初始化查询违规借阅记录的SQL语句,从borrow_books表中查询所有字段, + // 筛选出违规信息不为空且长度大于0的记录,并且只查询当前读者的记录 + sql = "select * from borrow_books where ILLEGAL is not null and length(trim(illegal))>0 AND CARD_ID = " + + session.getAttribute("reader"); + // 如果查询条件字段和条件值都不为空,则添加额外的WHERE子句到SQL语句中 + if (condition != null && conditionValue != null && !condition.equals("") && !conditionValue.equals("")) { + where = " and " + condition + " like '%" + conditionValue + "%' "; + sql += where; + } + // 添加分页查询的LIMIT子句到SQL语句中 + sql += " limit ?,?"; + // 打印生成的SQL语句,方便调试 + System.out.println("???" + sql); + // 预编译SQL语句 + pstmt = connection.prepareStatement(sql); + // 设置LIMIT子句的第一个参数,计算当前页的起始记录索引 + pstmt.setInt(1, (Integer.parseInt(page) - 1) * Integer.parseInt(limit)); + // 设置LIMIT子句的第二个参数,即每页显示的记录数 + pstmt.setInt(2, Integer.parseInt(limit)); + // 执行查询操作,获取结果集 + resultSet = pstmt.executeQuery(); + // 遍历结果集 + while (resultSet.next()) { + // 将违规借阅记录的各个字段信息添加到JSON对象中 + jsonData.put("id", resultSet.getString("id")); + jsonData.put("card_id", resultSet.getString("card_id")); + jsonData.put("book_id", resultSet.getString("book_id")); + jsonData.put("borrow_date", resultSet.getString("borrow_date")); + jsonData.put("end_date", resultSet.getString("end_date")); + jsonData.put("return_date", resultSet.getString("return_date")); + jsonData.put("illegal", resultSet.getString("illegal")); + jsonData.put("manager_id", resultSet.getString("manager_id")); + // 将单条违规借阅记录信息的JSON对象添加到JSON数组中 + jsonArray.add(jsonData); + // 清空JSON对象,以便存储下一条违规借阅记录信息 + jsonData = new JSONObject(); + } + // 构建查询违规借阅记录总数的SQL语句,同样筛选出违规信息不为空且长度大于0的记录, + // 并且只查询当前读者的记录 + countSql = "select count(*) as count from borrow_books where ILLEGAL is not null and length(trim(illegal))>0 AND CARD_ID = " + + session.getAttribute("reader"); + // 添加额外的WHERE子句到总数查询的SQL语句中 + countSql += where; + // 预编译该SQL语句 + countPstmt = connection.prepareStatement(countSql); + // 执行查询操作,获取结果集 + countSet = countPstmt.executeQuery(); + // 从结果集中获取违规借阅记录总数 + if (countSet.next()) { + count = countSet.getInt("count"); + } + // 如果JSON数组不为空,说明查询到了数据,将状态码设置为0,提示信息设置为查询成功 + if (!jsonArray.isEmpty()) { + code = 0; + msg = "查询成功"; + } + } catch (ClassNotFoundException e) { + // 如果在加载数据库驱动类时出现异常,将提示信息设置为class没找到 + msg = "class没找到"; + } catch (SQLException e) { + // 如果在执行SQL语句时出现异常,将提示信息设置为sql错误 + msg = "sql错误"; + } finally { + try { + // 关闭数据库资源,包括PreparedStatement和ResultSet + Base.closeResource(null, pstmt, resultSet); + Base.closeResource(connection, countPstmt, countSet); + } catch (SQLException e) { + // 如果在关闭资源时出现异常,将提示信息设置为关闭资源失败 + msg = "关闭资源失败"; + } + } + // 将状态码、总数、提示信息和违规借阅记录信息数组添加到最终的JSON对象中 + jsonResult.put("code", code); + jsonResult.put("count", count); + jsonResult.put("msg", msg); + jsonResult.put("data", jsonArray.toArray()); + // 获取响应的输出流 + PrintWriter out = resp.getWriter(); + // 将最终的JSON对象转换为字符串并输出到客户端 + out.print(jsonResult.toString()); + } + } + // 声明该Servlet类所在的包,用于处理读者相关的Servlet操作 + package servlet.reader; + + // 导入处理输入输出异常的类 + import java.io.IOException; + // 导入用于向客户端发送字符文本的类 + import java.io.PrintWriter; + // 导入处理SQL操作异常的类 + import java.sql.SQLException; + // 导入HashMap类,用于存储键值对,方便组织响应数据 + import java.util.HashMap; + + // 导入处理Servlet异常的类 + import javax.servlet.ServletException; + // 导入用于注解Servlet映射路径的类 + import javax.servlet.annotation.WebServlet; + // 导入HttpServlet类,用于创建Servlet + import javax.servlet.http.HttpServlet; + // 导入表示HTTP请求的类 + import javax.servlet.http.HttpServletRequest; + // 导入表示HTTP响应的类 + import javax.servlet.http.HttpServletResponse; + // 导入用于管理用户会话的类 + import javax.servlet.http.HttpSession; + + // 导入自定义的Reader类,用于处理读者登录逻辑 + import javabean.Reader; + // 导入用于处理JSON对象的类 + import net.sf.json.JSONObject; + + /** + * 该Servlet用于处理读者的登录请求。 + * 当客户端发送POST请求到 /readerLogin 路径时,会验证读者的账号和密码, + * 并根据验证结果返回相应的JSON数据给客户端。 + */ + @WebServlet("/readerLogin") + public class ReaderLogin extends HttpServlet { + + /** + * 处理HTTP GET请求。 + * 此方法在该Servlet中只是简单地将当前应用的上下文路径返回给客户端, + * 一般登录操作不使用GET请求,这里只是一个默认的处理。 + * + * @param request 客户端的HTTP请求对象 + * @param response 服务器的HTTP响应对象 + * @throws ServletException 如果在处理请求过程中出现Servlet相关的错误 + * @throws IOException 如果在输入输出过程中出现错误 + */ + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + response.getWriter().append("Served at: ").append(request.getContextPath()); + } + + /** + * 处理HTTP POST请求,实现读者登录功能。 + * + * @param request 客户端的HTTP请求对象,包含读者输入的账号和密码 + * @param response 服务器的HTTP响应对象,用于返回登录结果的JSON数据 + * @throws ServletException 如果在处理请求过程中出现Servlet相关的错误 + * @throws IOException 如果在输入输出过程中出现错误 + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + // 设置响应的内容类型为JSON格式,并指定字符编码为UTF-8 + response.setContentType("application/json; charset=utf8"); + // 获取用于向客户端输出响应内容的PrintWriter对象 + PrintWriter out = response.getWriter(); + + // 从HTTP请求中获取读者输入的账号和密码 + String user = request.getParameter("user"); + String psw = request.getParameter("psw"); + + // 创建一个HashMap对象,用于存储要返回给客户端的响应信息, + // 键为字符串类型,值为对象类型 + HashMap hashMap = new HashMap(); + + // 创建Reader类的实例,用于调用登录验证方法 + Reader reader = new Reader(); + // 用于存储登录验证结果的变量 + String result = null; + try { + // 调用Reader类的login方法进行登录验证,传入账号和密码 + result = reader.login(user, psw); + } catch (ClassNotFoundException | SQLException e) { + // 捕获可能出现的类未找到异常或SQL异常,并打印异常堆栈信息 + e.printStackTrace(); + } + + // 判断登录验证结果是否为 "1","1" 表示登录成功 + if (result != null && result.equals("1")) { + // 获取当前请求的会话对象,如果不存在则创建一个新的会话 + HttpSession session = request.getSession(); + // 将登录的读者账号存储到会话中,方便后续使用 + session.setAttribute("reader", user); + // 设置一个标志,表示读者是首次登录(这里假设 "1" 表示登录状态) + session.setAttribute("reader_first", "1"); + // 向HashMap中添加登录成功的状态码 + hashMap.put("code", 0); + // 向HashMap中添加登录成功的提示信息 + hashMap.put("msg", "登录成功"); + // 向HashMap中添加登录成功后要跳转的页面URL + hashMap.put("url", request.getContextPath() + "/reader/01main.jsp"); + } else { + // 登录失败,向HashMap中添加登录失败的状态码 + hashMap.put("code", 1); + // 向HashMap中添加登录失败的提示信息,即登录验证结果中的错误信息 + hashMap.put("msg", result); + } + + // 将HashMap对象转换为JSON对象 + JSONObject json = JSONObject.fromObject(hashMap); + // 将JSON对象转换为字符串并通过PrintWriter输出到客户端 + out.write(json.toString()); + } + } + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + 图书馆欢迎页 + + + + + + + + + + + + + + <%-- 会话校验与自动跳转逻辑 --%> + <% + // 检查是否已登录且为首次访问(通过reader_first标记判断) + if (session.getAttribute("reader") != null + && session.getAttribute("reader_first") != null + && session.getAttribute("reader_first").equals("1")) { + + // 更新标记为已访问(防止重复跳转) + session.setAttribute("reader_first", "2"); + + // 首次登录时,通过JS跳转至框架页(使用parent.location实现嵌套页面跳转) + %> + + <% + } + %> + + + + + + + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + 读者导航栏 + + + + + + + + + + + + + + + + + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + + + + + 借阅者页面 + + + + + + + + + + + + + + + + + + <% + // 检查会话中是否存在"reader"属性,即判断用户是否已登录 + if(session.getAttribute("reader") == null){ + %> + + + <% + } else { + %> + + + <% + } + %> + + + + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + 图书查询管理 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + Insert title here + + + + + + + + + + + + + + + + + + + <% + // 检查会话中是否存在"reader"属性,若不存在则表示用户未登录 + if(session.getAttribute("reader")==null){%> + + <% + } + %> + +
+ + + + + + + + <%@ page import="java.sql.*" %> + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + + + + + + + + + + + + + + + +
+ + + + ❤图书馆公告栏,记得查收公告呀!❤ + +
+ + +
+ +

近期公告

+ + + + <% + // 定义SQL查询语句,从announcement表中查询所有记录 + String sql="select*from announcement"; + // 调用JavaBean的executeQuery方法执行SQL查询,并将结果存储在ResultSet对象中 + ResultSet rs = check.executeQuery(sql); + // 遍历ResultSet对象,处理每一条查询结果 + while (rs.next()) { + %> + +
+ +
+ + <%=rs.getString("TITLE") %> + + <%=rs.getString("PUBLISH_DATE") %> +
+ +
+ +

<%=rs.getString("DETAIL") %>

+
+
+ <% + } + %> +
+ + + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + Insert title here + + + + + + + + + + + + + + + + + + <% + // 检查会话中是否存在 "reader" 属性,如果不存在则表示用户未登录 + if(session.getAttribute("reader") == null){ + %> + + <% + } + %> + + + +
+ + + + + + + + <%@ page import="java.sql.*"%> + <%@ page import="java.util.*"%> + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + 修改密码 + + + + + + + + + <% + // 从请求中获取用户输入的新密码(第一次输入) + String psw1 = request.getParameter("psw1"); + // 从请求中获取用户输入的新密码(第二次输入) + String psw2 = request.getParameter("psw2"); + + // 输出两次输入的密码,用于调试,此处注释掉了 + //out.println(psw1 + " " + psw2); + + // 从会话中获取当前登录读者的ID,并将其转换为字符串类型 + String id = session.getAttribute("reader").toString(); + + // 检查两次输入的密码是否相同,且不为空或仅包含空格 + if (psw1.equals(psw2) && psw1 != null && psw2 != null && !psw1.trim().equals("") + && !psw2.trim().equals("")) { + // 构建SQL更新语句,将borrow_card表中对应ID的用户密码更新为新密码 + String sql = "update borrow_card set PASSWORD ='" + psw1 + "' where ID=" + id; + try { + // 调用JavaBean的executeUpdate方法执行SQL更新语句,并获取受影响的行数 + int i = check.executeUpdate(sql); + // 判断受影响的行数是否为1,即是否成功更新了一条记录 + if (i == 1) { + %> + + + <% + } else { + %> + + + <% + } + } catch (Exception e) { + %> + + + <% + } + } else { + %> + + + <% + } + %> + + + <%@ page import="java.sql.*" %> + <%@ page import="java.util.*" %> + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + + + + + + + + + + + + + + + + + + + + +

读者规则信息查看

+ +
+ <% + // 定义SQL查询语句,从rules表中查询所有记录 + String sql = "select * from rules"; + // 调用JavaBean的executeQuery方法执行SQL查询,并将结果存储在ResultSet对象中 + ResultSet rs = msg.executeQuery(sql); + // 遍历ResultSet对象,处理每一条查询结果 + while (rs.next()) { + // 判断借阅证规则编号是否为奇数 + if(Integer.parseInt(rs.getString("ID")) % 2== 1){ + %> + +

+ <% + } else { + %> + +

+ <% + } + } + %> +
+ + + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + +

+ +
+ +     + +     + + +
+
+
+
+ + + <%@ page import="java.sql.*" %> + <%@ page import="java.util.*" %> + <%@ page import="javabean.DateTime"%> + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + + + + + + + + + + + + + + + + + + + + <% + // 创建 DateTime 类的实例,用于获取日期时间 + DateTime date = new DateTime(); + // 获取当前日期时间 + String time = date.show(); + + // 从请求中获取用户输入的留言内容 + String mes = request.getParameter("msg"); + + try { + // 从会话中获取读者的借阅证编号,并转换为字符串 + String card = session.getAttribute("reader").toString(); + // 构建 SQL 插入语句,将读者的借阅证编号、留言内容和留言时间插入到 message 表中 + String sql = "insert into message(card_id,detail,public_date)values('" + card + "','" + mes + "','" + time + "');"; + + // 检查借阅证编号和留言内容是否不为空,且留言内容不是仅包含空白字符 + if (card != null && mes != null && !mes.trim().equals("")) { + // 调用 JDBCBean 实例的 executeUpdate 方法执行 SQL 插入语句,并获取受影响的行数 + int i = msg.executeUpdate(sql); + + // 如果受影响的行数为 1,说明插入成功 + if (i == 1) { + %> + + <% + } else { + %> + + <% + } + } else { + %> + + <% + } + %> + <% + // 捕获异常,当出现异常时执行以下操作 + } catch (Exception e) { + %> + + <% + } + %> + + + <%@ page import="java.sql.*" %> + <%@ page import="java.util.*" %> + <%@ page import="javabean.DateTime"%> + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + + + + + + + + + + + + + + + + + + + + + +

☆★留言板★☆

+ <% + // 定义 SQL 查询语句,从 message 表中查询 CARD_ID、DETAIL 和 PUBLIC_DATE 字段,并按 PUBLIC_DATE 降序排列 + String sql = "select CARD_ID,DETAIL,PUBLIC_DATE from message order by PUBLIC_DATE desc"; + // 调用 JavaBean 的 executeQuery 方法执行 SQL 查询,并将结果存储在 ResultSet 对象中 + ResultSet rs = msg.executeQuery(sql); + // 遍历 ResultSet 对象,处理每一条查询结果 + while (rs.next()) { + %> + +
+ +
+ +

<%=rs.getString("CARD_ID")%>

+ +
+ +

<%=rs.getString("DETAIL")%>

+
+ +

<%=rs.getString("PUBLIC_DATE")%>

+
+
+ +
+ <% + } + %> + + + <%@ page import="java.sql.*"%> + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + + + + + Nifty Modal Window Effects + + + + + + + + + + + + + + + + + + + + + + + <% + // 检查会话中是否存在 "reader" 属性,如果不存在则表示用户未登录 + if (session.getAttribute("reader") == null) { + %> + + <% + } + %> + + + + + +
+ +
+ +

个人信息 + + +

+
+ +
+
+ <% + try { + // 从会话中获取当前登录读者的借阅证编号 + String cardId = session.getAttribute("reader").toString(); + // 构建 SQL 查询语句,从 borrow_card 表中查询当前读者的信息 + String sql = "select*from borrow_card where ID = '" + cardId + "';"; + // 调用 JavaBean 的 executeQuery 方法执行 SQL 查询,并将结果存储在 ResultSet 对象中 + ResultSet rs = check.executeQuery(sql); + // 遍历查询结果 + while (rs.next()) { + // 获取借阅证编号 + int id = rs.getInt(1); + %> + +

 借阅证编号:<%=rs.getString("ID")%>


+ +

 借阅证姓名:<%=rs.getString("READER")%>


+ +

 规则编号:<%=rs.getString("RULE_ID")%>


+ +

 状态: + <% + // 根据 STATUS 字段的值判断借阅证状态 + if (rs.getString("STATUS").equals("1")) { + out.println("可用"); + } else { + out.println("挂失"); + } + %> +

+ <% + } + } catch (Exception e) { + // 异常处理,可根据实际需求添加日志记录等操作 + } + %> +
+
+ + +
+
+
+ +
+ + + + + + + + + + + + + package javabean; + + // 导入 java.sql 包中的相关类,用于与数据库进行交互 + import java.sql.Connection; + import java.sql.DriverManager; + import java.sql.PreparedStatement; + import java.sql.ResultSet; + import java.sql.SQLException; + + /** + * 该类提供了与数据库交互的基础方法,包括获取数据库连接、执行查询、执行更新以及释放资源等操作。 + */ + public class Base { + // 从 DBConstants 类中获取数据库驱动信息,并将其设置为常量,后续不可修改 + private static final String driver = DBConstants.driver; + // 从 DBConstants 类中获取数据库连接的 URL 信息,并将其设置为常量,后续不可修改 + private static final String url = DBConstants.url; + // 从 DBConstants 类中获取数据库用户名信息,并将其设置为常量,后续不可修改 + private static final String username = DBConstants.username; + // 从 DBConstants 类中获取数据库密码信息,并将其设置为常量,后续不可修改 + private static final String password = DBConstants.password; + + /** + * 获取数据库连接 + * + * @return 返回一个数据库连接对象,如果连接失败则返回 null + * @throws ClassNotFoundException 如果找不到数据库驱动类,抛出该异常 + */ + public static Connection getConnection() throws ClassNotFoundException { + // 初始化数据库连接对象为 null + Connection connection = null; + try { + // 加载数据库驱动类 + Class.forName(driver); + // 通过 DriverManager 获取数据库连接 + connection = (Connection) DriverManager.getConnection(url, username, password); + } catch (SQLException e) { + // 若发生 SQL 异常,打印异常堆栈信息 + e.printStackTrace(); + } + // 返回数据库连接对象 + return connection; + } + + /** + * 公共查询方法,用于执行 SQL 查询语句 + * + * @param connection 数据库连接对象,用于与数据库建立连接 + * @param preparedStatement 预编译的 SQL 语句对象,用于执行带参数的 SQL 语句 + * @param resultSet 结果集对象,用于存储查询结果 + * @param sql 要执行的 SQL 查询语句 + * @param params SQL 语句中的参数数组 + * @return 返回包含查询结果的结果集对象 + * @throws SQLException 如果执行 SQL 语句时发生错误,抛出该异常 + */ + public static ResultSet executequery(Connection connection, PreparedStatement preparedStatement, + ResultSet resultSet, String sql, Object[] params) throws SQLException { + // 如果预编译的 SQL 语句对象为空,则创建一个新的预编译对象 + if (preparedStatement == null) { + preparedStatement = (PreparedStatement) connection.prepareStatement(sql); + } + // 遍历参数数组,将参数设置到预编译的 SQL 语句中 + for (int i = 0; params != null && i < params.length; i++) { + preparedStatement.setObject(i + 1, params[i]); + } + // 执行查询语句,并将结果存储在结果集对象中 + resultSet = preparedStatement.executeQuery(); + // 返回结果集对象 + return resultSet; + } + + /** + * 公共修改方法,用于执行 SQL 更新、插入、删除等语句 + * + * @param connection 数据库连接对象,用于与数据库建立连接 + * @param preparedStatement 预编译的 SQL 语句对象,用于执行带参数的 SQL 语句 + * @param sql 要执行的 SQL 修改语句 + * @param params SQL 语句中的参数数组 + * @return 返回受影响的行数 + * @throws SQLException 如果执行 SQL 语句时发生错误,抛出该异常 + */ + public static int executeUpdate(Connection connection, PreparedStatement preparedStatement, String sql, + Object[] params) throws SQLException { + // 如果预编译的 SQL 语句对象为空,则创建一个新的预编译对象 + if (preparedStatement == null) { + preparedStatement = (PreparedStatement) connection.prepareStatement(sql); + } + // 遍历参数数组,将参数设置到预编译的 SQL 语句中 + for (int i = 0; params != null && i < params.length; i++) { + preparedStatement.setObject(i + 1, params[i]); + } + // 执行更新语句,并返回受影响的行数 + int updateRows = preparedStatement.executeUpdate(); + // 返回受影响的行数 + return updateRows; + } + + /** + * 释放数据库资源,包括结果集、预编译语句和数据库连接 + * + * @param connection 数据库连接对象 + * @param preparedStatement 预编译的 SQL 语句对象 + * @param resultSet 结果集对象 + * @return 如果所有资源都成功释放,返回 true;否则返回 false + * @throws SQLException 如果关闭资源时发生错误,抛出该异常 + */ + public static boolean closeResource(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet) + throws SQLException { + // 初始化标志位为 true,表示资源释放成功 + boolean flag = true; + // 如果结果集对象不为空,则尝试关闭结果集 + if (resultSet != null) { + try { + resultSet.close(); + // 关闭后将结果集对象置为 null,便于垃圾回收 + resultSet = null; + } catch (SQLException e) { + // 若关闭结果集时发生异常,打印异常堆栈信息,并将标志位设为 false + e.printStackTrace(); + flag = false; + } + } + // 如果预编译语句对象不为空,则尝试关闭预编译语句 + if (preparedStatement != null) { + try { + preparedStatement.close(); + // 关闭后将预编译语句对象置为 null,便于垃圾回收 + preparedStatement = null; + } catch (SQLException e) { + // 若关闭预编译语句时发生异常,打印异常堆栈信息,并将标志位设为 false + e.printStackTrace(); + flag = false; + } + } + // 如果数据库连接对象不为空,则尝试关闭数据库连接 + if (connection != null) { + try { + connection.close(); + // 关闭后将数据库连接对象置为 null,便于垃圾回收 + connection = null; + } catch (SQLException e) { + // 若关闭数据库连接时发生异常,打印异常堆栈信息,并将标志位设为 false + e.printStackTrace(); + flag = false; + } + } + // 返回标志位 + return flag; + } + } + package javabean; + + import java.sql.Connection; + import java.sql.PreparedStatement; + import java.sql.ResultSet; + import java.sql.SQLException; + import java.util.HashMap; + import java.util.TreeMap; + + /** + * Common 类提供了一些通用的数据库操作方法,例如获取指定表的行数和获取图书馆信息的映射。 + */ + public class Common { + + /** + * 获取指定表的行总数。 + * + * @param table 要查询的表名 + * @return 表中的行数,如果表名为空或者查询过程中出现异常则返回 0 + * @throws SQLException 如果在执行 SQL 语句时发生数据库访问错误 + * @throws ClassNotFoundException 如果无法找到数据库驱动类 + */ + public static int getCount(String table) throws SQLException, ClassNotFoundException { + // 检查传入的表名是否为空或 null,如果是则直接返回 0 + if (table == null || table.equals("")) { + return 0; + } + // 声明数据库连接对象 + Connection connection = null; + // 声明预编译 SQL 语句对象 + PreparedStatement pstmt = null; + // 声明结果集对象 + ResultSet resultSet = null; + // 通过 Base 类的 getConnection 方法获取数据库连接 + connection = Base.getConnection(); + try { + // 准备 SQL 查询语句,用于统计指定表的行数 + pstmt = connection.prepareStatement("select count(*) as count from " + table); + // 执行查询操作,将结果存储在结果集对象中 + resultSet = pstmt.executeQuery(); + // 将结果集指针移动到第一行 + resultSet.next(); + // 从结果集中获取名为 "count" 的列的值,并返回该值 + return resultSet.getInt("count"); + } catch (Exception e) { + // 如果查询过程中出现异常,返回 0 + return 0; + } finally { + // 无论查询是否成功,都调用 Base 类的 closeResource 方法关闭数据库连接、预编译语句和结果集 + Base.closeResource(connection, pstmt, resultSet); + } + } + + /** + * 获取图书馆信息的映射,键为图书馆的 ID,值为图书馆的名称。 + * + * @return 包含图书馆信息的 TreeMap,如果出现异常则返回 null + * @throws SQLException 如果在执行 SQL 语句时发生数据库访问错误 + */ + public static TreeMap getLibraryMap() throws SQLException { + // 声明数据库连接对象 + Connection connection = null; + // 声明用于查询图书馆信息的预编译 SQL 语句对象 + PreparedStatement libraryPstmt = null; + // 声明用于存储图书馆信息查询结果的结果集对象 + ResultSet librarySet = null; + // 定义查询图书馆信息的 SQL 语句 + String librarySql = "select id,name from library"; + + // 创建一个 TreeMap 用于存储图书馆信息,TreeMap 会根据键的自然顺序进行排序 + TreeMap map = new TreeMap(); + // 获取图书馆信息 + try { + // 通过 Base 类的 getConnection 方法获取数据库连接 + connection = (Connection) Base.getConnection(); + // 准备查询图书馆信息的 SQL 语句 + libraryPstmt = connection.prepareStatement(librarySql); + // 执行查询操作,将结果存储在结果集对象中 + librarySet = libraryPstmt.executeQuery(); + // 遍历结果集 + while (librarySet.next()) { + // 将图书馆的 ID 作为键,图书馆的名称作为值,存入 TreeMap 中 + map.put(librarySet.getString("id"), librarySet.getString("name")); + } + } catch (ClassNotFoundException e) { + // 如果找不到数据库驱动类,返回 null + return null; + } catch (SQLException e) { + // 如果执行 SQL 语句时发生异常,返回 null + return null; + } finally { + // 无论查询是否成功,都调用 Base 类的 closeResource 方法关闭数据库连接、预编译语句和结果集 + Base.closeResource(connection, libraryPstmt, librarySet); + } + // 返回存储图书馆信息的 TreeMap + return map; + } + + /** + * 主方法,用于测试 getLibraryMap 方法。 + * + * @param args 命令行参数 + * @throws SQLException 如果在执行 SQL 语句时发生数据库访问错误 + */ + public static void main(String[] args) throws SQLException { + // 调用 getLibraryMap 方法并打印结果 + System.out.println(Common.getLibraryMap()); + } + } + package javabean; + + import java.text.ParseException; + import java.text.SimpleDateFormat; + import java.util.Date; + + /** + * CompareDate 类用于比较两个日期字符串所表示的日期之间的天数差。 + */ + public class CompareDate { + /** + * 该方法用于计算两个日期字符串所表示的日期之间相差的天数。 + * + * @param Str1 第一个日期字符串,格式必须为 "yyyy-MM-dd HH:mm:ss" + * @param Str2 第二个日期字符串,格式必须为 "yyyy-MM-dd HH:mm:ss" + * @return 两个日期之间相差的天数,如果解析日期字符串时发生异常则返回 0 + */ + public static long show(String Str1, String Str2) { + // 用于存储两个日期之间的毫秒差值 + long between = 0; + // 创建一个 SimpleDateFormat 对象,指定日期字符串的格式为 "yyyy-MM-dd HH:mm:ss" + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + // 将第一个日期字符串解析为 Date 对象 + Date date1 = format.parse(Str1); + // 将第二个日期字符串解析为 Date 对象 + Date date2 = format.parse(Str2); + // 计算两个日期对象之间的毫秒差值 + between = (date2.getTime() - date1.getTime()); + // 打印第一个日期对象,方便调试查看 + System.out.println(date1); + // 打印第二个日期对象,方便调试查看 + System.out.println(date2); + } catch (ParseException e) { + // 如果在解析日期字符串时发生异常,打印异常堆栈信息 + e.printStackTrace(); + } + // 将毫秒差值转换为天数,一天有 24 小时,每小时 60 分钟,每分钟 60 秒,每秒 1000 毫秒 + long days = between / (24 * 60 * 60 * 1000); + // 返回计算得到的天数差值 + return days; + } + } + package javabean; + + import java.text.SimpleDateFormat; + import java.util.Calendar; + import java.util.Date; + + /** + * DateTime 类提供了一系列用于日期和时间处理的静态方法, + * 可以获取当前日期时间、计算指定天数偏移后的日期时间等。 + */ + public class DateTime { + + /** + * 获取当前日期和时间,格式为 "yyyy-MM-dd HH:mm:ss"。 + * + * @return 格式化后的当前日期和时间字符串 + */ + public static String show() { + // 创建一个 SimpleDateFormat 对象,指定日期时间的格式为 "yyyy-MM-dd HH:mm:ss" + SimpleDateFormat myFmt2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + // 获取当前日期和时间 + Date now = new Date(); + // 使用指定格式将当前日期和时间转换为字符串并返回 + return myFmt2.format(now); + } + + /** + * 获取指定天数偏移后的日期和时间,格式为 "yyyy-MM-dd HH:mm:ss"。 + * + * @param n 要偏移的天数,正数表示未来的日期,负数表示过去的日期 + * @return 格式化后的偏移日期和时间字符串 + */ + public static String show(int n) { + // 获取当前日期和时间 + Date d = new Date(); + // 创建一个 SimpleDateFormat 对象,指定日期时间的格式为 "yyyy-MM-dd HH:mm:ss" + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + // 将当前日期和时间按照指定格式转换为字符串 + String currdate = format.format(d); + // 获取一个 Calendar 实例,用于日期计算 + Calendar ca = Calendar.getInstance(); + // 在当前日期的基础上增加或减少指定的天数 + ca.add(Calendar.DATE, n); + // 获取计算后的日期 + d = ca.getTime(); + // 将计算后的日期按照指定格式转换为字符串 + String enddate = format.format(d); + // 返回计算后的日期和时间字符串 + return enddate; + } + + /** + * 获取指定天数偏移后的日期,只包含年、月、日,格式为 "yyyy-MM-dd"。 + * + * @param n 要偏移的天数,正数表示未来的日期,负数表示过去的日期 + * @return 格式化后的偏移日期字符串 + */ + public static String showDate(int n) { + // 获取当前日期和时间 + Date d = new Date(); + // 创建一个 SimpleDateFormat 对象,指定日期的格式为 "yyyy-MM-dd" + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + // 将当前日期按照指定格式转换为字符串 + String currdate = format.format(d); + // 获取一个 Calendar 实例,用于日期计算 + Calendar ca = Calendar.getInstance(); + // 在当前日期的基础上增加或减少指定的天数 + ca.add(Calendar.DATE, n); + // 获取计算后的日期 + d = ca.getTime(); + // 将计算后的日期按照指定格式转换为字符串 + String enddate = format.format(d); + // 返回计算后的日期字符串 + return enddate; + } + + /** + * 获取指定天数偏移后的日期,只包含月和日,格式为 "MM-dd"。 + * + * @param n 要偏移的天数,正数表示未来的日期,负数表示过去的日期 + * @return 格式化后的偏移日期字符串 + */ + public static String showMD(int n) { + // 获取当前日期和时间 + Date d = new Date(); + // 创建一个 SimpleDateFormat 对象,指定日期的格式为 "MM-dd" + SimpleDateFormat format = new SimpleDateFormat("MM-dd"); + // 将当前日期按照指定格式转换为字符串 + String currdate = format.format(d); + // 获取一个 Calendar 实例,用于日期计算 + Calendar ca = Calendar.getInstance(); + // 在当前日期的基础上增加或减少指定的天数 + ca.add(Calendar.DATE, n); + // 获取计算后的日期 + d = ca.getTime(); + // 将计算后的日期按照指定格式转换为字符串 + String enddate = format.format(d); + // 返回计算后的日期字符串 + return enddate; + } + } + package javabean; + + /** + * DBConstants 类用于存储数据库连接所需的常量信息, + * 这些常量包含了数据库驱动、连接 URL、用户名和密码, + * 方便在项目中统一管理和使用数据库连接相关的配置。 + */ + public class DBConstants { + /** + * 数据库驱动类的全限定名。这里使用的是 MySQL 8.x 及以上版本的 JDBC 驱动, + * 如果使用的是 MySQL 5.x 版本,驱动类名应为 "com.mysql.jdbc.Driver"。 + */ + public static final String driver = "com.mysql.cj.jdbc.Driver"; + + /** + * 数据库连接的 URL。该 URL 指定了要连接的数据库的地址、端口、数据库名以及一些连接参数。 + * - "jdbc:mysql://":表示使用 JDBC 连接 MySQL 数据库的协议。 + * - "localhost:3306":表示数据库服务器的地址为本地主机(localhost),端口号为 3306。 + * - "library":表示要连接的数据库名。 + * - "&useSSL=false":表示不使用 SSL 加密连接。 + * - "serverTimezone=UTC":指定数据库服务器的时区为协调世界时(UTC),避免时间处理上的问题。 + * - "useUnicode=true&characterEncoding=UTF-8":表示使用 Unicode 字符集,并将字符编码设置为 UTF-8, + * 确保能够正确处理各种语言的字符。 + */ + public static final String url = "jdbc:mysql://localhost:3306/library?&useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8"; + + /** + * 连接数据库时使用的用户名。这里使用的是 MySQL 的默认超级用户 "root", + * 在实际项目中,建议使用具有适当权限的普通用户来连接数据库,以提高安全性。 + */ + public static final String username = "root"; + + /** + * 连接数据库时使用的密码。这里的密码为 "123456", + * 在实际项目中,应使用更复杂、安全的密码,并妥善保管,避免泄露。 + */ + public static final String password = "123456"; + } + package javabean; + + import java.text.SimpleDateFormat; + import java.util.Calendar; + import java.util.Date; + + /** + * EndTime 类提供了一个静态方法,用于计算从当前日期开始经过指定天数后的日期和时间。 + */ + public class EndTime { + + /** + * 计算从当前日期开始经过指定天数后的日期和时间。 + * + * @param n 要增加的天数,可以是正数(表示未来的日期)或负数(表示过去的日期)。 + * @return 经过指定天数后的日期和时间,格式为 "yyyy-MM-dd HH:mm:ss"。 + */ + public static String show(int n) { + // 获取当前日期和时间 + Date d = new Date(); + // 创建一个 SimpleDateFormat 对象,用于格式化日期和时间,格式为 "yyyy-MM-dd HH:mm:ss" + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + // 将当前日期和时间格式化为字符串 + String currdate = format.format(d); + // 打印当前日期和时间 + System.out.println("现在的日期是:" + currdate); + + // 获取一个 Calendar 实例,用于进行日期计算 + Calendar ca = Calendar.getInstance(); + // 在当前日期的基础上增加指定的天数 + ca.add(Calendar.DATE, n); + // 获取增加天数后的日期 + d = ca.getTime(); + // 将增加天数后的日期格式化为字符串 + String enddate = format.format(d); + // 返回增加天数后的日期和时间字符串 + return enddate; + } + } + package javabean; + + import java.sql.Connection; + import java.sql.DriverManager; + import java.sql.ResultSet; + import java.sql.Statement; + + /** + * JDBCBean 类封装了与数据库交互的基本操作,包括建立连接、执行更新、执行查询以及关闭连接等功能。 + */ + public class JDBCBean { + // 从 DBConstants 类中获取数据库驱动信息,使用 static final 修饰确保其不可变 + private static final String driver = DBConstants.driver; + // 从 DBConstants 类中获取数据库连接的 URL 信息,使用 static final 修饰确保其不可变 + private static final String url = DBConstants.url; + // 从 DBConstants 类中获取数据库用户名信息,使用 static final 修饰确保其不可变 + private static final String username = DBConstants.username; + // 从 DBConstants 类中获取数据库密码信息,使用 static final 修饰确保其不可变 + private static final String password = DBConstants.password; + // 数据库连接对象,用于与数据库建立连接 + private Connection conn = null; + // 用于执行 SQL 语句的 Statement 对象 + private Statement stmt = null; + + /** + * 构造函数,在创建 JDBCBean 对象时自动尝试与数据库建立连接。 + */ + public JDBCBean() { + try { + // 加载数据库驱动类 + Class.forName(driver); + // 通过 DriverManager 获取数据库连接 + conn = DriverManager.getConnection(url, username, password); + // 创建一个 Statement 对象,用于执行 SQL 语句 + stmt = conn.createStatement(); + // 若连接成功,打印提示信息 + System.out.println("同数据库建立连接!"); + } catch (Exception ex) { + // 若连接过程中出现异常,打印错误提示信息 + System.out.println("无法同数据库建立连接!"); + } + } + + /** + * 执行 SQL 更新语句(如 INSERT、UPDATE、DELETE)。 + * + * @param s 要执行的 SQL 更新语句 + * @return 执行更新操作后受影响的行数,若执行出错则返回 0 + */ + public int executeUpdate(String s) { + // 用于存储执行更新操作后受影响的行数 + int result = 0; + try { + // 打印要执行的 SQL 语句和 Statement 对象信息,方便调试 + System.out.println(s + "------" + stmt + "-----"); + // 执行 SQL 更新语句,并将受影响的行数赋值给 result + result = stmt.executeUpdate(s); + } catch (Exception e) { + // 若执行更新操作时出现异常,打印错误提示信息 + System.out.println("执行更新错误!"); + } + // 返回受影响的行数 + return result; + } + + /** + * 执行 SQL 查询语句(如 SELECT)。 + * + * @param s 要执行的 SQL 查询语句 + * @return 包含查询结果的 ResultSet 对象,若执行出错则返回 null + */ + public ResultSet executeQuery(String s) { + // 用于存储查询结果的 ResultSet 对象 + ResultSet rs = null; + try { + // 执行 SQL 查询语句,并将结果存储在 rs 中 + rs = stmt.executeQuery(s); + } catch (Exception e) { + // 若执行查询操作时出现异常,打印错误提示信息和异常信息 + System.out.println("执行查询错误! " + e.getMessage()); + } + // 返回包含查询结果的 ResultSet 对象 + return rs; + } + + /** + * 关闭数据库连接和 Statement 对象,释放资源。 + */ + public void close() { + try { + // 关闭 Statement 对象 + stmt.close(); + // 关闭数据库连接 + conn.close(); + } catch (Exception e) { + // 若关闭过程中出现异常,不做处理 + } + } + } + package javabean; + + import java.math.BigInteger; + import java.security.MessageDigest; + import java.security.NoSuchAlgorithmException; + import java.text.SimpleDateFormat; + import java.util.Date; + + import net.sf.json.JSONObject; + + /** + * Util 类提供了一系列实用工具方法,涵盖字符串处理、日期格式化、JSON 数据生成和 MD5 加密等功能。 + */ + public class Util { + + /** + * 计算字符串中指定子字符串的出现次数。 + * 此方法可用于计算 JSON 字符串中特定对象标识的数量。 + * + * @param str 要进行检查的原始字符串 + * @param contain 要查找的子字符串 + * @return 子字符串在原始字符串中出现的次数 + */ + public static int getCountString(String str, String contain) { + // 通过替换子字符串后计算长度差,再除以子字符串长度得到出现次数 + int count = (str.length() - str.replace(contain, "").length()) / contain.length(); + return count; + } + + /** + * 格式化从数据库取出的日期时间字符串,去除可能存在的 ".0" 后缀。 + * + * @param dateTime 从数据库取出的日期时间字符串 + * @return 去除 ".0" 后缀后的日期时间字符串,如果原字符串无 ".0" 或为空则返回原字符串或 null + */ + public static String getFormatDateTime(String dateTime) { + // 若日期时间字符串不为空且包含 ".0" + if (dateTime != null && dateTime.indexOf(".0") != -1) { + // 截取去除 ".0" 后的部分并返回 + return dateTime.substring(0, dateTime.length() - 2); + } else if (dateTime != null) { + // 若不包含 ".0" 且不为空,直接返回原字符串 + return dateTime; + } + // 若为空则返回 null + return null; + } + + /** + * 获取当前时间的字符串表示,格式为 "yyyy-MM-dd hh:mm:ss"。 + * + * @return 当前时间的格式化字符串 + */ + public static String getCurrentTimeString() { + // 获取当前日期对象 + Date date = new Date(); + // 定义日期时间格式化模式 + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + // 将日期对象按指定格式转换为字符串并返回 + return dateFormat.format(date); + } + + /** + * 生成包含状态码、消息和数据的 JSON 格式字符串响应。 + * + * @param code 状态码 + * @param msg 消息内容 + * @param data 附带的数据,如果为 null 则不包含在 JSON 中 + * @return 生成的 JSON 格式字符串 + */ + public static String jsonResponse(int code, String msg, String data) { + // 创建一个 JSONObject 对象 + JSONObject jsonObject = new JSONObject(); + // 向 JSON 对象中添加状态码 + jsonObject.put("code", code); + // 向 JSON 对象中添加消息内容 + jsonObject.put("msg", msg); + // 若数据不为空,则添加到 JSON 对象中 + if (data != null) { + jsonObject.put("data", data); + } + // 将 JSON 对象转换为字符串并返回 + return jsonObject.toString(); + } + + /** + * 对输入的字符串进行 MD5 加密。 + * + * @param plainText 待加密的明文字符串 + * @return 加密后的 32 位 MD5 字符串 + */ + public static String stringToMD5(String plainText) { + byte[] secretBytes = null; + try { + // 获取 MD5 算法的 MessageDigest 实例 + MessageDigest md = MessageDigest.getInstance("md5"); + // 对输入字符串的字节数组进行加密处理 + secretBytes = md.digest(plainText.getBytes()); + } catch (NoSuchAlgorithmException e) { + // 若指定的 MD5 算法不存在,抛出运行时异常 + throw new RuntimeException("没有这个md5算法!"); + } + // 将加密后的字节数组转换为 16 进制字符串 + String md5code = new BigInteger(1, secretBytes).toString(16); + // 补齐到 32 位 + for (int i = 0; i < 32 - md5code.length(); i++) { + md5code = "0" + md5code; + } + return md5code; + } + + /** + * 对密码进行加盐后的 MD5 加密。 + * + * @param password 原始密码 + * @return 加盐加密后的 MD5 字符串 + */ + public static String passMd5(String password) { + // 定义盐值 + String salt = "ew!.E"; + // 将密码和盐值拼接后进行 MD5 加密并返回结果 + return Util.stringToMD5(password + salt); + } + + /** + * 主方法,用于测试 passMd5 方法。 + * + * @param args 命令行参数 + */ + public static void main(String[] args) { + // 打印对 "admin" 密码加盐加密后的 MD5 结果 + System.out.println(Util.passMd5("admin")); + // 以下代码为注释掉的测试获取当前时间功能的代码 + // Date date = new Date(); + // SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + // System.out.println(dateFormat.format(date)); + } + } + /*! + * classie - class helper functions + * from bonzo https://github.com/ded/bonzo + * + * classie.has( elem, 'my-class' ) -> true/false + * classie.add( elem, 'my-new-class' ) + * classie.remove( elem, 'my-unwanted-class' ) + * classie.toggle( elem, 'my-class' ) + */ + + /*jshint browser: true, strict: true, undef: true */ + /*global define: false */ + + // 立即执行函数表达式 (IIFE),将 window 对象作为参数传入,避免全局作用域污染 + ( function( window ) { + + // 使用严格模式,开启更严格的语法检查 + 'use strict'; + + // 从 bonzo 库借鉴的类操作辅助函数 + // 该函数用于生成一个正则表达式,用于匹配元素类名中的指定类名 + function classReg( className ) { + // 正则表达式解释: + // (^|\\s+) 匹配字符串的开头或者一个或多个空白字符 + // className 是要匹配的类名 + // (\\s+|$) 匹配一个或多个空白字符或者字符串的结尾 + return new RegExp("(^|\\s+)" + className + "(\\s+|$)"); + } + + // 用于管理元素类名的函数声明 + // 这些函数将根据浏览器是否支持 classList 属性来实现不同的逻辑 + var hasClass, addClass, removeClass; + + // 检查浏览器是否支持 classList 属性 + if ( 'classList' in document.documentElement ) { + // 如果支持 classList 属性 + // hasClass 函数用于检查元素是否包含指定的类名 + hasClass = function( elem, c ) { + // 使用 classList 的 contains 方法检查元素是否包含指定类名 + return elem.classList.contains( c ); + }; + // addClass 函数用于给元素添加指定的类名 + addClass = function( elem, c ) { + // 使用 classList 的 add 方法添加指定类名 + elem.classList.add( c ); + }; + // removeClass 函数用于从元素中移除指定的类名 + removeClass = function( elem, c ) { + // 使用 classList 的 remove 方法移除指定类名 + elem.classList.remove( c ); + }; + } + else { + // 如果浏览器不支持 classList 属性 + // hasClass 函数使用正则表达式来检查元素是否包含指定的类名 + hasClass = function( elem, c ) { + // 使用 classReg 函数生成的正则表达式来测试元素的 className 属性 + return classReg( c ).test( elem.className ); + }; + // addClass 函数用于给元素添加指定的类名 + addClass = function( elem, c ) { + // 先检查元素是否已经包含指定的类名 + if ( !hasClass( elem, c ) ) { + // 如果不包含,则在元素的 className 属性后面添加该类名 + elem.className = elem.className + ' ' + c; + } + }; + // removeClass 函数用于从元素中移除指定的类名 + removeClass = function( elem, c ) { + // 使用 classReg 函数生成的正则表达式来替换元素 className 属性中的指定类名 + elem.className = elem.className.replace( classReg( c ), ' ' ); + }; + } + + // toggleClass 函数用于切换元素的指定类名 + function toggleClass( elem, c ) { + // 根据元素是否包含指定类名来选择调用 removeClass 或 addClass 函数 + var fn = hasClass( elem, c ) ? removeClass : addClass; + // 调用选择的函数来切换类名 + fn( elem, c ); + } + + // 创建一个 classie 对象,包含所有的类操作函数 + var classie = { + // 完整的函数名 + hasClass: hasClass, + addClass: addClass, + removeClass: removeClass, + toggleClass: toggleClass, + // 简短的函数名,方便使用 + has: hasClass, + add: addClass, + remove: removeClass, + toggle: toggleClass + }; + + // 模块加载机制的处理 + if ( typeof define === 'function' && define.amd ) { + // 如果使用 AMD 模块加载器(如 RequireJS),则使用 define 函数定义模块 + define( classie ); + } else { + // 如果没有使用 AMD 模块加载器,则将 classie 对象添加到 window 对象上,作为全局变量使用 + window.classie = classie; + } + + })( window ); + /*! + * css-filters-polyfill.js + * + * Author: Christian Schepp Schaefer + * Summary: A polyfill for CSS filter effects + * License: MIT + * Version: 0.22 + * + * URL: + * https://github.com/Schepp/ + * + */ + // 立即执行函数表达式,将 window 对象作为参数传入,避免全局变量污染 + ;(function(window){ + // 创建一个名为 polyfilter 的对象,用于封装所有与 CSS 滤镜填充相关的功能和属性 + var polyfilter = { + // Detect if we are dealing with IE <= 9 + // http://james.padolsey.com/javascript/detect-_ie-in-js-using-conditional-comments/ + // 检测当前浏览器是否为 IE 9 及以下版本 + _ie: (function(){ + var undef; + // 初始化版本号为 3 + var v = 3; + // 创建一个 div 元素 + var div = document.createElement('div'); + // 获取 div 元素内的所有 i 元素 + var all = div.getElementsByTagName('i'); + + // 通过条件注释来检测 IE 版本 + // 不断增加版本号 v,直到条件注释不满足(即当前 IE 版本不大于 v) + while( + // 设置 div 的 innerHTML 为条件注释 + div.innerHTML = '', + // 如果条件注释生效,会创建一个 i 元素,此时 all[0] 存在 + all[0] + ); + + // 如果 v 大于 4,说明检测到了 IE 版本,返回该版本号;否则返回 undefined + return v > 4 ? v : undef; + })(), + + // 用于缓存创建的 SVG 元素,避免重复创建 + _svg_cache: {}, + + // 创建 SVG 元素的辅助函数 + _create_svg_element: function(tagname,attributes){ + // SVG 的命名空间 + var xmlns = 'http://www.w3.org/2000/svg'; + // 使用 createElementNS 方法创建 SVG 元素 + var elem = document.createElementNS(xmlns,tagname); + // 遍历传入的属性对象 + for(var key in attributes){ + // 为 SVG 元素设置属性 + elem.setAttributeNS(null,key,attributes[key]); + } + + return elem; + }, + + // 创建完整 SVG 滤镜定义的函数 + _create_svg: function(id,filterelements){ + // SVG 的命名空间 + var xmlns = 'http://www.w3.org/2000/svg'; + // 创建一个 SVG 元素 + var svg = document.createElementNS(xmlns,'svg'); + // 设置 SVG 元素的宽度为 0 + svg.setAttributeNS(null,'width','0'); + // 设置 SVG 元素的高度为 0 + svg.setAttributeNS(null,'height','0'); + // 设置 SVG 元素的样式为绝对定位,避免影响页面布局 + svg.setAttributeNS(null,'style','position:absolute'); + + // 创建一个 filter 元素 + var svg_filter = document.createElementNS(xmlns,'filter'); + // 为 filter 元素设置 id + svg_filter.setAttributeNS(null,'id',id); + // 将 filter 元素添加到 SVG 元素中 + svg.appendChild(svg_filter); + + // 遍历传入的滤镜元素数组 + for(var i = 0; i < filterelements.length; i++){ + // 将每个滤镜元素添加到 filter 元素中 + svg_filter.appendChild(filterelements[i]); + } + + return svg; + }, + + // 记录待处理的外部样式表数量 + _pending_stylesheets: 0, + + // 存储所有样式表(内联和外部)的信息 + _stylesheets: [], + + // 判断是否处于开发模式 + _development_mode: (function(){ + // 检查当前页面的主机名是否为 localhost 或者以 .local 结尾,或者是 IP 地址 + if(location.hostname === 'localhost' || location.hostname.search(/.local$/) !== -1 || location.hostname.search(/\d+\.\d+\.\d+\.\d+/) !== -1){ + // 如果处于开发模式,在控制台输出提示信息 + if(window.console) console.log('Detected localhost or IP address. Assuming you are a developer. Caching of stylesheets is disabled.'); + return true; + } + // 如果不是开发模式,在控制台输出提示信息 + if(window.console) console.log('Caching of stylesheets is enabled. You need to refresh twice to see any changes.'); + return false; + })(), + + // 处理页面上所有样式表的函数 + process_stylesheets: function(){ + var xmlHttp = []; + + // 延迟 2 秒后检查库文件路径是否正确,避免干扰初始处理 + window.setTimeout(function(){ + var xmlHttpCheck; + // 根据浏览器支持情况创建 XMLHttpRequest 对象 + if (window.XMLHttpRequest) { + xmlHttpCheck = new XMLHttpRequest(); + } else if (window.ActiveXObject) { + xmlHttpCheck = new ActiveXObject("Microsoft.XMLHTTP"); + } + // 打开一个 GET 请求,检查 sepia.htc 文件是否存在 + xmlHttpCheck.open('GET', window.polyfilter_scriptpath + 'htc/sepia.htc', true); + // 监听请求状态变化 + xmlHttpCheck.onreadystatechange = function(){ + // 当请求完成且状态码不是 200 时,说明路径可能有误 + if(xmlHttpCheck.readyState == 4 && xmlHttpCheck.status != 200){ + // 弹出提示框,提示用户配置正确的绝对路径 + alert('The configured path \r\rvar polyfilter_scriptpath = "' + window.polyfilter_scriptpath + '"\r\rseems wrong!\r\rConfigure the polyfill\'s correct absolute(!) script path before referencing the css-filters-polyfill.js, like so:\r\rvar polyfilter_scriptpath = "/js/css-filters-polyfill/";\r\rLeaving IE dead in the water is no option. You damn Mac user... ;)'); + } + }; + try{ + // 发送请求 + xmlHttpCheck.send(null); + } catch(e){} + },2000); + + // 获取页面上所有的样式表元素 + var stylesheets = document.querySelectorAll ? document.querySelectorAll('style,link[rel="stylesheet"]') : document.getElementsByTagName('*'); + + // 遍历所有样式表元素 + for(var i = 0; i < stylesheets.length; i++){ + // 使用立即执行函数避免闭包问题 + (function(i){ + // 根据元素的节点名进行不同处理 + switch(stylesheets[i].nodeName){ + default: + // 如果节点名不是 STYLE 或 LINK,不做处理,直接跳出 + break; + + case 'STYLE': + // 当节点名是 STYLE 时,说明是内联样式表 + polyfilter._stylesheets.push({ + // 获取样式表的媒体查询属性,如果没有则默认为 'all' + media: stylesheets[i].media || 'all', + // 获取样式表的内容 + content: stylesheets[i].innerHTML + }); + break; + + case 'LINK': + // 当节点名是 LINK 时 + if(stylesheets[i].rel === 'stylesheet'){ + // 且该 LINK 元素的 rel 属性为 'stylesheet',说明是外部样式表 + var index = polyfilter._stylesheets.length; + + // 将该外部样式表的信息添加到 _stylesheets 数组中 + polyfilter._stylesheets.push({ + // 获取样式表的媒体查询属性,如果没有则默认为 'all' + media: stylesheets[i].media || 'all' + }); + + // 待处理的外部样式表数量加 1 + polyfilter._pending_stylesheets++; + + // 获取外部样式表的链接地址 + var href = stylesheets[i].href; + + // 如果不是开发模式,且浏览器支持 localStorage,并且 localStorage 中已经缓存了该样式表 + if(!polyfilter._development_mode && window.localStorage && window.localStorage.getItem('polyfilter_' + href)){ + // 待处理的外部样式表数量减 1 + polyfilter._pending_stylesheets--; + // 从 localStorage 中获取缓存的样式表内容,并赋值给 _stylesheets 数组中对应的元素 + polyfilter._stylesheets[index].content = localStorage.getItem('polyfilter_' + href); + // 如果所有待处理的外部样式表都已处理完毕 + if(polyfilter._pending_stylesheets === 0){ + // 调用 process 方法进行后续处理 + polyfilter.process(); + } + } + + // 无论是否有缓存,都要发起请求获取最新的样式表内容 + try{ + // 根据浏览器支持情况创建 XMLHttpRequest 对象 + if(window.XMLHttpRequest) { + var xmlHttp = new XMLHttpRequest(); + } else if(window.ActiveXObject) { + var xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); + } + // 打开一个 GET 请求,请求外部样式表 + xmlHttp.open('GET', href, true); + // 监听请求状态变化 + xmlHttp.onreadystatechange = function(){ + // 当请求完成时 + if(xmlHttp.readyState === 4){ + // 如果状态码为 0,可能是跨域问题导致请求失败 + if(xmlHttp.status === 0){ + if(window.console) console.log('Could not fetch external CSS via HTTP-Request ' + href + '. Probably because of cross origin.'); + // 如果 _stylesheets 数组中对应的元素还没有内容 + if(!polyfilter._stylesheets[index].content){ + // 待处理的外部样式表数量减 1 + polyfilter._pending_stylesheets--; + // 将响应内容赋值给 _stylesheets 数组中对应的元素 + polyfilter._stylesheets[index].content = xmlHttp.responseText; + // 如果所有待处理的外部样式表都已处理完毕 + if(polyfilter._pending_stylesheets === 0){ + // 调用 process 方法进行后续处理 + polyfilter.process(); + } + } + } else { + // 如果状态码正常 + if(!polyfilter._stylesheets[index].content){ + // 待处理的外部样式表数量减 1 + polyfilter._pending_stylesheets--; + // 将响应内容赋值给 _stylesheets 数组中对应的元素 + polyfilter._stylesheets[index].content = xmlHttp.responseText; + // 如果所有待处理的外部样式表都已处理完毕 + if(polyfilter._pending_stylesheets === 0){ + // 调用 process 方法进行后续处理 + polyfilter.process(); + } + } + // 如果不是开发模式,且浏览器支持 localStorage + if(!polyfilter._development_mode && window.localStorage){ + try{ + // 将样式表内容缓存到 localStorage 中 + window.localStorage.setItem('polyfilter_' + href, polyfilter._stylesheets[index].content); + } + catch(e){ + // 如果 localStorage 空间不足,输出提示信息 + if(window.console) console.log('Local storage quota have been exceeded. Caching of stylesheet ' + href + ' is not possible'); + } + } + } + } + }; + try{ + // 发送请求 + xmlHttp.send(null); + } catch(e){ + // 如果请求发送失败,可能是使用 file:// 协议进行测试导致的 + if(window.console) console.log('Could not fetch external CSS via HTTP-Request ' + href + '. Are you maybe testing using the file://-protocol?'); + if(!polyfilter._stylesheets[index].content){ + // 待处理的外部样式表数量减 1 + polyfilter._pending_stylesheets--; + if(polyfilter._pending_stylesheets === 0){ + // 调用 process 方法进行后续处理 + polyfilter.process(); + } + } + } + } catch(e){} + } + break; + } + })(i); + } + // 如果所有待处理的外部样式表都已处理完毕 + if(this._pending_stylesheets === 0){ + // 调用 process 方法进行后续处理 + this.process(); + } + }, + + // 处理 CSS 规则声明的函数 + _processDeclarations: function(rule){ + var newstyles = ''; + // 遍历规则中的所有声明 + for(var k in rule.declarations){ + var declaration = rule.declarations[k]; + + // 如果声明的属性是 'filter' + if(declaration.property === 'filter'){ + if(document.querySelectorAll){ + // 检查浏览器是否支持 document.querySelectorAll 方法 + // 使用选择器 rule.mSelectorText 获取匹配的元素集合 + var elems = document.querySelectorAll(rule.mSelectorText); + for(var k = 0; k < elems.length; k++){ + // 遍历匹配的元素集合,将当前 filter 声明的值存储到元素的 style.polyfilterStore 属性中 + // 方便后续可能的使用 + elems[k].style.polyfilterStore = declaration.valueText; + } + } + + // 获取 filter 属性声明的原始值 + var gluedvalues = declaration.valueText; + // 将原始值按 ")" 后面跟着一个或多个空白字符进行分割,得到每个单独的滤镜值 + var values = gluedvalues.split(/\)\s+/), + // 初始化一个对象,用于存储不同浏览器支持的滤镜属性值 + properties = { + filtersW3C: [], // 存储 W3C 标准的滤镜属性值 + filtersWebKit: [], // 存储 WebKit 内核浏览器(如 Safari、旧版 Chrome)的滤镜属性值 + filtersSVG: [], // 存储 SVG 滤镜属性值 + filtersIE: [], // 存储 Internet Explorer 浏览器的滤镜属性值 + behaviorsIE: [] // 存储 Internet Explorer 浏览器的行为属性值(用于模拟滤镜效果) + }; + + // 遍历分割后的每个滤镜值 + for(var idx in values){ + var value = values[idx] + ')'; // 恢复完整的滤镜值 + + // 调用 polyfilter 对象的 convert 方法,将单个滤镜值转换为不同浏览器支持的属性值 + var currentproperties = polyfilter.convert(value); + + // 遍历转换后的属性值对象 + for(var key in currentproperties){ + if(typeof properties[key] !== 'undefined'){ + // 如果当前属性在 properties 对象中存在,则将转换后的属性值追加到对应数组中 + properties[key] = properties[key].concat(currentproperties[key]); + } + } + } + + // 开始构建新的样式规则字符串,添加选择器和左花括号 + newstyles += rule.mSelectorText + '{'; + if(properties['filtersW3C'].length > 0){ + // 如果存在 W3C 标准的滤镜属性值 + var filter = + webkitFilter = + mozFilter = + oFilter = + msFilter = + properties['filtersW3C'].join(' '); // 将 W3C 标准的滤镜属性值用空格连接成一个字符串 + + if(properties['filtersWebKit'] && properties['filtersWebKit'].length > 0){ + // 如果存在 WebKit 内核浏览器的滤镜属性值,则使用这些值替换 webkitFilter + webkitFilter = properties['filtersWebKit'].join(' '); + } + + if(typeof this._ie === 'undefined'){ + // 如果不是 Internet Explorer 浏览器,则添加 -ms-filter 属性 + newstyles += '-ms-filter:' + msFilter + ';'; + } + + // 添加不同浏览器前缀的 filter 属性 + newstyles += '-webkit-filter:' + webkitFilter + ';'; + newstyles += '-moz-filter:' + mozFilter + ';'; + newstyles += '-o-filter:' + oFilter + ';'; + } + if(properties['filtersSVG'].length > 0){ + if(properties['filtersSVG'][0] != 'none'){ + // 如果 SVG 滤镜属性值不为 'none' + // 生成一个唯一的 ID,用于标识 SVG 滤镜 + var id = gluedvalues.replace(/[^a-z0-9]/g,''); + + if(typeof this._svg_cache[id] === 'undefined'){ + // 如果 SVG 滤镜缓存中不存在该 ID 对应的 SVG 元素 + // 创建一个新的 SVG 元素,并将其存储到缓存中 + this._svg_cache[id] = this._create_svg(id,properties['filtersSVG']); + + if(typeof XMLSerializer === 'undefined'){ + // 如果浏览器不支持 XMLSerializer,则直接将 SVG 元素添加到 body 中 + document.body.appendChild(this._svg_cache[id]); + } + else { + // 如果浏览器支持 XMLSerializer + var s = new XMLSerializer(); + // 将 SVG 元素序列化为字符串 + var svgString = s.serializeToString(this._svg_cache[id]); + if(svgString.search('SourceGraphic') != -1){ + // 如果 SVG 字符串中包含 'SourceGraphic',则将 SVG 元素添加到 body 中 + document.body.appendChild(this._svg_cache[id]); + } + } + } + + if(typeof XMLSerializer === 'undefined'){ + // 如果浏览器不支持 XMLSerializer,则使用 url(#id) 形式引用 SVG 滤镜 + newstyles += 'filter: url(#' + id + ')'; + } + else { + var s = new XMLSerializer(); + // 将 SVG 元素序列化为字符串 + var svgString = s.serializeToString(this._svg_cache[id]); + + if(svgString.search('SourceGraphic') != -1){ + // 如果 SVG 字符串中包含 'SourceGraphic',则使用 url(#id) 形式引用 SVG 滤镜 + newstyles += 'filter: url(#' + id + ')'; + } + else { + // 否则,使用 data URI 形式引用 SVG 滤镜 + newstyles += 'filter: url(\'data:image/svg+xml;utf8,' + svgString + '#' + id + '\')'; + } + } + } + else { + // 如果 SVG 滤镜属性值为 'none',则添加 filter: none; + newstyles += 'filter: none;'; + } + } + if(typeof this._ie !== 'undefined'){ + // 如果是 Internet Explorer 浏览器 + if(properties['filtersIE'].length > 0){ + // 如果存在 Internet Explorer 浏览器的滤镜属性值 + var filtersIE = properties['filtersIE'].join(' '); + // 添加 filter 属性 + newstyles += 'filter:' + filtersIE + ';'; + } + if(properties['behaviorsIE'].length > 0){ + // 如果存在 Internet Explorer 浏览器的行为属性值 + var behaviorsIE = properties['behaviorsIE'].join(' '); + // 添加 behavior 属性 + newstyles += 'behavior:' + behaviorsIE + ';'; + } + } + // 完成当前选择器样式规则的构建,添加右花括号和换行符 + newstyles += '}\r\n'; + } + } + // 返回新生成的样式规则字符串 + return newstyles; + }, + + // 存储 .htc 文件的绝对路径 + scriptpath: window.polyfilter_scriptpath ? window.polyfilter_scriptpath : (function(){ + // 如果没有在 window 对象中配置 polyfilter_scriptpath + // 弹出提示框,提醒用户在引用 css - filters - polyfill.js 之前配置正确的绝对路径 + alert('Please configure the polyfill\'s absolute(!) script path before referencing the css-filters-polyfill.js, like so:\r\nvar polyfilter_scriptpath = "/js/css-filters-polyfill/";'); + // 默认返回当前目录 + return './' + })(), + + // 处理样式表的主函数 + process: function(){ + // 创建一个 CSS 解析器实例 + var parser = new CSSParser(); + + // 遍历存储的所有样式表 + for(var i = 0; i < this._stylesheets.length; i++){ + // 初始化新样式字符串 + var newstyles = ''; + // 使用解析器解析当前样式表的内容 + var sheet = parser.parse(this._stylesheets[i].content, false, true); + // 如果解析结果不为空 + if(sheet !== null) for(var j in sheet.cssRules){ + var rule = sheet.cssRules[j]; + + // 根据规则类型进行不同处理 + switch(rule.type){ + default: + // 默认情况不做处理 + break; + + case 1: + // 类型 1 通常表示普通的样式规则 + // 调用 _processDeclarations 方法处理该规则,并将结果添加到新样式字符串中 + newstyles += this._processDeclarations(rule); + break; + + case 4: + // 类型 4 通常表示 @media 媒体查询规则 + // 构建媒体查询的开头部分 + newstyles += '@media ' + rule.media.join(',') + '{'; + // 遍历媒体查询内的子规则 + for(var k in rule.cssRules){ + var mediarule = rule.cssRules[k]; + // 调用 _processDeclarations 方法处理子规则,并将结果添加到新样式字符串中 + newstyles += this._processDeclarations(mediarule); + } + // 构建媒体查询的结尾部分 + newstyles += '}'; + break; + } + } + // 创建一个新的