一. 文件存储到MongoDB中,借助GridFS
1.介绍
MongoDB是一种非关系型数据库(NoSql),很好的实现了面向对象的思想(OO思想),在Mongo DB中 每一条记录都是一个Document对象。Mongo DB最大的优势在于所有的数据持久操作都无需开发人员手动编写SQL语句,直接调用方法就可以轻松的实现CRUD操作。
GridFS是MongoDB的一种存储机制,用来存储大型二进制文件,最适合于不常改变但是经常需要连续访问的大文件。
2.java代码
controller层:
/**
* 上传单份资料
*
* @param file
* @return
* @throws IOException
* @throws ServletException
*/
@ApiOperation(value = "上传单份资料", notes = "上传单份资料", produces = "application/json")
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public ReturnInfo upload(@RequestParam("subjectId") Integer subjectId, @RequestParam("file") MultipartFile file) throws IOException, ServletException {
try {
String fName = file.getOriginalFilename(); // 获得提交的文件名
InputStream ins = file.getInputStream(); // 获取文件输入流
String contentType = file.getContentType(); // 获取文件类型
Long fileSize = file.getSize(); // 获取文件大小
String fSize="";
if(fileSize<1024*1024){
fSize=String.valueOf(Math.ceil(fileSize/1024))+"K";
}else{
fSize=String.valueOf(Math.ceil(fileSize/(1024*1024)))+"M";
}
String[] arr = fName.split("\\.");
int len=arr.length;
String fileName ="";
for(int i=0;i<len-1;i++){
if(i==len-2){
fileName=fileName+arr[i];
}
else{
fileName=fileName+arr[i]+".";
}
} //最后一个.之前的为文件名称,
String fileSuffix = arr[len-1]; //最后一个.之后的为文件类型 例如file-v1.0.1.pdf这种情况
ObjectId objectId = gridFsTemplate.store(ins, fName, contentType); // 将文件存储到mongodb中
logger.info("保存成功,objectId:" + objectId + "fileName:" + fileName + "fileSuffix:" + fileSuffix + "fileSize:"
+ fSize);
materialService.addMaterial(subjectId, fileName, fileSuffix, fSize,
objectId.toString());
return new ReturnInfo("1", "上传资料成功", null, 0);
} catch (Exception e) {
logger.error("上传资料异常", e);
return new ReturnInfo("0", "上传资料失败", null, 0);
}
}
/**
* 下载资料
*
* @param mId
* @param request
* @param response
* @throws IOException
*/
@ApiOperation(value = "下载资料", notes = "下载资料", produces = "application/json")
@ApiImplicitParams(value = {
@ApiImplicitParam(name = "mId", value = "资料id", required = true, paramType = "path", dataType = "int") })
@RequestMapping(value = "/download/{mId}", method = RequestMethod.GET)
public ReturnInfo download(@PathVariable("mId") Integer mId, HttpServletRequest request,
HttpServletResponse response) throws IOException {
try {
// 由mId获取pid
Material material = materialService.getMaterialByMId(mId);
String pId = material.getPid();
Query query = Query.query(Criteria.where("_id").is(pId));
// 查询单个文件
GridFSFile gridFSFile = gridFsTemplate.findOne(query);
if (gridFSFile == null) {
return new ReturnInfo("0", "下载文件失败", null, 0);
}
String fileName = gridFSFile.getFilename().replace(",", "");
String contentType = gridFSFile.getMetadata().get("_contentType").toString();
logger.info("fileName:" + fileName);
// 通知浏览器进行文件下载
response.setContentType(contentType);
response.setHeader("Content-Disposition",
"attachment;filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"");
GridFSBucket gridFSBucket = GridFSBuckets.create(mongoDbFactory.getDb());
GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
GridFsResource resource = new GridFsResource(gridFSFile, gridFSDownloadStream);
OutputStream outputStream = response.getOutputStream();
InputStream inputStream = resource.getInputStream();
IOUtils.copy(inputStream, outputStream);
outputStream.flush();
outputStream.close();
inputStream.close();
return new ReturnInfo("1", "下载文件成功", null, 0);
} catch (Exception e) {
logger.error("下载文件异常", e);
return new ReturnInfo("0", "下载文件失败", null, 0);
}
}
/**
* 根据资料id删除资料信息和对应mongodb中文件
*
* @param mId
* @return
*/
@ApiOperation(value = "根据资料id删除资料信息和对应文件", notes = "根据资料id删除资料信息和对应文件", produces = "application/json")
@ApiImplicitParams(value = {
@ApiImplicitParam(name = "mId", value = "资料id", required = true, paramType = "path", dataType = "int") })
@RequestMapping(value = "/delete/{mId}", method = RequestMethod.DELETE)
public ReturnInfo deleteMaterialByMId(@PathVariable("mId") Integer mId) {
try {
// 由mId获取pid
Material material = materialService.getMaterialByMId(mId);
String pId = material.getPid();
if(pId==null){
return new ReturnInfo("0", "不存在该文件", null, 0);
}
Query query = Query.query(Criteria.where("_id").is(pId));
gridFsTemplate.delete(query); //删除mongodb中文件
materialService.deleteMaterialByMId(mId); //删除数据库中资料信息
return new ReturnInfo("1", "删除资料信息和对应文件成功", null,0);
} catch (Exception e) {
logger.error("删除资料信息和对应文件异常", e);
return new ReturnInfo("0", "删除资料信息和对应文件失败", null, 0);
}
}
/**
* 修改资料名称(mongodb中也要修改)
*
* @param mId
* @param fileName
* @return
*/
@ApiOperation(value = "修改资料名称", notes = "修改资料名称", produces = "application/json")
@ApiImplicitParams(value = {
@ApiImplicitParam(name = "mId", value = "资料id", required = true, paramType = "path", dataType = "int") ,
@ApiImplicitParam(name = "fileName", value = "资料新名称", required = true, paramType = "body", dataType = "object")})
@RequestMapping(value = "/rename/{mId}", method = RequestMethod.PUT)
public ReturnInfo updateMaterialFileName(@PathVariable("mId") Integer mId, @RequestBody Object fileName) {
try {
// 由mId获取pid
Material material = materialService.getMaterialByMId(mId);
String pId = material.getPid();
if(pId==null){
return new ReturnInfo("0", "不存在该文件", null, 0);
}
Query query = Query.query(Criteria.where("_id").is(pId));
GridFSFile gridFSFile = gridFsTemplate.findOne(query);
String contentType = gridFSFile.getMetadata().get("_contentType").toString();
GridFSBucket gridFSBucket = GridFSBuckets.create(mongoDbFactory.getDb());
GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
GridFsResource resource = new GridFsResource(gridFSFile, gridFSDownloadStream);
InputStream ins= resource.getInputStream();
String fName=fileName+"."+material.getFileSuffix();
ObjectId objectId = gridFsTemplate.store(ins, fName, contentType); //mongodb保存新文件
material.setFileName(fileName.toString());
material.setPid(objectId.toString());
materialService.updateMaterialByMId(material); //更新资料信息
gridFsTemplate.delete(query); //删除mongodb旧文件
return new ReturnInfo("1", "修改资料名称成功", null,0);
} catch (Exception e) {
logger.error("修改资料名称异常", e);
return new ReturnInfo("0", "修改资料名称失败", null, 0);
}
}
service层:
public int addMaterial(int subjectId,String fileName,String fileSuffix,String fileSize,String pid) throws Exception{
Material material=new Material();
material.setSubjectId(subjectId);
material.setFileName(fileName);
material.setFileSuffix(fileSuffix);
material.setFileSize(fileSize);
material.setPid(pid);
return materialMapper.insert(material); //基本信息插入到数据库中
}
model层:
public class Material implements Serializable {
private Integer mId;
private Integer subjectId;
private String fileSize;
private String fileName;
private String fileSuffix;
private String pid; // 文件id
}
3.js代码 前端实现
上传功能使用upload组件:
import React, { PureComponent } from 'react'
import { Upload, Button, message, Modal, Form } from 'antd';
import { connect } from 'dva';
import Cookies from 'js-cookie';
const { Dragger } = Upload;
class UploadModal extends PureComponent {
constructor(props) {
super(props)
this.state = {
fileLists:undefined,
}
};
handleOk = () => {
console.log('handlesave');
const { fileLists } = this.state;
// 上传
for (const item of fileLists) {
const formData = new FormData()
formData.append('subjectId', subjectId)
formData.append('file', item)
const url = '/upload'; //与后端上传接口一致
fetch(url, {
method: 'POST',
body: formData,
}).then(response => response.json()).then((res) => {
console.log(res)
});
}
}
renderUpload=()=>{
const { fileLists } = this.state;
const props = {
name: 'file',
multiple: true,
accept: "application/msword, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-powerpoint, application/pdf, image/jpeg, image/png", // 支持上传的文件格式
headers: {
authorization: 'authorization-text',
},
onChange(info) {
const { status } = info.file;
if (status !== 'uploading') {
console.log(info.file, info.file.fileList)
}
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`);
fileLists.push(info.file.originFileObj)
} else if (status === 'removed') {
fileLists.splice(fileLists.indexOf(info.file.originFileObj), 1)
} else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
},
};
return (
<Form encType="multipart/form-data" style={{ marginTop:20}}>
<Dragger {...props}>
<Button type="primary">选取文件</Button>
<p style={{ marginTop:10}}>
支持上传的文件格式:doc, docx, xls, xlsx, ppt, pptx, pdf, jpg, png
</p>
</Dragger>
</Form>
);
};
render() {
const { visible, handleCancel } = this.props;
const { fileLists } = this.state;
console.log(fileLists);
return (
<Modal
visible={visible}
width={800}
title="上传资料"
onOk={this.handleOk}
onCancel={() => handleCancel('uploadModalVisiable')}
>
{this.renderUpload()}
</Modal>
);
}
}
export default UploadModal
下载功能实现:
<Button type='primary' onClick={() => this.handleDownloadMaterial(record)}>下载</Button>
handleDownloadMaterial =(record)=>{
const { mId } = record;
const downloadUrl = `/download/${mId}`; // 与后端接口请求地址一致
fetch(downloadUrl, {
method: 'get',
credentials: 'include',
headers: new Headers({
'Content-Type': 'application/json',
})
}).then((response) => {
response.blob().then(blob => {
const aLink = document.createElement('a');
document.body.appendChild(aLink);
aLink.style.display='none';
const objectUrl = window.URL.createObjectURL(blob);
aLink.href = objectUrl;
aLink.download = record.fileName;
aLink.click();
document.body.removeChild(aLink);
});
}).catch((error) => {
console.log('文件下载失败', error);
});
};
二. 文件存储到数据库中,存储为blob形式
1. 存储方式
数据库中为blob,model里面用byte[]存文件字段
xml中:
<result column="context" jdbcType="BLOB" property="context" typeHandler="org.apache.ibatis.type.BlobTypeHandler"/>
<insert id="insert"
insert into 表名 (id,context, ...)
values (#{id,jdbcType=BIGINT},
#{context,jdbcType=BLOB}, ...
)
</insert>
2. java代码
/**
* 上传
*
* @param ...
* @param file
* @return
* @throws Exception
*/
@ApiOperation(value = "上传", notes = "上传", produces = "application/json")
@RequestMapping(value = "/...", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
public ReturnInfo upload(@RequestParam("...") ..., @RequestParam("file") MultipartFile file) throws Exception {
String fName = file.getOriginalFilename(); // 获得提交的文件名
String[] arr = fName.split("\\.");
int len = arr.length;
String fileName = "";
for (int i = 0; i < len - 1; i++) {
if (i == len - 2) {
fileName = fileName + arr[i];
} else {
fileName = fileName + arr[i] + ".";
}
}
String fileSuffix = arr[len - 1]; // 获取文件类型
byte[] context=file.getBytes(); // 获取文件内容
logger.info("上传文件:" + "fileName:" + fileName + "fileSuxffix:" + fileSuffix);
int res = service.add(); //执行添加操作,向数据库内添加信息
if (res == 1) {
return new ReturnInfo("1", "上传成功", null, 0);
} else {
return new ReturnInfo("0", "上传失败", null, 0);
}
}
/**
* 根据ID获取内容(下载)
*
* @param id
* @return
*/
@ApiOperation(value = "根据ID获取内容(下载)", notes = "根据ID获取内容(下载)", produces = "application/json")
@ApiImplicitParams(value = {
@ApiImplicitParam(name = "id", value = "ID", required = true, paramType = "path", dataType = "Long")})
@RequestMapping(value = "/.../{id}", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
public void download(@PathVariable("id") Long id, HttpServletResponse response) {
OutputStream outputStream = null;
try {
TextStamp textStamp = service.selectById(id); // 根据id获取数据库内容
byte[] context = textStamp.getContext();
String fileName= textStamp.getStampName();
String contentType="";
if(textStamp.getStampType().equals("jpg")){
contentType="image/jpeg";
}else if(textStamp.getStampType().equals("png")){
contentType="image/png";
}
response.setContentType(contentType);
response.setHeader("Content-Disposition", "attachment;filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"");
outputStream = response.getOutputStream();
outputStream.write(context);
outputStream.flush();
} catch (Exception e) {
logger.error("error", e);
} finally {
try {
outputStream.close();
} catch (IOException e) {
logger.error("error", e);
}
}
}
/**
* 仅预览(不下载,用于页面实现图片预览)
*
* @param stampId
* @param response
*/
@ApiOperation(value = "根据Id获取内容", notes = "根据Id获取内容", produces = "application/json")
@ApiImplicitParams(value = {
@ApiImplicitParam(name = "id", value = "ID", required = true, paramType = "path", dataType = "Long")})
@RequestMapping(value = "/.../{id}", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
public void showPhoto(@PathVariable("id") Long id, HttpServletResponse response) {
OutputStream outputStream = null;
try {
byte[] context = textStampMapper.getStamp(id).getContext();
outputStream = response.getOutputStream();
outputStream.write(context);
outputStream.flush();
} catch (Exception e) {
logger.error("error", e);
} finally {
try {
outputStream.close();
} catch (IOException e) {
logger.error("error", e);
}
}
}
3.js代码,前端实现
上传组件:
import React from 'react';
import router from 'umi/router';
import { Button, Card, Form, Switch, Upload, notification, message, Icon, Modal, TreeSelect } from 'antd';
import GridContent from '@/components/PageHeaderWrapper/GridContent';
import { connect } from 'dva';
import Cookies from 'js-cookie';
const FormItem = Form.Item;
@Form.create()
class StampAdd extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
fileLists: [],
loading: false,
previewVisible: false,
previewImage: '',
}
};
back = () => {
router.goBack();
};
getBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
beforeUpload = (file) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('You can only upload JPG/PNG file!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
return isJpgOrPng && isLt2M;
}
handleSave = () => {
console.log('save');
const { fileLists } = this.state;
for (const item of fileLists) {
const formData = new FormData()
formData.append(...); // 添加文件之外的字段 @RequestParam
formData.append('file', item);
const url = '/...'; // 后端上传接口
fetch(url, {
method: 'POST',
body: formData,
}).then(response => response.json()).then((res) => {
console.log(res)
const {retCode, retValue} = res;
if(retCode && retCode === '1'){
notification.success({
message: `上传成功`,
description: retValue
})
}else {
notification.error({
message: `上传失败`,
description: retValue
})
}
});
}
}
normFile = e => {
// console.log('Upload event:', e);
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
};
handleChange = info => {
const { fileLists } = this.state;
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
fileLists.push(info.file.originFileObj)
}else if (info.file.status === 'removed') {
fileLists.splice(fileLists.indexOf(info.file.originFileObj), 1)
}
};
handleCancel = () => this.setState({ previewVisible: false });
handlePreview = async file => {
if (!file.url && !file.preview) {
// eslint-disable-next-line no-param-reassign
file.preview = await this.getBase64(file.originFileObj);
}
this.setState({
previewImage: file.url || file.preview,
previewVisible: true,
});
};
render() {
const formItemLayout = {
labelCol: {
xs: { span: 4 },
sm: { span: 4 },
},
wrapperCol: {
xs: { span: 10 },
sm: { span: 10 },
},
};
const { loading, previewVisible, previewImage} = this.state;
const uploadButton = (
<div>
{{loading} ? <Icon type="plus" /> : <Icon type="loading" />}
<div className="ant-upload-text">Upload</div>
</div>
);
return (
<GridContent>
<div>
<Card
title="新建"
bordered={false}
extra={
<div>
<Button type="primary" onClick={this.back} style={{ marginLeft: 30 }}>取消</Button>
<Button type="primary" onClick={this.handleSave} style={{ marginLeft: 30 }}>保存</Button>
</div>}
>
<Form {...formItemLayout}>
<Form.Item label="上传">
{getFieldDecorator('file', {
valuePropName: 'fileList',
getValueFromEvent: this.normFile,
rules: [{ required: true, message: "请选择文件", },],
})(
<div>
<Upload
name="file"
listType="picture-card"
className="file-uploader"
beforeUpload={this.beforeUpload}
onChange={this.handleChange}
onPreview={this.handlePreview}
>
{uploadButton}
</Upload>
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
<img alt="file" style={{ width: '100%' }} src={previewImage} />
</Modal>
</div>
)}
</Form.Item>
</Form>
</Card>
</div>
</GridContent>
);
}
}
export default StampAdd;
下载功能:
<Button onClick={()=>this.handleDownload(record)}>下载</Button>
handleDownload =(record)=>{
const downloadUrl = `/.../${record.id}`; // 接口请求地址
fetch(downloadUrl, {
method: 'get',
credentials: 'include',
headers: new Headers({
'Content-Type': 'application/json',
})
}).then((response) => {
response.blob().then(blob => {
const aLink = document.createElement('a');
document.body.appendChild(aLink);
aLink.style.display='none';
const objectUrl = window.URL.createObjectURL(blob);
aLink.href = objectUrl;
aLink.download = `${record.name}.${record.type}`;
aLink.click();
document.body.removeChild(aLink);
});
}).catch((error) => {
console.log('文件下载失败', error);
notification.success({
message: `文件下载失败`,
description: error,
})
});
};
图片预览:
handlePreview =(record)=>{
const { id } = record;
const previewUrl = `/.../${id}`; // 接口请求地址
fetch(previewUrl, {
method: 'get',
credentials: 'include',
headers: new Headers({
'Content-Type': 'application/json',
})
}).then((response) => {
response.blob().then(blob => {
const aLink = document.createElement('a');
document.body.appendChild(aLink);
aLink.style.display='none';
const objectUrl = window.URL.createObjectURL(blob);
this.setState({
previewImageUrl: objectUrl,
});
});
}).catch((error) => {
console.log('图片显示失败', error);
});
};
<img src={previewImageUrl} alt="file" style={{ width: '100%' }} />