Springcloud微服務項目——人力資源管理(HRM)Day04 圖片的處理(Fastdfs)& 課程類型樹形菜單(遞歸的應用)

今日任務
在這裏插入圖片描述

什麼是分佈式文件系統

在這裏插入圖片描述
在集羣環境下面,圖片要使用分佈式文件系統統一管理 使文件傳到統一的一個服務區

主要解決的問題

單點故障
不支持高併發
海量數據

解決方案

方案1:租用別人已經搭建好了的.
阿里雲對象存儲(收費),七牛雲(10G內免費)

 好處:方便,小量數據可以
 壞處:大量數據時,要花很多錢.

方案2:自己搭建-採納
hdfs(hadoop),FastDfs(國產,小文件)…

其中hadoop的 hdfs的工作原理是:
在這裏插入圖片描述
說明: 比如說有500個節點 會按照特點的大小進行一個拆分 然後複製成多份 然後分別存儲到DataNode 這也及時一個DataNode掛掉了 其他的上面也會有該數據 然後因爲是DataNode集羣 所以也能解決高併發的問題 同時也能存儲較多的一個數據 這樣也保證了暴露在外的都是用一個地址


fastdfs的交互流程圖

在這裏插入圖片描述
說明:
當我們選擇圖片後 就會調用上傳服務進行上傳 然後通過我們的第三方的公共服務模塊將圖片上傳到fastdfs並放回一個標識 同時進行一個回顯(獲取logo返回的一個標識 再拼接上地址) 然後點入駐的時把fastdfs的表示存放到數據庫

實施步驟

  1. 準備一個fastdfs
  2. 在公共服務編寫分佈式文件系統
  3. 完成前後端交互 完成上傳 填充值 提交到數據庫 獲取標識 回顯圖片

Fastdfs

Fastdfs是什麼

FastDFS 是用 c 語言編寫的一款開源的分佈式文件系統。FastDFS 爲互聯網量身定製,充分考慮了冗餘備份、負載均衡、線性擴容等機制,並注重高可用、高性能等指標,使用 FastDFS很容易搭建一套高性能的文件服務器集羣提供文件上傳、下載等服務

FastDFS 架構包括 Tracker serverStorage server。客戶端請求 Tracker server 進行文件上傳、下載,通過 Tracker server 調度最終由 Storage server 完成文件上傳和下載

racker server 作用是負載均衡和調度,通過 Tracker server 在文件上傳時可以根據一些策略找到 Storage server 提供文件上傳服務。可以將 tracker 稱爲追蹤服務器或調度服務器。
Storage server 作用是文件存儲,客戶端上傳的文件最終存儲在 Storage 服務器上,Storageserver 沒有實現自己的文件系統而是利用操作系統 的文件系統來管理文件。可以將storage稱爲存儲服務器
在這裏插入圖片描述

服務端的兩個角色

Tracker:管理集羣,tracker 也可以實現集羣。每個 tracker 節點地位平等。收集 Storage 集羣的狀態。
Storage:實際保存文件 Storage 分爲多個組,每個組之間保存的文件是不同的。每個組內部可以有多個成員,組成員內部保存的內容是一樣的,組成員的地位是一致的,沒有主從的概念

參考:

官方網站:https://github.com/happyfish100/
配置文檔:https://github.com/happyfish100/fastdfs/wiki/

參考資料:https://www.oschina.net/question/tag/fastdfs
Java客戶端:https://github.com/happyfish100/fastdfs-client-java

上傳和下載流程

上傳

在這裏插入圖片描述
說明 :
1: Storage定期會發送自己的狀態信息 告訴 Tracker 哪些服務器是可用的

客戶端上傳文件後存儲服務器將文件 ID 返回給客戶端,此文件 ID 用於以後訪問該文件的索引信息。文件索引信息包括:組名,虛擬磁盤路徑,數據兩級目錄,文件名。

組名:文件上傳後所在的 storage 組名稱,在文件上傳成功後有 storage 服務器返回,需要客戶端自行保存。
虛擬磁盤路徑:storage 配置的虛擬路徑,與磁盤選項 store_path*對應。如果配置了
store_path0 則是 M00,如果配置了 store_path1 則是 M01,以此類推。
數據兩級目錄:storage 服務器在每個虛擬磁盤路徑下創建的兩級目錄,用於存儲數據
文件。
文件名:與文件上傳時不同。是由存儲服務器根據特定信息生成,文件名包含:源存儲
服務器 IP 地址、文件創建時間戳、文件大小、隨機數和文件拓展名等信息。


