Vue + Spring Boot 項目實戰(十):圖片上傳與項目的打包部署

前言

博客一直更新地很慢,多謝各位老鐵的支持與耐心等候。一開始的時候真的沒有想到做教程是這麼蛋疼的一件事,這個項目我也就利用業餘時間搞了一兩週,沒想到十篇文章竟然寫了四個月。當然,更新慢的主要原因,還是我這該死的懶惰。

這四個月裏有 80 天左右的時間處在強制加班狀態。忙完休了十幾天假,回了躺老家,各種探親訪友,體驗了一下鹹魚的生活,算是回了一波血。

這篇文章主要講講圖片的上傳。近來留言提建議的老鐵越來越多了,十分感謝大家的支持,但是畢竟做教程面面俱到很難,我的主要目的是在大家毫無頭緒的時候拋個磚,相信大家對框架有了一定認識之後,只要是想做的功能都可以實現。解決問題的過程可能會有坎坷,但事後想來一定會有滿足感。

一、圖片上傳

之前我們的封面圖片保存在網上的圖牀中,顯然有些瓜皮,現在我們來完善一下。

上傳文件的邏輯很簡單:前端向後端發送 post 請求,後端對接收到的數據進行處理(壓縮、格式轉換、重命名等),並保存到服務器中指定的位置,再把該位置對應的 URL 返回給前端即可。

1.前端部分

利用 element 提供的組件 <el-upload> 可以輕鬆搞定前端。該組件的詳細文檔地址如下:

https://element.eleme.cn/#/zh-CN/component/upload

爲了不讓原有組件的代碼量太大,我新建了一個組件,命名爲 ImgUpload.vue,對上傳組件做了一些簡單的配置,代碼如下:

<template>
  <el-upload
    class="img-upload"
    ref="upload"
    action="http://localhost:8443/api/covers"
    :on-preview="handlePreview"
    :on-remove="handleRemove"
    :before-remove="beforeRemove"
    :on-success="handleSuccess"
    multiple
    :limit="1"
    :on-exceed="handleExceed"
    :file-list="fileList">
    <el-button size="small" type="primary">點擊上傳</el-button>
    <div slot="tip" class="el-upload__tip">只能上傳jpg/png文件,且不超過500kb</div>
  </el-upload>
</template>

<script>
  export default {
    name: 'ImgUpload',
    data () {
      return {
        fileList: [],
        url: ''
      }
    },
    methods: {
      handleRemove (file, fileList) {
      },
      handlePreview (file) {
      },
      handleExceed (files, fileList) {
        this.$message.warning(`當前限制選擇 1 個文件,本次選擇了 ${files.length} 個文件,共選擇了 ${files.length + fileList.length} 個文件`)
      },
      beforeRemove (file, fileList) {
        return this.$confirm(`確定移除 ${file.name}?`)
      },
      handleSuccess (response) {
        this.url = response
        this.$emit('onUpload')
        this.$message.warning('上傳成功')
      },
      clear () {
        this.$refs.upload.clearFiles()
      }
    }
  }
</script>

可以看出,action 屬性指定了上傳操作對應的 api。各種事件鉤子可以顧名思義。我們主要用了 :before-remove:on-success:on-exceed 幾種,其它的可以根據需要自行編寫。

還有一個重要的屬性是 multiple,和 :limit 屬性配合使用可以檢測上傳文件的數量。

根據 :on-success 事件對應的方法,當該組件接收到後端返回的成功信息時,會觸發父組件 EditForm.vueonUpload 事件,把接收到的 URL 賦給圖書信息表單的 cover 字段,這個 URL 需要我們在後端根據資源存放位置生成。提交後,數據庫裏就會保存服務器上的資源對應的 URL。

EditForm.vue 對應的修改有兩處,一是在表單中封面字段的位置添加該組件,即下圖的效果:
在這裏插入圖片描述
需要把

<el-form-item label="封面" :label-width="formLabelWidth" prop="cover">
  <el-input v-model="form.cover" autocomplete="off" placeholder="圖片 URL"></el-input>
</el-form-item>

改爲

<el-form-item label="封面" :label-width="formLabelWidth" prop="cover">
  <el-input v-model="form.cover" autocomplete="off" placeholder="圖片 URL"></el-input>
  <img-upload @onUpload="uploadImg" ref="imgUpload"></img-upload>
</el-form-item>

別忘了導入該組件的語句。(import ImgUpload from './ImgUpload')要是寫的時候順序搞錯了,先寫標籤再寫導入語句,eslint 會檢測出錯誤,出現這種情況就把標籤再敲一遍就好了。

第二處修改是在 method 中添加對應的方法如下:

uploadImg () {
  this.form.cover = this.$refs.imgUpload.url
}

然後可以測試一下,當然是用不了的,畢竟後端啥也沒有嘛。

2.後端部分

後端主要解決如下兩個問題:

  • 如何接收前端傳來的圖片數據並保存
  • 如何避免重名(圖片資源的名字很可能重複,如不修改可能出現問題)

