今日任務
文章目錄
什麼是分佈式文件系統
在集羣環境下面,圖片要使用分佈式文件系統統一管理 使文件傳到統一的一個服務區
主要解決的問題
單點故障
不支持高併發
海量數據
解決方案
方案1:租用別人已經搭建好了的.
阿里雲對象存儲(收費),七牛雲(10G內免費)
好處:方便,小量數據可以
壞處:大量數據時,要花很多錢.
方案2:自己搭建-採納
hdfs(hadoop),FastDfs(國產,小文件)…
其中hadoop的 hdfs的工作原理是:
說明: 比如說有500個節點 會按照特點的大小進行一個拆分 然後複製成多份 然後分別存儲到DataNode 這也及時一個DataNode掛掉了 其他的上面也會有該數據 然後因爲是DataNode集羣 所以也能解決高併發的問題 同時也能存儲較多的一個數據 這樣也保證了暴露在外的都是用一個地址
fastdfs的交互流程圖
說明:
當我們選擇圖片後 就會調用上傳服務進行上傳 然後通過我們的第三方的公共服務模塊將圖片上傳到fastdfs並放回一個標識 同時進行一個回顯(獲取logo返回的一個標識 再拼接上地址) 然後點入駐的時把fastdfs的表示存放到數據庫
實施步驟
- 準備一個fastdfs
- 在公共服務編寫分佈式文件系統
- 完成前後端交互 完成上傳 填充值 提交到數據庫 獲取標識 回顯圖片
Fastdfs
Fastdfs是什麼
FastDFS 是用 c 語言編寫的一款開源的分佈式文件系統。FastDFS 爲互聯網量身定製,充分考慮了冗餘備份、負載均衡、線性擴容等機制,並注重高可用、高性能等指標,使用 FastDFS很容易搭建一套高性能的文件服務器集羣提供文件上傳、下載等服務
FastDFS 架構包括 Tracker server 和 Storage 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
完成!!!
好了 今日的分享就到這 以後我會陸陸續續的介紹更多的東西 謝謝大家的觀看