您的位置 首页 java

学成在线第8-2天讲义-课程图片管理 分布式文件系统 上传图片开发

上传图片开发

1.1.1需求分析

在很多系统都有上传图片/上传文件的需求,比如:上传课程图片、上传课程资料、上传用户头像等,为了提供系 统的可重用性专门设立文件系统服务承担图片/文件的管理,文件系统服务实现对文件的上传、删除、查询等功能 进行管理。

各各子系统不再开发上传文件的请求,各各子系统通过文件系统服务进行文件的上传、删除等操作。文件系统服务 最终会将文件存储到fastDSF文件系统中。

下图是各各子系统与文件系统服务之间的关系:

下图是课程管理中上传图片处理流程:

执行流程如下:

1、管理员进入教学管理前端,点击上传图片

2、图片上传至文件系统服务,文件系统请求fastDFS上传文件

3、文件系统将文件入库,存储到文件系统服务数据库中。

4、文件系统服务向前端返回文件上传结果,如果成功则包括文件的Url路径。

5、课程管理前端请求课程管理进行保存课程图片信息到课程数据库。

6、课程管理服务将课程图片保存在课程数据库。

1.1.2创建文件系统服务工程

导入xc-service-base-filesystem.zip工程。

1)工程目录结构

pom .xml

<?xml version="1.0" encoding="UTF‐8"?>
<project xmlns="" xmlns:xsi="‐instance" xsi:schemaLocation="
‐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>
 

2)配置文件

原测试程序中fastdfs-client.properties的配置信息统一放在application.yml 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.64:22122
 

1.1.3API接口

1.1.3.1模型类

系统的文件信息(图片、文档等小文件的信息)在mongodb中存储,下边是文件信息的模型类。

1)模型如下:

@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。

filetag:文件标签,由于文件系统服务是公共服务,文件系统服务会为使用文件系统服务的子系统分配文件标签, 用于标识此文件来自哪个系统。

businesskey:文件系统服务为其它子系统提供的一个业务标识字段,各子系统根据自己的需求去使用,比如:课 程管理会在此字段中存储课程id用于标识该图片属于哪个课程。

metadata:文件相关的元信息。

2)collection

在mongodb创建数据库xc_fs(文件系统数据库),并创建集合 filesystem。

1.1.3.2Api接口

在api工程下创建com.xuecheng.api.filesystem包,

public interface FileSystemControllerApi {
/**
*上传文件
*@param multipartFile 文 件
*@param filetag 文件标签
*@param businesskey 业务key
*@param metedata 元信息,json格式
*@return
*/public UploadFileResult upload(MultipartFile multipartFile, String filetag,
String businesskey, String metadata);
}
 

1.1.2.3Dao

将文件信息存入数据库,主要存储文件系统中的文件路径。

public interface FileSystemRepository extends MongoRepository<FileSystem,String> {
}
 

1.1.2.4Service

@Service
public class FileSystemService {
private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemService.class); @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;
//加载fdfs的配置
private void initFdfsConfig(){ try {
ClientGlobal.initByTrackers(tracker_servers); ClientGlobal.setG_connect_timeout(connect_timeout_in_seconds); ClientGlobal.setG_network_timeout(network_timeout_in_seconds); ClientGlobal.setG_charset(charset);
} catch (Exception e) { e.printStackTrace();
//初始化文件系统出错
ExceptionCast.cast(FileSystemCode.FS_INITFDFSERROR);
}
}
//上传文件
public UploadFileResult  Upload (MultipartFile file,
String filetag, String businesskey, String metadata){
if(file == null){ ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_FILEISNULL);
}
//上传文件到fdfs
String fileId = fdfs_upload(file);
//创建文件信息对象
FileSystem fileSystem = new FileSystem();
// 文 件 id fileSystem.setFileId(fileId);
//文件在文件系统中的路径
fileSystem.setFilePath(fileId);
//业务标识fileSystem.setBusinesskey(businesskey);
//标签
fileSystem.setFiletag(filetag);
//元数据if(StringUtils.isNotEmpty(metadata)){
try {
Map map = JSON.parseObject(metadata, Map.class); fileSystem.setMetadata(map);
} catch (Exception e) { e.printStackTrace();
}
}
//名称fileSystem.setFileName(file.getOriginalFilename());
//大小
fileSystem.setFileSize(file.getSize());
//文件类型fileSystem.setFileType(file.getContentType()); fileSystemRepository.save(fileSystem);
return new UploadFileResult(CommonCode.SUCCESS,fileSystem);
}
//上传文件到fdfs,返回文件id
public String fdfs_upload(MultipartFile file) { try {
//加载fdfs的配置
initFdfsConfig();
//创建tracker client
TrackerClient trackerClient = new TrackerClient();
//获取trackerServer
TrackerServer trackerServer = trackerClient.getConnection();
//获取storage
StorageServer storeStorage = trackerClient.getStoreStorage(trackerServer);
//创建storage client
StorageClient1 storageClient1 = new StorageClient1(trackerServer,storeStorage);
//上传文件
//文件字节
byte[] bytes = file.getBytes();
//文件原始名称
String originalFilename = file.getOriginalFilename();
//文件扩展名
String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
//文件id
String file1 = storageClient1.upload_file1(bytes, extName, null);
return file1;
} catch (Exception e) { e.printStackTrace();
}
return null;
}
}
 