下載

在這裏插入圖片描述


Fastdfs搭建

方案1: Linux慢慢搭建-非常複雜,耗用資源
方案2:docker搭建-非常簡單
https://blog.csdn.net/tttzzztttzzz/article/details/86709318

後面我們會專門給大家講docker ,到時候我們再來搭建fastdfs 今天我暫時給大家演示就可以了

代碼實現

新建一個第三方的公共模塊 hrm-common-parent 每個功能都可以是單獨服務,但是放到一起方便管理
創建一個子模塊 hrm-common-service-2090
導入相關的依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!-- Eureka 客戶端依賴 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!--配置中心支持-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

配置文件
bootstrap.yml

spring:
  profiles:
    active: dev
  cloud:
    config:
      name: application-common #碼雲上面配置文件的名稱
      profile: ${spring.profiles.active} #環境 java -jar -D xxx jar
      label: master #分支
      discovery:
        enabled: true #從eureka上面找配置服務
        service-id: hrm-config-server #指定服務名
      #uri: http://127.0.0.1:1299 #配置服務器 單機配置
eureka: #eureka不能放到遠程配置中
  client:
    service-url:
      defaultZone: http://localhost:1010/eureka  #告訴服務提供者要把服務註冊到哪兒 #單機環境
  instance:
    prefer-ip-address: true #顯示客戶端真實ip

還有fasfdfs 的配置
fdfs_client.conf

tracker_server=122.51.119.246:22122

application-common-dev.yml

server:
  port: 2090
spring:
  application:
    name: hrm-common

導入swagger2.java文件

@Configuration
@EnableSwagger2
public class Swagger2 {
 
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //對外暴露服務的包,以controller的方式暴露,所以就是controller的包.
                .apis(RequestHandlerSelectors.basePackage("org.leryoo.controller"))
                .paths(PathSelectors.any())
                .build();
    }


    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("公共服務api")
                .description("公共服務接口文檔說明")
                .contact(new Contact("leryoo", "", "[email protected]"))
                .version("1.0")
                .build();
    }
}

同時在網關的配置文件中配置網關
application-zuul-dev.yml
添加這兩句就可以了
在這裏插入圖片描述
注意要提交!!!
然後在DocumentationConfig.java文件中修改Swagger的資源地址
在這裏插入圖片描述
然後我們訪問http://localhost:1030/swagger-ui.html 選擇公共服務就能得到這樣的界面 這樣就代表已經成功的集成了common-service
在這裏插入圖片描述

集成fastdfs

hrm-common-srevice-2090模塊中導入相關依賴

<dependency>
    <groupId>org.leryoo</groupId>
    <artifactId>hrm-basic-util</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<!--        fastdfs包-->
<!-- https://mvnrepository.com/artifact/cn.bestwu/fastdfs-client-java -->
<dependency>
    <groupId>cn.bestwu</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27</version>
</dependency>

先導入一個工具類
FastDfsApiOpr.java

package org.leryoo.util;

import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;

public class FastDfsApiOpr {
     
    public static String CONF_FILENAME  = FastDfsApiOpr.class.getClassLoader()
            .getResource("fdfs_client.conf").getFile();


