基于Servlet的学生管理系统
一、緣起
四個月前,我曾經(jīng)寫過一個基于JSP的學(xué)生管理系統(tǒng)《基于JSP的學(xué)生管理系統(tǒng)》,由于要交作業(yè)的關(guān)系 ,這一次我又帶來了一個基于Servlet的學(xué)生管理系統(tǒng),在原有的基礎(chǔ)上新增了其他功能,后端使用了經(jīng)典MVC分層架構(gòu),前端使用了一些比較新穎的框架,介系你沒有van過的船新版本 。
二、使用到的技術(shù)
1.前端
- HTML5
- CSS3
- JQuery 3.2.1
- 組件庫:BootStrap 4.1.0
- 表單校驗:nice-validator 1.1.4
- Web應(yīng)用框架:VUE
- 異步請求:AJAX
2.后端
- Servlet
3.使用到的工具包
- 連接數(shù)據(jù)庫:mysql-connector-java 5.1.6
- 單元測試:junit 4.12
- MD5加密:commons-codec 1.10
- bean工具:commons-beanutils 1.9.3
- Google的JSON生成器:gson 2.3.1
4.數(shù)據(jù)庫
- MySQL 5.7.10
4.本人電腦環(huán)境
- 操作系統(tǒng):Windows10
- Java版本:1.8.0_172
- TomCat:8.5
- IDE:MyEclipse 2017 CI
- 瀏覽器:Google Chrome 69 和Firefox
三、項目相關(guān)
1.項目下載
項目已在GitHub上托管開源,請遵守MIT協(xié)議進行使用。我的GitHub
2.項目安裝
詳情請查看我項目GitHub下的ReadMe
3.在線體驗
本項目已部署在我的服務(wù)器上,以供各位同學(xué)進行體驗,若在體驗過程發(fā)現(xiàn)有BUG,請務(wù)必在GitHub發(fā)出Issuse或者在下面評論留言,感謝您的反饋。在線體驗
由于超級管理員能夠刪除其他管理員賬號,故這里只能給出普通管理員賬號供大家使用,超級管理員和普通管理員的差別是普通管理員少了一個管理員管理模塊。
- 賬號:李白
- 密碼:test1234
請各位同學(xué)務(wù)必保持數(shù)據(jù)庫整潔,在下線前務(wù)必刪除體驗新增的內(nèi)容,以給接下來體驗的人有個良好體驗。
也由于我的服務(wù)器只是一個性能一般的學(xué)生服務(wù)器,沒有啥防護能力,求各位黑客大大放小弟服務(wù)器一條生路,如果服務(wù)器出現(xiàn)異常,在線體驗功能只能夠下線了。
四、管理頁面預(yù)覽
1.登錄頁面
2.主頁面
3.學(xué)生添加功能
4.專業(yè)管理
5.學(xué)院管理
五、項目實現(xiàn)功能亮點
六、項目結(jié)構(gòu)
1.前端
(1)登錄頁面布局
(2)主頁面
(3)內(nèi)容管理
2.后端
(1)后端接口
3.數(shù)據(jù)庫E-R圖
七、關(guān)鍵功能實現(xiàn)
1.如何實現(xiàn)Servlet返回JSON格式的數(shù)據(jù)?
一般來說,Servlet并不像Spring那樣有返回JSON數(shù)據(jù)的能力,所以我們只能依賴第三方的工具類,這里我推薦使用Google公司開發(fā)的GSON工具。它的用法很簡單,只需要使用gson.toJson()方法即可,這個方法能夠轉(zhuǎn)換例如String字符串,List列表,Map鍵值對等,一般我們都會使用Map<String,Object>來進行存儲,如以下步驟:
我們在MajorManagementServlet.java這個servlet中
當中有這樣一段語句
response.setHeader("Content-Type", "application/json");//設(shè)置響應(yīng)頭的內(nèi)容類別為JSON,以便前端更好識別 response.setCharacterEncoding("UTF-8");//設(shè)置相應(yīng)格式為UTF-8,防止輸出中文亂碼 PrintWriter out = response.getWriter();//獲取response.getWriter(),方便最后把JSON內(nèi)容輸出完成以上內(nèi)容后,我們在使用了一個Map來填裝了數(shù)據(jù),然后通過String res = gson.toJson(map);來轉(zhuǎn)換成JSON格式,最后使用out.print(res);
Gson gson = new Gson();Map<String,Object> map = new HashMap<String, Object>();map.put("majorList", majorList);map.put("allMajorCount", listCount);map.put("prePage", prePage);map.put("nextPage", nextPage);map.put("pageNum", pageNum);map.put("page", page);String res = gson.toJson(map);out.print(res);最終獲取的效果以JSON格式輸出
2.如何連接數(shù)據(jù)庫?
總所周知,使用JDBC查詢數(shù)據(jù)庫,有一個很經(jīng)典的模板如下
try {Class.forName(driverClass);connection = DriverManager.getConnection(url, user, password);statement = connection.createStatement();rs = statement.executeQuery(sql);rowSet = new CachedRowSetImpl();rowSet.populate(rs);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}finally {try {rs.close();statement.close();connection.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}可是我們需要頻繁地查詢數(shù)據(jù)庫,如果每一次這樣寫不就會很累?俗話說得好:“不要重復(fù)造輪子”,輪子只需要造一次就夠了,所以我們應(yīng)該把它抽象出來,單獨成為一個工具類MySQLConnectionUtils。
package com.management.utils;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties;import javax.sql.RowSet; import javax.sql.rowset.CachedRowSet;import com.sun.rowset.CachedRowSetImpl;/*** 數(shù)據(jù)庫連接工具* @author CheungChingYin**/ public class MySQLConnectionUtils {// 用于連接數(shù)據(jù)庫的工具類public static CachedRowSetImpl mySQLResult(String sql) {String path = MySQLConnectionUtils.class.getClassLoader().getResource("db.properties").getPath();FileInputStream in = null;Properties properties = new Properties();try {in = new FileInputStream(path);} catch (FileNotFoundException e1) {e1.printStackTrace();}try {properties.load(in);} catch (IOException e1) {e1.printStackTrace();}String user = properties.getProperty("jdbc.user");String password = properties.getProperty("jdbc.password");String driverClass = properties.getProperty("jdbc.driverClass");String url = properties.getProperty("jdbc.jdbcUrl");Connection connection = null;Statement statement = null;ResultSet rs = null;CachedRowSetImpl rowSet = null;try {Class.forName(driverClass);connection = DriverManager.getConnection(url, user, password);statement = connection.createStatement();rs = statement.executeQuery(sql);rowSet = new CachedRowSetImpl();rowSet.populate(rs);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}finally {try {rs.close();statement.close();connection.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return rowSet;}public static Connection mySQLConnection() {String path = MySQLConnectionUtils.class.getClassLoader().getResource("db.properties").getPath();FileInputStream in = null;Properties properties = new Properties();try {in = new FileInputStream(path);} catch (FileNotFoundException e1) {e1.printStackTrace();}try {properties.load(in);} catch (IOException e1) {e1.printStackTrace();}String user = properties.getProperty("jdbc.user");String password = properties.getProperty("jdbc.password");String driverClass = properties.getProperty("jdbc.driverClass");String url = properties.getProperty("jdbc.jdbcUrl");Connection connection = null;try {Class.forName(driverClass);connection = DriverManager.getConnection(url, user, password);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}return connection;} }db.properties相關(guān)內(nèi)容
jdbc.user=root jdbc.password=123456 jdbc.driverClass=com.mysql.jdbc.Driver jdbc.jdbcUrl=jdbc\:mysql\://localhost\:3306/student?useUnicode\=true&characterEncoding\=utf-8 jdbc.initPoolSize=5 jdbc.maxPoolSize=10由于我們會很頻繁地調(diào)用這個類,所以我們就需要考慮讓它成為一個靜態(tài)方法,以便于我們每次調(diào)用的時候不需要創(chuàng)建一個對象然后再使用。使用properties文件把數(shù)據(jù)庫的一些信息,如地址、數(shù)據(jù)庫賬戶、數(shù)據(jù)庫密碼存儲,以便日后項目遷移需要修改數(shù)據(jù)庫信息。
在一開始,我的public static CachedRowSetImpl mySQLResult(String sql)方法的返回值類型是使用ResultSet的,如這樣public static ResultSet mySQLResult(String sql),然后使用了一端時間后才發(fā)現(xiàn),當發(fā)出一大堆數(shù)據(jù)庫連接的時候,由于我沒有關(guān)閉釋放連接,就是在finally沒有connection.close();,所以會造成數(shù)據(jù)庫連接過多導(dǎo)致被數(shù)據(jù)庫拒絕連接,出現(xiàn)一個叫做Can not connect to MySQL server Error: Too many connections的異常錯誤,發(fā)現(xiàn)這個問題后我立馬在方法的finally塊中加入了
然后返回一個ResultSet結(jié)果集,雖然連接問題是解決了,可是新問題又出現(xiàn)了,控制臺發(fā)出了一個異常java.sql.SQLException: Operation not allowed after ResultSet closed(結(jié)果集已關(guān)閉,無法操作。),如果我在返回結(jié)果集之前關(guān)閉了結(jié)果集,那就不能夠操作結(jié)果集了;如果我不關(guān)閉結(jié)果集,數(shù)據(jù)庫連接一直都會存在,然后重復(fù)操作幾次又會出現(xiàn)數(shù)據(jù)庫連接數(shù)過多,拒絕訪問的異常了,那改怎么辦?
最終我在這篇博文上找到了答案《java開發(fā)中如何在ResultSet結(jié)果集關(guān)閉后,還能使用數(shù)據(jù)庫數(shù)據(jù)。》,只需要返回類型使用CachedRowSetImpl即可,詳細請查閱這篇文章。
3.如何讓用戶輸入的密碼進行加密放到數(shù)據(jù)庫中?
說到數(shù)據(jù)加密,我們不得不提到Apache公司的開發(fā)的一個常用加密工具類commons-codec。
這個項目的密碼是使用MD5進行加密的(也不能說是加密,因為它不可解密,只能說是一種認證,防止后臺的的數(shù)據(jù)庫操作員能夠輕易地獲得密碼)
在加入了commons-codec的JAR包后,我把加密方法做成了一個抽象方法,以便以后使用
PasswordEncryptionUtils類
像是管理員賬號李白,密碼是test1234,可是到了數(shù)據(jù)庫后密碼變成了這樣
一串沒有意義的亂碼。
所以在注冊和登錄的時候,一定要先把密碼加密,不然最后會應(yīng)為對不上數(shù)據(jù)庫所存放的密碼導(dǎo)致密碼不正確。
4.分頁功能
這一次的分頁功能我是在后臺完成的,為此我專門做了一個分頁的工具PageUtils,其中的邏輯和我上一次的項目分頁邏輯大致一致。
package com.management.utils;import java.util.LinkedList; import java.util.List;/*** 分頁工具* @author CheungChingYin**/ public class PageUtils {/*** 上一頁邏輯* @param page* @return*/public static Integer prePageHandler(Integer page) {Integer prePage;if (page - 1 == 0) {//如果當前頁-1為0,則表示當前頁為第一頁,prePage = 1;//前一頁只能為第一頁} else {prePage = page - 1;}return prePage;}/*** 下一頁邏輯* @param page* @param listCount* @return*/public static Integer nextPageHandler(Integer page, Integer listCount) {Integer PAGESIZE = ConstantUtils.Page.PAGESIZE;Integer pages = (listCount % PAGESIZE == 0) ? (listCount / PAGESIZE) : (listCount / PAGESIZE + 1);Integer nextPage;if (page == pages) {//如果當前頁等于最大頁數(shù)nextPage = pages;//下一頁只能等于最大頁數(shù)} else {nextPage = page + 1;}return nextPage;}/*** 列出頁碼列表* @param page* @param listCount* @return*/public static List<Integer> pageHandler(Integer page,Integer listCount) {List<Integer> list = new LinkedList<Integer>();Integer PAGESIZE = ConstantUtils.Page.PAGESIZE;Integer PAGENUM = ConstantUtils.Page.PAGENUM;Integer pages = (listCount % PAGESIZE == 0) ? (listCount / PAGESIZE) : (listCount / PAGESIZE + 1);Integer minPages = (page - PAGENUM > 0) ? (page - PAGENUM) : (1);//和上一頁同理Integer maxPages = (page + PAGENUM >= pages)?(pages):(page + PAGENUM);//與下一頁同理for(int i = minPages;i<= maxPages;i++){list.add(i);//添加最小頁到最大頁之間的頁碼}return list;}/*** 總共頁數(shù)邏輯* @param listCount* @return*/public static Integer pagesHandler(Integer listCount){Integer PAGESIZE = ConstantUtils.Page.PAGESIZE;Integer pages = (listCount % PAGESIZE == 0) ? (listCount / PAGESIZE) : (listCount / PAGESIZE + 1);return pages;}}在每次請求需要分頁的時候,首先傳入一個page參數(shù)代表當前頁數(shù),還有一個就是從數(shù)據(jù)庫獲得內(nèi)容的總條數(shù),從而計算出需要分多少頁,類中的PAGESIZE和PAGENUM其實是一個固定的常量,為了解耦才把常量抽取出來。
package com.management.utils;/*** 相關(guān)常量* @author CheungChingYin**/ public interface ConstantUtils {public static class Page {public static final Integer PAGESIZE = 10;//一頁顯示多少條資料public static final Integer PAGENUM = 3;//頁碼展示數(shù)量} }在每次需要用到分頁功能的時候,都會有進行分頁判斷,以SearchStudent的片段代碼為例
Integer page = Integer.parseUnsignedInt(request.getParameter("page"));//獲取傳來的參數(shù)當前頁Integer listCount = null;if (search == null || page==null) {return;}if (search.matches("\\d+")) {studentList = new LinkedList<Student>();Student s = service.searchStudentById(search);studentList.add(s);listCount = studentList.size();} else {studentList = service.searchStudentByName(search);listCount = studentList.size();Integer pages = PageUtils.pagesHandler(listCount);if(page == pages){//由于搜索結(jié)果如果有多個時候,是需要分頁的,若當前頁等于最大頁studentList = service.searchStudentByName(search).subList((page - 1) * 10, listCount);//列表只展示到最后一條,不然會越界}else{studentList = service.searchStudentByName(search).subList((page - 1) * 10, page * 10);//列表只能存10條數(shù)據(jù),達到偽分頁效果}}Integer prePage = PageUtils.prePageHandler(page);Integer nextPage = PageUtils.nextPageHandler(page, listCount);List<Integer> pageNum = PageUtils.pageHandler(page, listCount);map.put("studentList", studentList);map.put("allStudentCount", listCount);map.put("prePage", prePage);map.put("nextPage", nextPage);map.put("pageNum", pageNum);map.put("page", page);map.put("search",search);通過JSON把上一頁、下一頁、當前頁、顯示的頁數(shù)(即超鏈接中顯示的數(shù)字頁數(shù))傳回前端,讓前端自己處理即可。
5.搜索功能如何判斷搜索的是ID還是姓名?
同樣以SearchStudent為例,通過前端傳來了一個搜索內(nèi)容search,我們只需要通過字符串自帶的一個方法,String.matches()進行正則表達式判斷,代碼段中的"\\d+"指的是判斷字符串是不是一位以上純數(shù)字,如果返回判斷是正確,則表明是學(xué)號ID搜索,只需要向service層調(diào)用searchStudentById()方法即可,由于搜索內(nèi)容除了學(xué)號ID之外就只有姓名了,所以else是能是調(diào)用service層的searchStudentByName()方法。
6.學(xué)生管理中的學(xué)院欄和專業(yè)欄如何實現(xiàn)二級聯(lián)動表單?
這個就涉及到了JavaScript了,我的想法是這樣的(以添加學(xué)生信息為例)
這樣看起來貌似步驟很多,但其實有一些步驟是連起來做的
以下代碼源自StudentManagement.js代碼片段
步驟1~3代碼
步驟4~7代碼
/** 學(xué)院按鈕改變時* 當選中學(xué)院下拉菜單中的一項時,通過Ajax加載相對應(yīng)的學(xué)院專業(yè)*/ $("#college").change(function() {var collegeId = $(this).val();$("#major option:not(:first)").remove();$.get("getMajor?collegeId=" + collegeId, function(data, status) {var major = data;var res = "";for (var i = 0; i < major.length; i++) {res += "<option value='" + major[i].id + "'>" + major[i].name + "</option>";}$("#major").append(res);}); });7.如何實現(xiàn)主頁中部分地方刷新?
像這樣實現(xiàn)網(wǎng)頁中部分位置刷新,其實只要用到JQuery中的load()方法即可,以下代碼出自Ajax_Main.js
指定一個id為contain的div對傳來的地址進行刷新即可。
8.如何按不同權(quán)限管理員展示不同的頁面
超級管理員比普通管理員登錄時,側(cè)欄會多出一個管理員管理功能,普通權(quán)限的管理員是沒有的,效果如下
張三是超級管理員,李白則是普通管理員
要實現(xiàn)這個功能,首先需要在登錄成功的時候把獲得的權(quán)限存放在session域中,在主頁Ajax_Main.jsp中使用一個隱藏的<input>把值取出
代碼出自LoginServlet.java中
代碼出自Ajax_Main.jsp
只要頁面在初始化的時候檢測這個id為permission的輸入框中的value,只要值不等于1的話,就把管理管理員這個超鏈接移除,代碼出自Ajax_Main.js
僅僅靠前端是不保險的,有些了解我的代碼,直接在頁面上修改值不就能顯示管理員管理了嗎?所以說后端我們也需要驗證權(quán)限,雙份保障雙重保險。
在Servlet映射管理員管理相應(yīng)JSP那個接口(即AdministratorManagementUIServlet)做手腳
先判斷session中有沒有admin,沒有即表示是跳過登錄頁面進行訪問的,所以直接重定向回登錄頁面。然后再判斷存在session域中的permission,只有權(quán)限(permission)為1的時候才能夠跳轉(zhuǎn)到管理員管理的頁面中。
9.如何實現(xiàn)cookie免登陸?
方法很簡單,只要在登錄的時候,檢查cookie和session是否都同時存在,如果,只要登錄成功,檢測多選框有沒有選擇(不要問我為啥不用單選框,因為多選框的展示效果比單選框好),如果選擇了則寫cookie,沒有選擇就按普通流程登錄
Created with Rapha?l 2.2.0登錄頁打開是否有對應(yīng)cookie登錄成功輸入用戶名密碼用戶名和密碼是否正確提示用戶名或密碼錯誤yesnoyesno跳轉(zhuǎn)登錄頁面servletLoginUIServlet
package com.management.web.UI;import java.io.IOException;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession;/*** 跳轉(zhuǎn)到登錄頁面* @author CheungChingYin**/ @WebServlet(description = "跳轉(zhuǎn)到登錄界面", urlPatterns = { "/Login" }) public class LoginUIServlet extends HttpServlet {private static final long serialVersionUID = 1L;protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {HttpSession session = request.getSession();Cookie[] cookies = request.getCookies();if(cookies != null){for (Cookie cookie : cookies) {if (cookie.getName().equals("JSESSIONID")) {// 判斷是否存在第一次登錄時存放的cookieif (session.getAttribute("admin") != null) {// 判斷服務(wù)器里面的存放的cookie是否存在// 直接重定向到主界面response.sendRedirect(request.getContextPath() + "/Home");return;}}}}// 如果檢測不到cookie就轉(zhuǎn)發(fā)到登錄界面request.getRequestDispatcher("/WEB-INF/jsp/Login.jsp").forward(request, response);}protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}登錄驗證servletLoginUIServlet
package com.management.web.controller.administrator;import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession;import com.management.entities.Administrator; import com.management.service.AdministratorService; import com.management.service.impl.AdministratorServiceImpl; import com.management.utils.WebUtils;/*** 管理員登錄功能* 需要傳入?yún)?shù)* request:* 登錄界面表單:* user(管理員姓名)* password(管理員密碼)* rememberMe(可選)(記住我選項)* * @author CheungChingYin**/ @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet {private static final long serialVersionUID = 1L;protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {request.setCharacterEncoding("UTF-8");Administrator admin = WebUtils.request2Bean(request, Administrator.class);//獲得表單傳來的參數(shù)AdministratorService service = new AdministratorServiceImpl();HttpSession session = request.getSession();String user = admin.getUser();String password = admin.getPassword();response.setHeader("Content-type","text/html;charset=UTF-8");boolean loginResult = service.login(user, password);//檢查用戶名、密碼能否通過登錄Integer permission = service.searchAdministratorByName(user).getPermission();if (loginResult) {//登錄通過執(zhí)行事件if (request.getParameter("remeberMe") != null && request.getParameter("remeberMe").equals("on")) {//檢查是否勾選了記住我,需要先檢查獲取是否為空,不然會報空指針異常session.setAttribute("admin", user);session.setAttribute("permission",permission);session.setMaxInactiveInterval(7 * 24 * 3600);// Session保存7天Cookie cookie = new Cookie("JSESSIONID", session.getId());cookie.setMaxAge(7 * 24 * 3600);// cookie的有效期也為7天cookie.setPath("/");response.addCookie(cookie);//設(shè)置Cookieresponse.getWriter().write("<script language='JavaScript'>alert('登錄成功');window.location.href='"+request.getContextPath()+"/Home'</script>");} else {//沒有勾選“記住我”,使用非cookie功能登錄session.setAttribute("admin", user);session.setAttribute("permission",permission);response.getWriter().write("<script language='JavaScript'>alert('登錄成功');window.location.href='"+request.getContextPath()+"/Home'</script>");}} else {//登錄失敗response.getWriter().write("<script language='JavaScript'>alert('您的用戶名或密碼有誤,請重新輸入或者注冊');window.location.href='"+request.getContextPath()+"/Login'</script>");}}protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}10.存放JSP需要注意的問題
由于在servlet中,每一個jsp頁面都應(yīng)該由相對應(yīng)的servlet進行跳轉(zhuǎn),而不是讓瀏覽器直接訪問到。所以我們必須把所有的JSP頁面都存放在WEB-INF目錄下,只有這樣才能不被外界直接訪問到JSP,而要通過相應(yīng)的Servlet進行跳轉(zhuǎn)。而其他靜態(tài)資源,如css、JavaScript、images等的資源文件由于保護等級沒有那么重要,所以可以直接把這些靜態(tài)資源放在WebRoot目錄下,以方便調(diào)用。
11.前端表單校驗如何實現(xiàn)?
在前端的表單校驗中,我使用了第三方插件nice-validator,這個插件是國人開發(fā)的,有中文文檔,只要閱讀了快速上手后就能夠自己實現(xiàn)表單校驗了。
以專業(yè)添加表單為例,代碼出自MajorManagement.js
八、在開發(fā)的時候遇到的坑
在開發(fā)前端頁面的時候,有幾個按鈕設(shè)置了點擊事件,而這些按鈕是通過JavaScript語句動態(tài)創(chuàng)建的,導(dǎo)致這些按鈕的點擊事件失效了,無論怎樣按都不能觸發(fā)點擊事件。
這個好像是因為DOM渲染后,已經(jīng)綁定好相應(yīng)的點擊事件,但是由于有一些元素如按鈕,是通過JavaScript動態(tài)新建的,所以沒有被綁定事件,導(dǎo)致無法觸發(fā)點擊事件。(由于本人對前端一知半解,所以如果有發(fā)現(xiàn)這一段話有錯的同學(xué)請務(wù)必寫評論糾正)
解決方法:只需要使用JQuery的on()方法即可,詳細使用方法可以參考這篇博文《js/jq 動態(tài)添加的元素不能觸發(fā)綁定事件解決方案》
九、開發(fā)感想
????這個作業(yè)花了我24天時間才完成,當然在當中也少不了有摸魚 的時間,估計如果在沒有課,不摸魚的情況下大概12~16天就能夠完成。這一次的代碼估摸2000行應(yīng)該是有的,開發(fā)難度還好,就是前端的問題比較多,因為自己不太熟悉前端框架,JQuery、Vue、BootStrap這些前端框架我都是在用到的時候才學(xué),在之前并沒有系統(tǒng)性地學(xué)習過,用到啥功能就學(xué)啥,不求精通,只求能用。花了兩個小時看了下Vue的文檔,基本的功能就能夠使用了,所以說不要怕學(xué)習新的東西,你越怕就越不想去學(xué),花點心思了解一下,總會有收獲的。
????這次開發(fā)總會想到各種奇奇怪怪的功能,有一些功能可能暫時實現(xiàn)不了,總想放棄,其實可以先放下這個問題,做點別的,有時候靈感一到,問題就會迎刃而解。還有,在開發(fā)之前一定要做一份計劃書,不要像我這樣一邊開發(fā)一邊想功能,有時候你寫到頂層如Web層,想實現(xiàn)一些功能,發(fā)現(xiàn)服務(wù)層沒有,服務(wù)層寫的時候又發(fā)現(xiàn)dao層沒有相應(yīng)的方法,這樣一層層地從高層向下“補救”是不值得推薦的,就算寫得不詳細,不要緊,只要有大致框架即可。
????就快畢業(yè)了,希望自己能在畢業(yè)之前能夠做一些比較像樣的項目,以至于到外面面試也不會太丟人,繼續(xù)努力,“窮且益堅 不墜青云之志”!
總結(jié)
以上是生活随笔為你收集整理的基于Servlet的学生管理系统的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ubuntu下SMPlayer播放器安装
- 下一篇: matlab显示图像频谱