1.1.2.5Controller

@RestController @RequestMapping("/filesystem")
public class FileSystemController implements FileSystemControllerApi { @Autowired
FileSystemService fileSystemService;
@Override @PostMapping("/upload")
public UploadFileResult upload(@RequestParam("file") MultipartFile file,
@RequestParam(value = "filetag", required = true) String
filetag,
@RequestParam(value = "businesskey", required = false) String
businesskey,
@RequestParam(value = "metedata", required = false) String
metadata) {
return fileSystemService.upload(file,filetag,businesskey,metadata);
}
}
 

1.1.2.6测试

使用swagger-ui或postman进行测试。

下图是使用swagger-ui进行测试的界面:

1.1.3上传课程图片前端

1.1.3.1需求

上传图片界面如下图:

点击“加号”上传图片,图片上传成功自动显示;点击“删除”将删除图片。

1.1.3.2页面

使用Element-UI的Upload上传组件实现上边的效果。

  1. template
<el‐upload action="/filesystem/upload" list‐type="picture‐card"
:before‐upload="setbusinesskey"
:on‐success="handleSuccess"
:file‐list="fileList"
:limit="picmax"
:on‐exceed="rejectupload"
:data="uploadval">
<i class="el‐icon‐plus"></i>
</el‐upload>
 

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"}, imgUrl:sysConfig.imgUrl
}
},
methods: {
//超出文件上传个数提示信息rejectupload(){
this.$ message .error("最多上传"+this.picmax+"个图片");
},
//在上传前设置上传请求的数据setuploaddata(){
},
//删除图片
handleRemove(file, fileList) { console.log(file)
alert('删除')
},
//上传成功的钩子方法handleSuccess(response, file, fileList){
console.log(response) alert('上传成功')
},
//上传失败执行的钩子方法handleError(err, file, fileList){
this.$message.error('上传失败');
//清空文件队列
this.fileList = []
}
},
mounted(){
//课程id
this.courseid = this.$route.params.courseid;
}
}
</script>
 

1.1.3.3测试

1、点击“加号”测试上传图片。

3保存课程图片

1.2.1需求分析

图片上传到文件系统后,其它子系统如果想使用图片可以引用图片的地址,课程管理模块使用图片的方式是将图片 地址保存到课程数据库中。

业务流程如下:

1、上传图片到文件系统服务

2、保存图片地址到课程管理服务

在课程管理服务创建保存课程与图片对应关系的表 course_pic。

3、在course_pic保存图片成功后方可查询课程图片信息。

通过查询course_pic表数据则查询到某课程的图片信息。

1.2.2课程管理服务端开发

1.2.2.1API

课程管理需要使用图片则在课程管理服务中要提供保存课程图片的api。

@ApiOperation("添加课程图片")
public ResponseResult addCoursePic(String courseId,String pic);
 

1.2.2.2Dao

模型:

@Data @ToString @Entity
@Table(name="course_pic")
@GenericGenerator(name = "jpa‐assigned", strategy = "assigned") public class CoursePic implements Serializable {
private static final long serialVersionUID = ‐916357110051689486L;
@Id
@GeneratedValue(generator = "jpa‐assigned") private String courseid;
private String pic;
}
 

API如下:

public interface CoursePicRepository extends JpaRepository<CoursePic, String> {
}
 

1.2.3.3Service