    /**
     * 上傳文件
     * @param file
     * @param extName
     * @return
     */
    public static  String upload(byte[] file,String extName) {

        try {
            ClientGlobal.init(CONF_FILENAME);

            TrackerClient tracker = new TrackerClient();
            TrackerServer trackerServer = tracker.getConnection();
            StorageServer storageServer = null;

            StorageClient storageClient = new StorageClient(trackerServer, storageServer);
            NameValuePair nvp [] = new NameValuePair[]{
                    new NameValuePair("age", "18"),
                    new NameValuePair("sex", "male")
            };
            String fileIds[] = storageClient.upload_file(file,extName,nvp);

            return  "/"+fileIds[0]+"/"+fileIds[1];

        } catch (Exception e) {
            e.printStackTrace();
            return  null;
        }
    }
    /**
     * 上傳文件
     * @param extName
     * @return
     */
    public static  String upload(String path,String extName) {
 
        try { 
            ClientGlobal.init(CONF_FILENAME);
 
            TrackerClient tracker = new TrackerClient(); 
            TrackerServer trackerServer = tracker.getConnection(); 
            StorageServer storageServer = null;
            StorageClient storageClient = new StorageClient(trackerServer, storageServer);
            String fileIds[] = storageClient.upload_file(path, extName,null);
             
            System.out.println(fileIds.length); 
            System.out.println("組名:" + fileIds[0]); 
            System.out.println("路徑: " + fileIds[1]);
            return  "/"+fileIds[0]+"/"+fileIds[1];
 
        } catch (Exception e) {
            e.printStackTrace();
            return  null;
        }
    }

    /**
     * 下載文件
     * @param groupName
     * @param fileName
     * @return
     */
    public static byte[] download(String groupName,String fileName) {
        try {
 
            ClientGlobal.init(CONF_FILENAME);
 
            TrackerClient tracker = new TrackerClient(); 
            TrackerServer trackerServer = tracker.getConnection(); 
            StorageServer storageServer = null;
 
            StorageClient storageClient = new StorageClient(trackerServer, storageServer); 
            byte[] b = storageClient.download_file(groupName, fileName);
            return  b;
        } catch (Exception e) {
            e.printStackTrace();
            return  null;
        } 
    }
     

    /**
     * 刪除文件
     * @param groupName
     * @param fileName
     */
    public static void delete(String groupName,String fileName){
        try { 
            ClientGlobal.init(CONF_FILENAME);
 
            TrackerClient tracker = new TrackerClient(); 
            TrackerServer trackerServer = tracker.getConnection(); 
            StorageServer storageServer = null;
 
            StorageClient storageClient = new StorageClient(trackerServer, 
                    storageServer); 
            int i = storageClient.delete_file(groupName,fileName);
            System.out.println( i==0 ? "刪除成功" : "刪除失敗:"+i);
        } catch (Exception e) {
            e.printStackTrace();
            throw  new RuntimeException("刪除異常,"+e.getMessage());
        } 
    }
}

新建一個FastDfsController.java文件 做文件的上傳 下載 刪除 修改(先刪除後上傳)


@RestController
@RequestMapping("/fastDfs")
public class FastDfsController {
    //crud 上傳 下載(查看) 刪除 修改(先刪除後添加)
    /**
     * 參數:上傳文件
     * 返回值:成功與否,還要返回地址
     */
    @PostMapping
    public AjaxResult upload(@RequestParam(required = true,value = "file")MultipartFile file){
        try {
            System.out.println(file.getOriginalFilename() + ":" + file.getSize());
            String originalFilename = file.getOriginalFilename();
            // xxx.jpg
            String extName = originalFilename.substring(originalFilename.lastIndexOf(".")+1);
            System.out.println(extName);
            String filePath =  FastDfsApiOpr.upload(file.getBytes(), extName);
            return AjaxResult.me().setResultObj(filePath); //把上傳後的路徑返回回去
        } catch (IOException e) {
            e.printStackTrace();
            return AjaxResult.me().setSuccess(false).setMessage("上傳失敗!"+e.getMessage());
        }
    }


    /**
     * 參數:完整路徑 /goup1/xxxxx/yyyy
     * 返回值:成功與否,還要返回地址
     *
     */
    @DeleteMapping
    public AjaxResult del(@RequestParam(required = true,value = "path") String path){
        String pathTmp = path.substring(1); // goup1/xxxxx/yyyy
        String groupName =  pathTmp.substring(0, pathTmp.indexOf("/")); //goup1
        String remotePath = pathTmp.substring(pathTmp.indexOf("/")+1);// /xxxxx/yyyy
        System.out.println(groupName);
        System.out.println(remotePath);
        FastDfsApiOpr.delete(groupName, remotePath);
        return  AjaxResult.me();
    }
}

然後重啓服務 訪問Swagger頁面
在這裏插入圖片描述
得到這樣的界面就算成功了 就下來我們用postman測試一下
在這裏插入圖片描述
注意紅框框住的位置
成功後會返回信息
在這裏插入圖片描述
我們也可以用fastdfs的地址拼接上返回的標識去訪問
在這裏插入圖片描述