首先,在後端新建 utils 包,創建一個工具類 StringUtils 並編寫生成指定長度隨機字符串的方法:

package com.gm.wj.util;

import java.util.Random;

public class StringUtils {
    public static String getRandomString(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }
}

然後在 LibraryController 中添加 PostMapping:

@CrossOrigin
@PostMapping("api/covers")
public String coversUpload(MultipartFile file) throws Exception {
    String folder = "D:/workspace/img";
    File imageFolder = new File(folder);
    File f = new File(imageFolder, StringUtils.getRandomString(6) + file.getOriginalFilename()
            .substring(file.getOriginalFilename().length() - 4));
    if (!f.getParentFile().exists())
        f.getParentFile().mkdirs();
    try {
        file.transferTo(f);
        String imgURL = "http://localhost:8443/api/file/" + f.getName();
        return imgURL;
    } catch (IOException e) {
        e.printStackTrace();
        return "";
    }
}

這裏涉及到對文件的操作,對接收到的文件重命名,但保留原始的格式。可以進一步做一下壓縮,或者校驗前端傳來的數據是否爲指定格式,這裏不再贅述。

測試一下,成了!URL 變成了我們自己的了。
在這裏插入圖片描述
當然,現在這樣還不行,這個 URL 的前綴是我們自己構建的,還需要把它跟我們設置的圖片資源文件夾,即 D:/workspace/img 對應起來。

