圖床小世界(基於Java Servlet搭建的圖片伺服器)

峰迴路轉發表於2020-03-12

一、前言

1)背景需求

  1. 在我們的日常生活或者工作過程中,有時候我們需要在寫部落格或者寫文件的時候需要插入圖片,這個時候我們就需要給定一個連結就把圖片的內容獲取出來。

  2. 這個伺服器可以進行把我們的圖片進行上傳,下載,檢視等等操作,可以適當的作為一個雲儲存圖片的地方。

2)重要知識點

  1. 簡單的Web伺服器設計的能力
  2. 通過JDBC來操作MySQL資料庫
  3. 進行資料庫的設計
  4. Gson庫的使用,更好的去理解JSON
  5. 強化對HTTP協議的理解
  6. 對Servlet的理解及使用
  7. 基於Md5對圖片進行校驗
  8. 加深對Postman,Filddler,Tomcat工具的使用
  9. 軟體測試的基本理解和方法

二、專案設計

1)整體架構及設計

  • 核心就是一個HTTP伺服器,提供對圖片的增刪改查操作,同時搭配簡單的前端頁面輔助完成圖片的操作展示。
    在這裡插入圖片描述
    在這裡插入圖片描述

2)資料庫設計

資料庫的屬性:
圖片編號:imageId  --> int型非空且自增
圖片的名字:imageName   -->  varchar(50)
圖片的大小:size  -->   int型
圖片的上傳時間:uploadTime varchar(50)
圖片正文型別:contentType varchar(50)
圖片路徑:path  varchar(1024)
校驗和:md5演算法:圖片的md5校驗和   --> varchar(1024)

在這裡插入圖片描述

  • 資料庫儲存的是圖片的屬性(元資訊),而圖片正文是以檔案的形式直接存在磁碟上的,當有一個圖片上傳到伺服器上時,資料庫就記錄一個path對應到磁碟上的這個檔案

    校驗和:通過一個更短的字串,來驗證整體資料是否正確。短的字串是根據原串內容通過一定的規則來計算出來的
    這是一種常見字串 hash 演算法, 具有三個特性:
       1.不管源字串多長, 得到的最終 md5 值都是固定長度
       2.源字串稍微變化一點點內容, md5 值會變化很大(降低衝突概率)
       3.通過原字串很容易計算得到 md5 值, 但是根據 md5 推匯出原字串很難(幾乎不可能).
    

3)伺服器 API 設計(前後端互動介面設計)

3.1 Gson的使用
  • 這裡我們使用JSON來組織資料。Json 是一種常見是資料格式組織方式. 源於 JavaScript, 是一種鍵值對風格的資料格式.
  • Java 中可以使用 Gson 庫來完成 Json 的解析和構造.

在Maven 中新增 Gson 的依賴
在這裡插入圖片描述
一般的時候它會是這個樣子:
在這裡插入圖片描述

3.2 新增圖片

我們先寫一個簡單的 html 來實現上傳圖片
在這裡插入圖片描述
3.3 檢視所有圖片元資訊
在這裡插入圖片描述

3.4 檢視指定圖片元資訊

在這裡插入圖片描述

3.5 刪除圖片

在這裡插入圖片描述

3.6 檢視圖片內容

在這裡插入圖片描述

4)進行原始碼的開發

4.1 封裝資料庫操作

4.1.1 建立dao包

(1)先建立DBUtil類實現了封裝獲取資料庫連線的操作

  • 建立一個單例類DBUtail輔助建立連線,其中URL為我的雲伺服器的MySQL連結

在這裡插入圖片描述

  • 這個類主要包含3個方法:

      建立DateSource的例項:DataSource getDataSource()
      獲取連線:Connection getConnection()
      關閉連線:void close(Connection connection, PreparedStatement statement, ResultSet resultSet)
          這裡關閉的時候要注意:先建立的物件後關閉,後建立的物件先關閉
          close也不是真的銷燬連線,只是回收到池子裡了,後邊還可以用,因為DataSource內建了連線池
    

(2)建立Image類,每一個image物件對應到一個圖片物件(包含圖片的相關屬性)
(3)建立imageDao類:作為image物件的管理器,藉助這個類完成image物件的增刪改查操作
(4)使用JDBC的時候是用DataSourse訪問資料庫的,稍微要比JDBCDriver方式高效一些
(5)當寫完一組功能後用單元測試的思想進行測試,也就是將一個類或者方法作為一個單元,然後分開測試,一旦出現了問題,就能及時發現BUG。

4.1.2 瞭解加回顧
  • 回顧:受查異常與非受查異常

    出現異常之後,處理的具體措施:
    1)當前接觸的大部分都是列印呼叫棧
    2)讓程式直接直接終止:及時止損
    3)監控報警通知程式猿
    
  • 瞭解jar與war包:類似於zip這樣的壓縮包,也就是將一大堆.class檔案放到一起打包成一個檔案

  • 使用maven打包成war包放在伺服器上的Tomcat的安裝目錄裡的webapps目錄下

