項目介紹——對話式教學系統

對話式教學系統

背景

  • 該項目爲團隊合作開發完成的課程項目,是一個基於微信小程序的教學內容管理平臺。教師在網頁端編輯併發布課程內容,學生通過微信小程序進行對話式學習。對話式學習,就是學生在微信小程序端一個類似於聊天室的頁面進行學習,知識點是以聊天氣泡的形式展示的,每學習完一個知識點可以點擊next顯示下一個,該系統暫時只支持一般的陳述性知識點和簡單的單選題。當學生在學習過程中遇到單選題時,需要從A、B、C、D四個選項中選擇出正確的那個才能繼續查看後面的知識點。

職責

  • 微信小程序的開發
  • 後端開發及服務器的部署
  • 設計數據庫表結構

重點

  1. 身份認證和權限管理
    1. 使用攔截器+註解+JWT實現身份認證和權限管理:在用戶登錄/註冊時,生成一個JWT返回給用戶,用戶得到token之後,在每次HTTP請求的請求頭中帶上這個token。創建相應的註解,添加到需要身份認證和權限認證的Controller方法上。配置攔截器,在攔截器中判斷Controller方法是否有註解,如果有註解,則使用工具類判斷用戶身份是否合法以及是否有相應的訪問權限。
    2. JWT簡單介紹:JWT主要分爲3個部分header、payload、signature,也就是頭部、有效載荷以及簽名。使用的時候首先需要設置頭部,然後將用戶名、權限、過期時間等信息(這些信息都可以解碼獲得的)放到有效載荷中,最後使用服務器中保存的密鑰以及頭部指定的簽名算法對header、payload進行簽名。
    3. JWT的優點:JWT是無狀態的,不在服務器端存儲任何狀態,這樣便降低了服務器的負載。
    4. JWT的缺點:安全性,JWT的payload是使用base64編碼的,並沒有加密,因此jwt中不能存儲敏感數據,可以對JWT進行加密來解決此問題;一次性,JWT是一次性的,這帶來了兩個問題,無法廢棄以及續簽。如果想要解決JWT一次性的問題,那麼必須添加額外的機制使得服務器保存一些狀態信息,這和使用session差不多。JWT只適合那種有效期短或者只使用一次的場景,如郵件激活賬戶。
      1. 無法廢棄:如果你在payload存儲了一些信息,當信息更新時,則重新簽發一個JWT,但是由於舊的JWT還沒有過期,拿着這個JWT仍然可以登錄。爲了解決此問題,我們需要在服務器添加額外的邏輯,例如設置一個黑名單,一旦簽發了新的JWT,那麼舊的JWT就添加到黑名單(如使用redis實現)中,避免舊的JWT被再次使用。
      2. 續簽:要改變JWT的有效時間,就需要簽發新的JWT。最簡單的方式就是每次請求續簽JWT,便返回一個新的JWT,但是這種方式的性能很差。另外一種方式就是在redis中爲每個JWT設置過期時間,每次續簽便刷新JWT的過期時間。
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); // 計算過期時間
Map<String, Object> header = new HashMap<>(2); // 設置頭部信息
header.put("typ", "JWT");
header.put("alg", "HS256");
Algorithm secret= Algorithm.HMAC256(TOKEN_SECRET); // 簽名密鑰
// 將用戶名、權限、過期日期等信息放到有效載荷中
return JWT.create()
	.withHeader(header)
	.withClaim("username", username)
	.withClaim("query", true)
	.withExpiresAt(date)
	.sign(secret);
  1. 數據庫中存儲鏈表:爲表增加一個字段next,一個記錄的next字段指向其在鏈表中的下一個節點的id。以knowledge表爲例,現在共包含id、next、description三個字段。以下爲查詢鏈表、增加節點、刪除節點三個基本操作的概述(由於這些操作都涉及了多個查詢,而且前一個查詢的結果會對後一個查詢產生影響,所以實現時應該使用事務。由於MySQL默認開啓自動提交,所以將這些操作放到一個事務中之後還會提升一點性能,相當於鎖粗化)。

    1. 查詢鏈表:首先將數據庫中所有數據查詢出來,然後找到next字段爲-1(next字段爲-1表示鏈表的尾部節點)的節點並記下此節點的id,假設爲2,接着找到next字段爲2的節點並記下此節點的id,如此循環直到得到鏈表的所有節點。
    2. 插入節點:由於需要在鏈表的任意位置插入節點,所以需要客戶端把前一個節點的id傳給服務器。先把新的節點插入數據庫,記作C,並從數據庫中查詢到要插入位置的前一個節點記作A及後一個節點記作B。將A的next字段更新爲C的id,C的next字段設置成B的id,然後更新數據庫中的記錄。
    3. 刪除節點:刪除節點也需要客戶端把前一個節點的id傳給服務器,根據這個id從數據庫中查詢出前一個節點記作A,當前節點記作B,下一個節點記作C。然後從數據庫中刪除節點B對應的記錄,將A的next字段更新爲C的id。
  2. 服務器部署:租了阿里雲的服務器,然後將項目後端服務部署到了Docker容器中。如何解決服務器OOM或者物理宕機等問題?

    1. OOM:嗯~當時我們並沒有考慮這個問題,我能想到的原因有兩個,一個是程序的邏輯有問題,另一個是高併發。程序邏輯有問題導致OOM的話,可以使用一些Java內存分析工具如jconsole來排查,然後修復bug。高併發導致OOM的話,可以在啓動JVM時分配更多的內存,如果還不能解決問題的話,可能就需要創建服務器集羣,最簡單的服務器集羣是兩臺雲服務器+負載均衡器
    2. 物理宕機:因爲服務都是部署到雲服務器上的,當阿里雲檢測到雲服務器所在的物理機宕機時,會對服務進行保護性遷移,將雲服務器遷移到性能正常的宿主機上。發生宕機遷移時,服務器會被重啓,所以我需要將服務設置成開機自啓動。docker容器設置開機自啓動,使用指令 docker update --restart=always 容器名稱或者容器id
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章