config\MyWebConfigurer 中添加如下代碼:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/api/file/**").addResourceLocations("file:" + "d:/workspace/img/");
    }

測試一下,改一改第一本書的封面

STEP 1
在這裏插入圖片描述
STEP 2
在這裏插入圖片描述
STEP 3
在這裏插入圖片描述
一本好好的書,瞬間變得又 low 又醜!

查看資源文件夾,圖片也成功保存了下來。

在這裏插入圖片描述

有的老鐵可能想到了, 6 位的隨機數也可能隨出完全一樣的名字,這個問題嘛,也交給你們想辦法解決嘍。

二、部署項目到服務器

這部分其實是附贈內容,一開始覺得不用說,但考慮到整個教程的完整性(主要是本篇字數不太夠),我決定簡單嘮一嘮。

我們在開發時採用了前後端分離的模式,在部署的時候有兩種選擇:

選擇一: 把前端項目部署在 web 服務器中,把後端項目部署在應用服務器

選擇二: 把前端項目打包,作爲後端項目的靜態文件,再把後端項目部署在應用服務器

一般來講,既然我們前後端分離了,那選擇一是自然而然的,前面也說過,使用 web 服務器的好處有如下幾點:

  • 可以實現反向代理,提高網站的安全性
  • 方便維護,一些小的修改不必同時協調前後端開發人員
  • 對靜態資源的加載速度更快

但考慮到成本、開發團隊技術能力等問題,選擇二也有其存在的意義。

下面具體講一下兩種選擇的操作方法。

1.選擇一

首先下載 nginx,官方網址如下:

http://nginx.org/en/download.html

我選擇了 Windows 下的最新版本 1.17.3,下載下來是一個壓縮包
下載 nginx
把它解壓到某個位置,比如我是 D 盤根目錄。

打開前端項目,執行 npm run build,等候進程完成。這時,項目的 dist 文件夾下將出現我們打包好的內容。
打包
把它拷貝進 nginx\html 下(如果該文件夾裏有內容,需要把原來的內容刪掉)

接着,配置一下服務器的默認端口,打開 nginx\conf\nginx.conf,找到 server 的配置處,把 listen 80 改爲 listen 8081,方便測試,注意後面還有配置虛擬主機的地方,是加了註釋符號的,不要找錯了
配置默認端口
最後,由於 nginx 無法直接處理 vue 的 history 模式路由(感謝熱心網友【張敬遠】、【甜蜜雲豆】、【二哥很難】反饋),通過地址欄輸入地址或刷新頁面會導致頁面無法訪問。

這是由於輸入地址或刷新操作會向服務器發出請求,但我們這個單頁面應用表面上像更改了地址,實際上還是通過 js 來控制頁面的變化,nginx 上並不存在與請求所對應的頁面,也就無法做出響應。因此,與後端的做法相同,我們需要把這個請求轉發到 index.html。

讓我們再次打開 nginx.conf 文件,添加一條 location 配置,同時將原來默認的 location 註釋掉:

#location / {
#    root   html;
#    index  index.html index.htm;
#}

location / {
     try_files $uri $uri/ /index.html;
}

最後,爲了能夠默認打開首頁,我們在前端 router\index.js 裏添加一條路由:

    {
      path: '/',
      name: 'index',
      redirect: '/index',
      component: AppIndex,
      meta: {
        requireAuth: true
      }
    },

這樣在已登錄狀態下訪問 http://localhost:8081/ 會跳轉到 /index,否則會跳轉到登錄頁面。

配置完成後,運行 nginx 根目錄下的 nginx.exe 即可,訪問 http://localhost:8081/ ,發現自動跳轉到了登錄界面。當然,這時候沒有後端的驗證,是登錄不了的。
在這裏插入圖片描述
接着,部署後端項目,流程基本類似。正常來講開發 Java Web 應用都是要配置 tomcat 的,只是由於我們使用的 Spring Boot 內置了一個 tomcat,所以省了不少功夫。更牛逼的是,把 Spring Boot 項目打成 jar 包,這個 tomcat 就被內置到了 jar 包裏,也就是說你只需要把這個 jar 包放在有 Java 環境的服務器上直接執行,就萬事大吉了。

一般來說 Web 項目我們會打包成 war,然鵝前後端分離嘛,Java 項目只是提供接口,跟傳統的 Java 服務端程序類似,打成 jar 包更加輕便。

下面說說打包的步驟。

首先打開後端項目的 pom.xml,修改 <packaging> 標籤裏的 warjar ,除此之外,還可以配置版本號、jar 包名稱等。
在這裏插入圖片描述
在該文件夾下執行 mvn install 命令(可以利用 IDEA 的終端)
在這裏插入圖片描述
等待程序執行完成,在項目的 target 文件夾下就會出現我們的 jar 包(.jar.original 是上一次打包的備份文件)
在這裏插入圖片描述
然後在控制檯中到 jar 包對應的目錄下執行 java -jar wj-1.0.0.jar (注意名稱)即可。
執行 jar 包
最後測試一下,訪問 http://localhost:8081 ,發現可以正常登錄並使用後端接口了。
在這裏插入圖片描述
第一本書的封面真醜啊。。。

2.選擇二

前端打包的方式是相同的,不同的是需要把前端項目 dist 文件夾中的兩個文件 static 和 index.html 拷貝到後端項目的 \src\main\resources\static 目錄下。
拷貝
之後,把後端項目打成 war 包。打包之前,我們需要把內置的 tomcat 排除出去,避免冗餘。

排除的方法有兩種,第一種是在 spring-boot-starter-web 的依賴裏添加一個 <exclusion>,代碼如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

第二種是在 dependencies 中添加 tomcat 相關依賴如下:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-tomcat</artifactId>
	<scope>provided</scope>
</dependency>

這個 provided 的意思是 tomcat 由外部提供,不用打包。(如果過去複製了我提供的 pom.xml,這條依賴是本來就存在的)

接下來記得把 <packaging> 處的 jar 改爲 war 執行打包命令 mvn install,然後 war 包就出現啦。
在這裏插入圖片描述
至於如何部署 war 包我就不多說了,記得把 tomcat 的默認端口修改爲 8443(根據前端的轉發端口設置)即可。

三、關於完善項目的思考

1.整體思路

作爲詳細教程的部分我想基本上是結束了,但這個項目本身是不夠完善的,主要是目前的功能難以發揮實際作用。

我的設想是,把這個項目作爲我個人的 知識庫。作用有下面幾處:

  • 把我搜集到的有用的資料、自己的心得體會之類都有效地管理起來,並能夠方便的加以利用。這個庫可以不斷地更新,不斷地擴展,這樣再過幾十年,一定是一份了不得的財富。
  • 對知識庫的內容以及操作記錄等進行自動分析,監測自身學習行爲規律,甚至可以利用一些模型分析某種知識帶來的經濟效益。利用這些分析結果,可以及時優化學習規劃與學習策略。
  • 用這個知識庫系統的歷代版本作爲未來開發技術發展的一個見證,也作爲保持我個人技術能力的一條伏線。由於種種原因,我的工作逐漸偏離了開發崗位,但我對技術的熱情不會變,希望成爲真正理解技術的人的願望不會變。

接下來我會繼續完善項目本身,並使用更規範的方式開發,爭取儘快把它做成一個可以上線的成熟產品。前一段趁活動在騰訊雲買了 3 年的服務器,剛好可以玩一玩。

這個系列的後續,我會嘗試轉變 “貼代碼 + 講解基礎概念” 這種模式。這前十篇可以看作面向 “程序員” 的文章,是對框架的一個基本認識,接下來的文章我想面向 “開發者”,從產品、項目管理等角度分享開發的經驗。

2.下一步

  • 優化搜索功能,這個我想了一下,一是目前對模糊查詢的支持程度不夠,需要優化 sql 語句,二是全站查詢時不僅僅針對圖書這一種資源,需要更強大的功能,可以嘗試使用開源的站內搜索引擎。這部分內容我會盡快補充到本篇文章裏。
  • 重建項目基礎框架,分析並剝離前後臺功能。
  • 着手開發項目第二部分——用戶角色權限管理模塊。

希望 Vue3.0 早日問世。

項目的進程,還請關注 github 倉庫:

https://github.com/Antabot/White-Jotter

查看系列文章目錄:
https://learner.blog.csdn.net/article/details/88925013

上一篇:Vue + Spring Boot 項目實戰(九):核心功能的前端實現

下一篇:Vue + Spring Boot 項目實戰(十一):用戶角色權限管理模塊設計

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章