4.2 基於Servlet來搭建伺服器

Servlet負責處理客戶端發來的請求,生成伺服器所需要生成的響應

4.2.1  建立api包 

(1)在包下建立ImageServlet類繼承HttpServlet父類並且重寫這個父類中的一些方法,完成圖片的增刪改查

  • 這個類的 doPost 對應插入圖片, doGet 對應檢視圖片資訊, doDelete 對應刪除圖片.
  • 這裡的doGet要分成兩種情況, 一個是獲取所有圖片資訊, 一個是獲取單個圖片資訊,根據請求中是否帶有 image_id 引數來決定
  • 記得將這個類加到web.xml中,其中的類名要寫完整的帶包的名字

(2)在包下建立ImageShowServlet類繼承HttpServlet父類並且重寫這個父類中的doGet方法實現展示圖片詳細內容

4.2.2 瞭解加回顧
  • 對Servlet的理解:
    Servlet相關的程式碼執行方式和平時寫的不太一樣,平時的程式碼是從main方法執行的,而Servlet裡面沒有main方法,而是靠Tomcat來自動呼叫到Servlet的程式碼,Tomcat的工作原理和曾經寫的Http伺服器是很相似的

  • Tomcat的工作步驟:
    (1)啟動的時候要繫結埠號(一般是8080)
    (2)進入一個迴圈
    (3)在主迴圈裡面,呼叫accept獲取到當前的請求的連結
    (4)讀取客戶端發生的資料(字串)
    (5)把這個字串按照Http協議來進行解析
    (6)解析出的Http請求的方法和URL之後,找到對應的Servlet,並執行對應的doXXX方法
    (7)生成響應,回覆客戶端

  • 理解Tomcat中URL與Servlet的對映關係,步驟如下:
    (1)Tomcat根據URL查詢對映關係表(在那個web.xml檔案中),找到api.ImageServlet類
    (2)Tomcat根據get方法,決定給你api.ImageServlet類建立一個物件,並且呼叫其中的doGet方法(這個方法我們一般要進行重寫)
    (3)執行doGet方法,往resp物件中寫入一些內容
    (4)Tomcat構造resp物件,根據這個物件生成Http響應報文,再通過socket寫回給客戶端(瀏覽器或者其他)

5)前端頁面設計

5.1使用HTML模板

  • 因為當前的知識對前端不是很瞭解,所以我們採用對別人的HTML模板就行刪減,下載好一個比較好看的模板之後下載到專案的webapp目錄中。

    前置知識
    
  • HTML:網頁的骨架 --骨架

  • CSS:描述網頁上元件的樣式(位置,顏色,大小,字型,背景等等) --皮囊

  • JavaScript:描述前端頁面上的一些動作(和使用者具體互動的行為) --靈魂

  • HTML,CSS,JavaScript都可以寫到同一個HTML檔案中,也可以分開寫,瀏覽器載入這個HTML的時候,就會執行到這些程式碼

5.2基於模板進行刪減

(1)導航欄實現上傳
  • 檔案上傳和提交按鈕:在原來的input標籤下載增加一個input標籤,將type改為file,增加name=“filename”,新的input標籤type=“submit”

  • 修改form標籤屬性,新增method(請求方式)=“POST” enctype(上傳到伺服器上資料的組織型別)=“multipart/form-data” action(訪問的路徑)="/java_image_server/image"

  • 小問題:發現“上傳按鈕”和前面不一樣高,用style="height:41px"修飾它

(2)頁面主題展示圖片預覽

在這裡插入圖片描述

5.3使用Vue.js

前置知識
  • 把網頁上顯示的預覽圖片替換成我們伺服器上儲存的圖片:將img標籤中src改成伺服器存的圖片的url就可以了,需要獲取伺服器上所有的圖片的url(ImageServlet),需要通過JS先獲取到所有圖片的屬性,在分別載入每一個圖片,使用JS來完成。

  • 此處引入Vue JS的框架來幫助我們更方便的編寫程式碼:JS中變數型別都是在初始化的時候自動推導的。

  • var宣告這是一個“變數”,const宣告這是一個“常量”。

  • Vue所做的最核心的工作,就是把頁面顯示的內容和JS中的程式碼相互關聯在一起,修改JS的變數就能很方便的影響到頁面的顯示情況

  • {{author}}稱為“差值表示式”:

     如果是在標籤內部使用Vue物件中的資料,就需要使用插值表示式;
     如果是在標籤屬性中使用Vue物件中的資料就不需要用插值表示式,但是需要搭配Vue的命令
     Vue的命令:v-for:迴圈訪問一個資料
               v-bind:把資料繫結到html標籤上的
               v-on:繫結某種事件的處理函式,比如點選滑鼠,雙擊,右鍵,按下某個鍵盤,調整視窗大小...
    

