文章目錄
一. 需求分析
1). 需求分析
在很多系統都有上傳圖片/上傳文件的需求,比如:上傳課程圖片、上傳課程資料、上傳用戶頭像等;
爲了提供系統的可重用性,專門設立文件系統服務承擔圖片/文件的管理,文件系統服務實現對文件的上傳、刪除、查詢等功能進行管理;
各個子系統不再開發上傳文件的請求,通過文件系統服務進行文件的上傳、刪除等操作;
文件系統服務最終會將文件存儲到fastDSF文件系統中。
下圖是各個子系統與文件系統服務之間的關係:
2). 圖片上傳流程
其中,課程管理服務中上傳圖片處理流程如下:
執行流程如下:
1、管理員進入教學管理前端,點擊上傳圖片
2、圖片上傳至文件系統服務,文件系統請求fastDFS上傳文件
3、文件系統將文件入庫,存儲到文件系統服務數據庫中。
4、文件系統服務向前端返回文件上傳結果,如果成功則包括文件的Url路徑。
5、課程管理前端請求課程管理服務進行保存課程圖片信息到課程數據庫。
6、課程管理服務將課程和圖片保存在課程數據庫。
二. 創建文件系統服務工程
1). 工程目錄結構
2). 項目依賴pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xc-framework-parent</artifactId>
<groupId>com.xuecheng</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../xc-framework-parent/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>xc-service-base-filesystem</artifactId>
<dependencies>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-framework-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-framework-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies>
</project>
3). 配置文件application.yml
server:
port: 22100
spring:
application:
name: xc-service-base-filesystem
#mongo配置
data:
mongodb:
database: xc_fs
uri: mongodb://root:123@127.0.0.1:27017
#SpringMVC上傳文件配置
servlet:
multipart:
#默認支持文件上傳.
enabled: true
#支持文件寫入磁盤.
file-size-threshold: 0
# 上傳文件的臨時目錄
location:
# 最大支持文件大小
max-file-size: 1MB
# 最大支持請求大小
max-request-size: 30MB
xuecheng:
fastdfs:
connect_timeout_in_seconds: 5
network_timeout_in_seconds: 30
charset: UTF-8
tracker_servers: 192.168.101.65:22122 #多個 trackerServer中間以逗號分隔
三. 後端開發
1. 模型類
1). 模型類
系統的文件信息(圖片、文檔等小文件的信息)在mongodb中存儲,下邊是文件信息的模型類
package com.xuecheng.framework.domain.filesystem;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Map;
@Data
@ToString
@Document(collection = "filesystem")
public class FileSystem {
@Id
private String fileId;
//文件請求路徑
private String filePath;
//文件大小
private long fileSize;
//文件名稱
private String fileName;
//文件類型
private String fileType;
//圖片寬度
private int fileWidth;
//圖片高度
private int fileHeight;
//用戶id,用於授權
private String userId;
//業務key
private String businesskey;
//業務標籤
private String filetag;
//文件元信息
private Map metadata;
}
說明:
fileId:fastDFS返回的文件ID。
filePath:請求fastDFS瀏覽文件URL。
businesskey:文件系統服務爲其它子系統提供的一個業務標識字段,各子系統根據自己的需求去使用,比如:課
程管理會在此字段中存儲課程id用於標識該圖片屬於哪個課程。
filetag:文件標籤,由於文件系統服務是公共服務,文件系統服務會爲使用文件系統服務的子系統分配文件標籤,
用於標識此文件來自哪個系統。
metadata:文件相關的元信息。
2). Collection
在mongodb創建數據庫xc_fs(文件系統數據庫),並創建集合 filesystem。
2. Api接口
文件位置:xcEduService01\xc-service-api\src\main\java\com\xuecheng\api\filesystem\ FileSystemControllerApi.java
package com.xuecheng.api.filesystem;
import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.multipart.MultipartFile;
/**
* Created by Administrator.
*/
@Api(value="文件管理接口",description = "文件管理接口,提供文件的增、刪、改、查")
public interface FileSystemControllerApi {
//上傳文件
@ApiOperation("上傳文件接口")
public UploadFileResult upload(MultipartFile multipartFile,
String filetag,
String businesskey,
String metadata);
}
3. Dao
文件位置:xcEduService01\xc-service-base-filesystem\src\main\java\com\xuecheng\filesystem\dao\ FileSystemRepository.java
package com.xuecheng.filesystem.dao;
import com.xuecheng.framework.domain.filesystem.FileSystem;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface FileSystemRepository extends MongoRepository<FileSystem,String> {
}
4. Service
文件位置:xcEduService01\xc-service-base-filesystem\src\main\java\com\xuecheng\filesystem\service\ FileSystemService.java
package com.xuecheng.filesystem.service;
import com.alibaba.fastjson.JSON;
import com.xuecheng.filesystem.dao.FileSystemRepository;
import com.xuecheng.framework.domain.filesystem.FileSystem;
import com.xuecheng.framework.domain.filesystem.response.FileSystemCode;
import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import org.apache.commons.lang3.StringUtils;
import org.csource.fastdfs.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
/**
* @author Administrator
* @version 1.0
**/
@Service
public class FileSystemService {
@Value("${xuecheng.fastdfs.tracker_servers}")
String tracker_servers;
@Value("${xuecheng.fastdfs.connect_timeout_in_seconds}")
int connect_timeout_in_seconds;
@Value("${xuecheng.fastdfs.network_timeout_in_seconds}")
int network_timeout_in_seconds;
@Value("${xuecheng.fastdfs.charset}")
String charset;
@Autowired
FileSystemRepository fileSystemRepository;
//上傳文件
public UploadFileResult upload( MultipartFile multipartFile,
String filetag,
String businesskey,
String metadata){
if(multipartFile ==null){
ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_FILEISNULL);
}
//第一步:將文件上傳到fastDFS中,得到一個文件id
String fileId = fdfs_upload(multipartFile);
if(StringUtils.isEmpty(fileId)){
ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_SERVERFAIL);
}
//第二步:將文件id及其它文件信息存儲到mongodb中。
FileSystem fileSystem = new FileSystem();
fileSystem.setFileId(fileId);
fileSystem.setFilePath(fileId);
fileSystem.setFiletag(filetag);
fileSystem.setBusinesskey(businesskey);
fileSystem.setFileName(multipartFile.getOriginalFilename());
fileSystem.setFileType(multipartFile.getContentType());
if(StringUtils.isNotEmpty(metadata)){
try {
Map map = JSON.parseObject(metadata, Map.class);
fileSystem.setMetadata(map);
} catch (Exception e) {
e.printStackTrace();
}
}
fileSystemRepository.save(fileSystem);
return new UploadFileResult(CommonCode.SUCCESS,fileSystem);
}
//上傳文件到fastDFS
/**
*
* @param multipartFile 文件
* @return 文件id
*/
private String fdfs_upload(MultipartFile multipartFile){
//初始化fastDFS的環境
initFdfsConfig();
//創建trackerClient
TrackerClient trackerClient = new TrackerClient();
try {
TrackerServer trackerServer = trackerClient.getConnection();
//得到storage服務器
StorageServer storeStorage = trackerClient.getStoreStorage(trackerServer);
//創建storageClient來上傳文件
StorageClient1 storageClient1 = new StorageClient1(trackerServer,storeStorage);
//上傳文件
//得到文件字節
byte[] bytes = multipartFile.getBytes();
//得到文件的原始名稱
String originalFilename = multipartFile.getOriginalFilename();
//得到文件擴展名
String ext = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
String fileId = storageClient1.upload_file1(bytes, ext, null);
return fileId;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//初始化fastDFS環境
private void initFdfsConfig(){
//初始化tracker服務地址(多個tracker中間以半角逗號分隔)
try {
ClientGlobal.initByTrackers(tracker_servers);
ClientGlobal.setG_charset(charset);
ClientGlobal.setG_network_timeout(network_timeout_in_seconds);
ClientGlobal.setG_connect_timeout(connect_timeout_in_seconds);
} catch (Exception e) {
e.printStackTrace();
//拋出異常
ExceptionCast.cast(FileSystemCode.FS_INITFDFSERROR);
}
}
}
5. Controller
文件位置:xcEduService01\xc-service-base-filesystem\src\main\java\com\xuecheng\filesystem\controller\ FileSystemController.java
package com.xuecheng.filesystem.controller;
import com.xuecheng.api.filesystem.FileSystemControllerApi;
import com.xuecheng.filesystem.service.FileSystemService;
import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* @author Administrator
* @version 1.0
**/
@RestController
@RequestMapping("/filesystem")
public class FileSystemController implements FileSystemControllerApi {
@Autowired
FileSystemService fileSystemService;
@Override
@PostMapping("/upload")
public UploadFileResult upload(MultipartFile multipartFile, String filetag, String businesskey, String metadata) {
return fileSystemService.upload(multipartFile, filetag, businesskey, metadata);
}
}
6. 測試
使用swagger-ui或postman進行測試。下圖是使用swagger-ui進行測試的界面:
四. 前端開發
1. 需求
上傳圖片界面如下圖,點擊“加號”上傳圖片,圖片上傳成功自動顯示;點擊“刪除”將刪除圖片。
2. 頁面
使用Element-UI的Upload上傳組件實現上邊的效果。
文件位置:xc-ui-pc-teach\src\module\course\page\course_manage\course_picture.vue
1). Template
<template>
<div>
<el-upload
action="/api/filesystem/upload"
list-type="picture-card"
:before-upload="setuploaddata"
:on-success="handleSuccess"
:file-list="fileList"
:limit="picmax"
:on-exceed="rejectupload"
:before-remove="handleRemove"
:data="uploadval"
name="multipartFile">
<i class="el-icon-plus"></i>
</el-upload>
</div>
</template>
el-upload參數說明:
action:必選參數,上傳的地址
list-type:文件列表的類型(text/picture/picture-card)
before-upload:上傳前執行鉤子方法 ,function(file)
on-success:上傳成功 執行的鉤子方法 ,function(response, file, fileList)
on-error:上傳失敗的鉤子方法,function(err, file, fileList)
on-remove:文件刪除的鉤子方法,function(file, fileList)
file-list:文件列表,此列表爲上傳成功 的文件
limit:最大允許上傳個數
on-exceed:文件超出個數限制時的鉤子,方法爲:function(files, fileList)
data:提交上傳的額外參數,需要封裝爲json對象,最終提交給服務端爲key/value串
2). 數據模型
<script>
import * as sysConfig from '@/../config/sysConfig';
import * as courseApi from '../../api/course';
import utilApi from '../../../../common/utils';
import * as systemApi from '../../../../base/api/system';
export default {
data() {
return {
picmax:1,//最大上傳文件的數量
courseid:'',
dialogImageUrl: '',
dialogVisible: false,
fileList:[],
uploadval:{filetag:"course",businesskey:"testbusinesskey"},//上傳提交的額外的數據 ,將uploadval轉成key/value提交給服務器
imgUrl:sysConfig.imgUrl
}
},
methods: {
//超出文件上傳個數提示信息
rejectupload(){
this.$message.error("最多上傳"+this.picmax+"個圖片");
},
//在上傳前設置上傳請求的數據
setuploaddata(){
},
//刪除圖片
handleRemove(file, fileList) {
console.log(file)
return new Promise((resolve,reject)=>{
courseApi.deleteCoursePic(this.courseid).then(res=>{
if(res.success){
//成功
resolve();
}else{
this.$message.error("刪除失敗");
//失敗
reject();
}
})
})
//上傳成功的鉤子方法
handleSuccess(response, file, fileList){
console.log(response)
// alert('上傳成功')
//調用課程管理的保存圖片接口,將圖片信息保存到課程管理數據庫course_pic中
//從response得到新的圖片文件的地址
if(response.success){
let fileId = response.fileSystem.fileId;
courseApi.addCoursePic(this.courseid,fileId).then(res=>{
if(res.success){
this.$message.success("上傳圖片成功")
}else{
this.$message.error(res.message)
}
})
}
},
//上傳失敗執行的鉤子方法
handleError(err, file, fileList){
this.$message.error('上傳失敗');
//清空文件隊列
this.fileList = []
},
//promise 有三種狀態:
//進行中pending
//執行成功 resolve
//執行失敗 reject
testPromise(i){
return new Promise((resolve,reject)=>{
if(i<2){
//成功了
resolve('成功了');
}else{
//失敗了
reject('失敗了');
}
})
}
},
mounted(){
//課程id
this.courseid = this.$route.params.courseid;
//查詢課程
courseApi.findCoursePicList(this.courseid).then(res=>{
if(res && res.pic){
let imgUrl = this.imgUrl+res.pic;
//將圖片地址設置到
this.fileList.push({name:'pic',url:imgUrl,fileId:res.pic})
}
}).catch(res=>{
})
</script>