我們再來試試刪除
在這裏插入圖片描述
成功!!!


集成前端

我們在Register.vue文件中logo的位置添加上傳圖片的組件
在這裏插入圖片描述
關於參數說明:

action="http://localhost:1030/services/common/fastDfs/"  上傳接口的地址
:on-preview="handlePreview" 縮略圖
:on-remove="handleRemove"  刪除成功的處理
:on-success="handleSuccess"   上傳成功的處理
file-list:  回顯文件列表
list-type="picture"  代表回顯的是圖片

需要注意的是
這裏的http://localhost:1030/services不能刪除 因爲這個地方沒有使用axios發請求

handleSuccess(response, file, fileList){
  //上傳成功後會返回這個圖片的路徑
  console.log("===========")
  console.log(response);
  console.log(file);
  console.log(fileList);
  this.tenant.logo = response.resultObj;
},
handleRemove(file, fileList) {
  var filePath =file.response.resultObj;
  this.$http.delete("/common/fastDfs/?path="+filePath).then(res=>{
      if(res.data.success){
        this.$message({
          message: '刪除成功!',
          type: 'success'
        });
        this.tenant.logo = "",
      }else{
        this.$message({
          message: '刪除失敗!',
          type: 'error'
        });
      }
    })
},

測試
上傳:
在這裏插入圖片描述
刪除
在這裏插入圖片描述
成功! 圖片功能已完成


課程類型

創建課程中心的父模塊 hrm-course-parent
創建課程中心公共類 hrm-course-common
導入相關依賴

<dependencies>
  <!--不能直接依賴starter,有自動配置,而服務消費者是不需要的。-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

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

    <dependency>
        <groupId>org.leryoo</groupId>
        <artifactId>hrm-basic-util</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <!--不能全部引入mybatis-plus,這是要做數據庫操作,這裏是不需要的,只需引入核心包解決錯誤而已-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus</artifactId>
        <version>2.2.0</version>
    </dependency>
</dependencies>

創建課程模塊 hrm-course-service-2020
導入相關的依賴

<!--所有provider公共依賴-->
<dependency>
    <groupId>org.leryoo</groupId>
    <artifactId>hrm-course-common</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!-- Eureka 客戶端依賴 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!--配置中心支持-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

<!--mybatis-plus支持-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

<!--數據庫支持-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

相關的配置文件 Swagger和Mybatisplus的分頁插件以及網關的集成 除了名字都和之前的一樣 這裏就不在囉嗦了


然後在Mybatisplus的模塊中添加一個新的配置文件
mybatisPlus-config-course.properties
在這裏插入圖片描述

然後修改一下代碼生成文件中的讀取的配置文件和數據表的名字就ok了
在這裏插入圖片描述
在這裏插入圖片描述

然後生成代碼


這裏今天我們先做一個基本的業務邏輯

樹狀菜單之 無限極獲取

課程類型一半都是有子類型和父類型的 我們要想個辦法將他一層一層的嵌套 類似樹一樣的數據

在做之前先修改一下我們的domain 添加一個兒子字段
在這裏插入圖片描述
@TableField(exist = false)
表示當前屬性不是數據庫的字段,但在項目中必須使用,這樣在新增等使用bean的時候,mybatis-plus就會忽略這個,不會報錯


好了現在我們開始說解決辦法
有兩個辦法
一種是先查找出最大的那一級 然後查找出他的子級 再將他的子級封裝到父級的字段中
在這裏插入圖片描述
這樣寫最大的一個缺陷就是 如果有很多級別 那我們寫出來的代碼就會很長 不美觀且非常麻煩

第二種方式就是遞歸思想(自己調用自己)
通過父id查詢兒子,有兒子就設置爲自己的兒子,沒有就返回
在這裏插入圖片描述
敲黑板!!! 遞歸調用一定要有結束條件 不然會一直循環下去 陷入死循環中 極大的損耗內存和CPU資源

測試:
我們直接通過瀏覽器訪問
在這裏插入圖片描述
格式化json

在這裏插入圖片描述
完成!!!

好了 今日的分享就到這 以後我會陸陸續續的介紹更多的東西 謝謝大家的觀看

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