Vue建立物件
在這裡插入圖片描述
示例圖片:
在這裡插入圖片描述

5.4 實現展示圖片

修改 html 程式碼, 和資料關聯
  • 使用v-bind:src 把圖片的src通過imageShow介面獲取到

  • 使用 {{image.imageName}} 表示圖片標題

在這裡插入圖片描述

從伺服器獲取資料
  • 在methods中新增獲取所有圖片的方法

  • ajax: JS中構造HTTP請求傳送給伺服器的一種實現方式.
    在這裡插入圖片描述

    解決小bug
    
  • 為了解決瀏覽器能自動適配圖片位置的問題,我們用 $("#app").resize(); 主動觸發瀏覽器 resize 事件即可.

5.5實現展示圖片

當前的上傳請求會返回一個 JSON 格式的資料. 而我們更需要的是直接能看到上傳的效果,解決如下:
  • 修改上傳介面的響應, 直接返回一個 302 響應, 重定向回主頁.
  • 修改 ImageServlet.doPost 在上傳成功程式碼最後, 加上一個重定向resp.sendRedirect(“index.html”);

5.6 完善功能

圖片下方新增刪除按鈕

在這裡插入圖片描述

實現事件處理函式
  • 瀏覽器給伺服器傳送一個DELETE /image?imageld= xxx這樣的請求就可以了. (ajax完成)

在這裡插入圖片描述

解決小bug
  1. 點選刪除按鈕之後, 會觸發預覽圖片效果:
    這是因為 JavaScript 的事件冒泡機制導致的. 一個標籤接受到的事件會依次傳給父級標籤.
    此處需要阻止 click 事件冒泡. Vue 中使用 v-on:click.stop 即可.
    在這裡插入圖片描述
    在這裡插入圖片描述

三、後期加入

1)實現基於白名單方式的防盜鏈

  1. 當我們不想讓別人在別的網站直接使用我們的圖片連結時,可以採用如下方法:

  2. 通過 HTTP 中的 refer 欄位判定是否是指定網站請求圖片.修改 ImageShowServlet.doGet 方法

  3. refer記錄了它的上一個請求頁面是哪
    在這裡插入圖片描述

2)基於 MD5 實現相同內容圖片只存一份:類似於百度網盤的 “秒傳” 功能

(1)前置知識

Md5的特點:

1.不管原串多長,得到的MD5值是固定長度.
2.原串哪怕變動一點點(一個位元組), MD5值就會變動很大.
3.計算MD5值的過程很簡單,但是通過MD5值無法推測出原字串的. (應用在密碼學)

在我們的專案裡Md5的應用:

1.如果兩個圖片內容完全一樣, 就在磁碟上只存-份檔案就可以了. 通過MD5就能判定兩個圖片內容是否是一樣的.
2.圖片檔案雖然是二進位制資料,但是本質上也是字串,針對圖片內容計算MD5. 如果兩個圖片內容相同,得到的MD5 一定是相同的.
3.反之,近似的認為MD5相同,原圖片內容一定相同. 理論上是有可能兩個圖片內容不同,
MD5相同,但是實際,上出現概率極低(MD5自身演算法設計.上弓|起的特性)
4.通過Md5計算出的字串是無法在推算出原檔案的,即這是一個不可逆的過程

(2)整體思路   

1.修改dao層的程式碼,在imageDao類裡邊增加selectByMd5方法,再根據Md5來查詢資料庫中的圖片資訊
2.修改上傳圖片的程式碼,上傳檔案時先進行判定,如果這個md5對應的檔案存在就不會上傳並提示使用者該圖片已經存在

(3)計算md5   

1.在pom.xml中引入依賴
在這裡插入圖片描述
2.修改ImageServlet.doPost方法,計算md5
在這裡插入圖片描述
3.修改ImageDao類,新增selectByMd5方法

 public static Image selectByMd5(String md5) {
        // 1. 獲取資料庫連線
        Connection connection = DBUtil.getConnection();
        // 2. 構造 SQL 語句
        String sql = "select * from image_table where md5 = ?";
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            // 3. 執行 SQL 語句
            statement = connection.prepareStatement(sql);
            statement.setString(1, md5);
            resultSet = statement.executeQuery();
            // 4. 處理結果集
            if (resultSet.next()) {
                Image image = new Image();
                image.setImageId(resultSet.getInt("imageId"));
                image.setImageName(resultSet.getString("imageName"));
                image.setSize(resultSet.getInt("size"));
                image.setUploadTime(resultSet.getString("uploadTime"));
                image.setContentType(resultSet.getString("contentType"));
                image.setPath(resultSet.getString("path"));
                image.setMd5(resultSet.getString("md5"));
                return image;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 5. 關閉連結
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }

4.根據md5決定是否寫入檔案

  • 修改ImageServlet.doPost方法,如果該md5值的檔案存在,則不會存入資料庫和磁碟
    在這裡插入圖片描述

相關文章