//添加课程图片@Transactional
public ResponseResult saveCoursePic(String courseId,String pic){
//查询课程图片
Optional<CoursePic> picOptional = coursePicRepository.findById(courseId); CoursePic coursePic = null;
if(picOptional.isPresent()){ coursePic = picOptional.get();
}
//没有课程图片则新建对象if(coursePic == null){
coursePic = new CoursePic();
}
coursePic.setCourseid(courseId); coursePic.setPic(pic);
//保存课程图片
coursePicRepository.save(coursePic);
return new ResponseResult(CommonCode.SUCCESS);
}
 

1.2.3.4Controller

@Override @PostMapping("/coursepic/add")
public ResponseResult addCoursePic(@RequestParam("courseId") String courseId, @RequestParam("pic") String pic) {
//保存课程图片
return courseService.saveCoursePic(courseId,pic);
}
 

1.2.4前端开发

前端需要在上传图片成功后保存课程图片信息。

1.2.4.1Api方法

//添加课程图片
export const addCoursePic= (courseId,pic) => {
return http.requestPost(apiUrl+'/course/coursepic/add?courseId='+courseId+"&pic="+pic)
}
 

1.2.4.2页面

1)添加上传成功的钩子 :on-success=“handleSuccess”

<el‐upload action="/api/filesystem/upload" list‐type="picture‐card"
:on‐success="handleSuccess">
<i class="el‐icon‐plus"></i>
</el‐upload>
 

2)在钩子方法 中保存课程图片信息

如果保存图片失败则上传失败,清除文件列表。

//上传成功的钩子方法
handleSuccess(response, file, fileList){ console.log(response) if(response.success){
//alert('上传成功')
//图片上传成功将课程图片地址保存到课程数据库
let pic = response.fileSystem.filePath courseApi.addCoursePic(this.courseid,pic).then((res) => {
if(res.success){ this.$message.success('上传成功');
}else{
this.handleError()
}
});
}else{
this.handleError()
}
},
//上传失败执行的钩子方法handleError(err, file, fileList){
this.$message.error('上传失败');
//清空文件队列
this.fileList = []
}
 

4图片查询

1.3.1需求分析

课程图片上传成功,再次进入课程上传页面应该显示出来已上传的图片。

1.3.2API

在课程管理服务定义查询方法

@ApiOperation("获取课程基础信息")
public CoursePic findCoursePic(String courseId);
 

1.3.2课程管理服务开发

1.3.2.1Dao

使用CoursePicRepository即可,无需再开发。

1.3.2.2Service

根据课程id查询课程图片

public CoursePic findCoursepic(String courseId) { return coursePicRepository.findOne(courseId);
}
 

1.3.2.3Controller

@Override @GetMapping("/coursepic/list/{courseId}")
public CoursePic findCoursePic(@PathVariable("courseId") String courseId) { return courseService.findCoursepic(courseId);
}
 

1.3.3前端开发

1.3.3.1API方法

//查询课程图片
export const findCoursePicList = courseId => {
return http.requestQuickGet(apiUrl+'/course/coursepic/list/'+courseId)
}
 

1.3.3.2页面

在课程图片页面的mounted钩子方法 中查询课程图片信息,并将图片地址赋值给数据对象

1、定义图片查询方法

//查询图片list(){
courseApi.findCoursePicList(this.courseid).then((res) => { console.log(res)
if(res.pic){
let name = '图片';
let url = this.imgUrl+res.pic; let fileId = res.courseid;
//先清空文件列表,再将图片放入文件列表
this.fileList = [] this.fileList.push({name:name,url:url,fileId:fileId});
}
console.log(this.fileList);
});
 

mounted钩子方法

在mounted钩子方法中调用服务端查询文件列表并绑定到数据对象。

mounted(){
//课程id
this.courseid = this.$route.params.courseid;
//查询图片this.list()
}
 

1.3.3.3测试

测试流程:

1、上传图片成功

2、进入上传图片页面,观察图片是否显示

5课程图片删除

1.4.1需求分析

课程图片上传成功后,可以重新上传,方法是先删除现有图片再上传新图片。

注意:此删除只删除课程数据库的课程图片信息,不去删除文件数据库的文件信息及文件系统服务器上的文件,由 于课程图片来源于该用户的文件库,所以此图片可能存在多个地方共用的情况,所以要删除文件系统中的文件需要 到图片库由用户确认后再删除。

1.4.2API

在课程管理服务添加删除课程图片api:

@ApiOperation("删除课程图片")
public ResponseResult deleteCoursePic(String courseId);
 

1.4.3课程管理服务端开发

1.4.2.1Dao

CoursePicRepository父类提供的delete方法没有返回值,无法知道是否删除成功,这里我们在CoursePicRepository下边自定义方法:

//删除成功返回1否则返回0
long deleteByCourseid(String courseid);
 

1.4.2.2Service

//删除课程图片@Transactional
public ResponseResult deleteCoursePic(String courseId) {
//执行删除,返回1表示删除成功,返回0表示删除失败
long result = coursePicRepository.deleteByCourseid(courseId); if(result>0){
return new ResponseResult(CommonCode.SUCCESS);
}
return new ResponseResult(CommonCode.FAIL);
}
 

1.4.2.3Controller

@Override @DeleteMapping("/coursepic/delete")
public ResponseResult deleteCoursePic(@RequestParam("courseId") String courseId) { return courseService.deleteCoursePic(courseId);
}
 

1.4.3前端开发

1.4.3.1API 调用

//删除课程图片
export const deleteCoursePic= courseId => {
return http.requestDelete(apiUrl+'/course/coursepic/delete?courseId='+courseId)
}
 

1.4.3.2页面测试

1)before-remove钩子方法

在upload组件的before-remove钩子方法 中实现删除动作。

<el‐upload action="/filesystem/upload" list‐type="picture‐card"
:before‐remove="handleRemove">
<i class="el‐icon‐plus"></i>
</el‐upload>
 

before-remove说明:删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被

reject,则停止删除。

定义handleRemove方法进行测试:

handleRemove 返回true则删除页面的图片,返回false则停止删除页面的图片。

//删除图片
handleRemove(file, fileList) { console.log(file)
alert('删除成功')
return true;
}
 

1.4.3.3promise异步调用

在handleRemove方法调用删除图片的api方法,删除成功时return true,删除失败时return false;

//删除图片
handleRemove(file, fileList) { console.log(file)
//alert('删除')
//return true;
//删除图片courseApi.deleteCoursePic('1').then((res) => {
if(res.success){ this.$message.success('删除成功'); return true;
}else{
this.$message.error(res.message); return false;
}
});
},
 

在上边代码中将提交的课程id故意写错,按照我们预期应该是删除失败,而测试结果却是图片在页面上删除成功。 问题原因:

通过查询deleteCoursePic方法的底层代码,deleteCoursePic最终返回一个promise对象。

Promise是ES6提供的用于异步处理的对象,因为axios提交是异步提交,这里使用promise作为返回值。Promise的使用方法如下:

Promise对象在处理过程中有三种状态: pending:进行中

resolved:操作成功rejected: 操作失败Promise的构建方法如下:

const promise = new Promise(function(resolve,reject){
//...TODO...
if(操作成功){
resolve(value);
}else{
reject(error);
}
})
 

上边的构造方法function(resolve,reject)执行流程如下:

1)方法执行一些业务逻辑。

2)如果操作成功将Promise的状态由pending变为resolved,并将操作结果传出去 3)如果操作失败会将promise的状态由pending变为rejected,并将失败结果传出去。上边说的操作成功将操作结果传给谁了呢?操作失败将失败结果传给谁了呢?

通过promise的then、catch来指定

promise.then(function (result) { console.log('操作成功:' + result);
});
promise.catch(function (reason) { console.log('操作失败:' + reason);
});
 

例子如下:

1、定义一个方法,返回promise对象

testpromise(i){
return new Promise((resolve,reject)=>{ if(i % 2==0){
resolve('成功了')
}else{
reject('拒绝了')
}
})
}
 

2、调用此方法

向方法传入偶数、奇数进行测试。

this.testpromise(3).then(res=>{//在then中对成功结果进行处理alert(res)
}).catch(res=>{//在catch中对操作失败结果进行处理
alert(res)
})
 

3、最终将handleRemove方法修改如下

handleRemove方法返回promise对象,当删除成功则resolve,删除失败则reject。

//删除图片
handleRemove(file, fileList) { console.log(file)
return new Promise((resolve,reject)=>{
//删除图片courseApi.deleteCoursePic(this.courseid).then((res) => {
if(res.success){ this.$message.success('删除成功'); resolve()//通过
}else{
this.$message.error(res.message); reject()//拒绝
}
});
})
}
 

文章来源:智云一二三科技

文章标题:学成在线第8-2天讲义-课程图片管理 分布式文件系统 上传图片开发

文章地址:https://www.zhihuclub.com/174672.shtml

关于作者: 智云科技

热门文章

网站地图