Net Core 使用Mongodb操作文件(上传,下载)
1.Mongodb GridFS 文件操作帮助类。
GridFS 介绍 https://baike.baidu.com/item/GridFS/6342715?fr=aladdin
DLL源码:https://gitee.com/chenjianhua1985/mongodb-client-encapsulation
DLL文件:链接:https://pan.baidu.com/s/1SpWDtaXjavalJQav89UE4A?pwd=yceu 提取码:yceu
/// <summary>
/// MongoDB 文件 操作类
/// </summary>
public abstract class MongoFileRepository : IMongoFileRepository
{
#region GridFS 介绍
// MongodB使用两个集合来存储GridFS文件,一个是fs.files,另一个是fs.chunks
// fs.files这个集合中存储的是每一个上传到数据库的文档的信息
// fs.chunks这个集合存储的是上传文件的内容。一个chunk相当于一个文档(大文件被拆分成多个有序的chunk)
// GridFS中的bucket这个概念指代的是fs.files和fs.chunks的组合
// 它的工作原理是:
// 在GridFS存储文件是将文件分块存储,文件会按照256KB的大小分割成多个块进行存储
// GridFS使用两个集合
// (collection)存储文件,一个集合是chunks, 用于存储文件的二进制数据
// 一个集合是files,用于存储文件的元数据信息(文件名称、块大小、上传时间等信息)
// 从GridFS中读取文件要对文件的各各块进行组装、合并
// GridFSBucket 用于打开下载流对象
#endregion
/// <summary>
/// 库 名
/// </summary>
public abstract string DataBaseName { get; }
/// <summary>
/// 配置类型
/// </summary>
public virtual DBConfigTypeEnum DBConfigType { get; set; } = DBConfigTypeEnum.WriteDB;
/// <summary>
/// 根据 Id 获取内部信息
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public ObjectId GetInternalId(string id)
{
if (!ObjectId.TryParse(id, out ObjectId internalId))
internalId = ObjectId.Empty;
return internalId;
}
/// <summary>
/// 根据 Id 获取文件 -- 异步
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<GridFSFileInfo> GetFileById(string id)
{
// 通过系统 Id 筛选出包含 _id 的文件数据
var filter = Builders<GridFSFileInfo>.Filter.Eq("_id", GetInternalId(id));
return await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).Find(filter).FirstOrDefaultAsync();
}
/// <summary>
/// 根据 Id 获取文件 -- 异步
/// </summary>
/// <param name="id">文件Id</param>
/// <returns></returns>
public async Task<GridFSFileInfo> GetFileById(ObjectId id)
{
var filter = Builders<GridFSFileInfo>.Filter.Eq("_id", id);
return await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).Find(filter).FirstOrDefaultAsync();
}
/// <summary>
/// 上传文件 -- 异步
/// </summary>
/// <param name="fileName">文件名称</param>
/// <param name="source">流</param>
/// <returns></returns>
public async Task<ObjectId> UploadFile(string fileName, Stream source)
{
var id = await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).UploadFromStreamAsync(fileName, source);
return id;
}
/// <summary>
/// 根据Id 下载文件流可搜索 -- 异步
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<GridFSDownloadStream<ObjectId>> DownloadFileStreamSeekable(string id)
{
var options = new GridFSDownloadOptions
{
Seekable = true
};
return await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).OpenDownloadStreamAsync(GetInternalId(id), options);
}
/// <summary>
/// 根据Id 下载文件流可搜索 -- 异步
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<GridFSDownloadStream<ObjectId>> DownloadFileStreamSeekable(ObjectId id)
{
var options = new GridFSDownloadOptions
{
Seekable = true
};
return await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).OpenDownloadStreamAsync(id, options);
}
/// <summary>
/// 根据 Id 下载文件流 -- 异步
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<GridFSDownloadStream<ObjectId>> DownloadFileStream(string id)
{
return await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).OpenDownloadStreamAsync(GetInternalId(id));
}
/// <summary>
/// 下载 Id 获取文件流 -- 异步
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<GridFSDownloadStream<ObjectId>> DownloadFileStream(ObjectId id)
{
return await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).OpenDownloadStreamAsync(id);
}
/// <summary>
/// 根据Id删除文件 -- 异步
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task DeleteFile(string id)
{
await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).DeleteAsync(GetInternalId(id));
}
/// <summary>
/// 根据Id删除文件 -- 异步
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task DeleteFile(ObjectId id)
{
await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).DeleteAsync(id);
}
/// <summary>
/// 根据Id和文件名称移除文件 -- 异步
/// </summary>
/// <param name="id"></param>
/// <param name="newFilename"></param>
/// <returns></returns>
public async Task RenameFile(string id, string newFilename)
{
await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).RenameAsync(GetInternalId(id), newFilename);
}
/// <summary>
/// 根据Id和文件名称移除文件 --异步
/// </summary>
/// <param name="id"></param>
/// <param name="newFilename"></param>
/// <returns></returns>
public async Task RenameFile(ObjectId id, string newFilename)
{
await MongoDBClient.GetGridFSBucketInstance(DataBaseName, DBConfigType).RenameAsync(id, newFilename);
}
}
在项目的使用实例
本人比较喜欢封装好再用。所以将上面的帮助类封装成了一个DLL文件,这样在项目中可以直接引用。
项目结构
API层有一个mongoDBConfig.json这个是客户端连接配置文件。
[
{
"DbName": "PMFiles",
"ReadConnectionString": "mongodb://192.168.10.200:27017",
"WriteConnectionString": "mongodb://192.168.10.200:27017"
},
{
"DbName": "Test",
"ReadConnectionString": "mongodb://192.168.10.200:27017",
"WriteConnectionString": "mongodb://192.168.10.200:27017"
}
]
使用很简单,在要引用的类库中直接引用DLL文件就可以。
下面是具体的实例代码:
1.数据访问层
/// <summary>
/// MongodbFile处理接口
/// </summary>
public interface IFileRepository : IMongoFileRepository
{
}
这里创建的接口是空的,没有要扩展的方法,常用的接口都在 IMongoFileRepository 基类接口中定义了.
实现类:
/// <summary>
/// Mongodb 文件 数据库 操作类
/// </summary>
public class FileRepository : MongoFileRepository , IFileRepository
{
public override string DataBaseName => "PMFiles";
}
这里创建的实现类也是空的,没有要扩展的方法,常用的接口都在 MongoFileRepository基类中实现了.
注意: 这里重写了DataBaseName这里一定要重写基类的数据库名称。
2.业务层
/// <summary>
/// 文件 操作 业务逻辑层接口
/// </summary>
public interface IFileService : IMongoFileRepository
{
}
实现类
这里主要是调用数据访问层的实例来实现功能。
[AutoInject(typeof(IFileService), InjectType.Scope)]
这里的服务注入用的是自动注入。可以改成手动注册。手动注册时可以删除类上的 AutoInject 标签
/// <summary>
/// 文件操作服务
/// </summary>
[AutoInject(typeof(IFileService), InjectType.Scope)]
public class FileService : IFileService
{
private readonly IFileRepository _srviceFile = RepositoryIocFactory.GetRegisterImp<IFileRepository>();
public Task DeleteFile(string id)
{
return _srviceFile.DeleteFile(id);
}
public Task DeleteFile(ObjectId id)
{
return _srviceFile.DeleteFile(id);
}
public Task<GridFSDownloadStream<ObjectId>> DownloadFileStream(string id)
{
return _srviceFile.DownloadFileStream(id);
}
public Task<GridFSDownloadStream<ObjectId>> DownloadFileStream(ObjectId id)
{
return _srviceFile.DownloadFileStream(id);
}
public Task<GridFSDownloadStream<ObjectId>> DownloadFileStreamSeekable(string id)
{
return _srviceFile.DownloadFileStreamSeekable(id);
}
public Task<GridFSDownloadStream<ObjectId>> DownloadFileStreamSeekable(ObjectId id)
{
return _srviceFile.DownloadFileStreamSeekable(id);
}
public Task<GridFSFileInfo> GetFileById(string id)
{
return _srviceFile.GetFileById(id);
}
public Task<GridFSFileInfo> GetFileById(ObjectId id)
{
return _srviceFile.GetFileById(id);
}
public ObjectId GetInternalId(string id)
{
return _srviceFile.GetInternalId(id);
}
public Task RenameFile(string id, string newFilename)
{
return _srviceFile.RenameFile(id, newFilename);
}
public Task RenameFile(ObjectId id, string newFilename)
{
return _srviceFile.RenameFile(id, newFilename);
}
public Task<ObjectId> UploadFile(string fileName, Stream source)
{
return _srviceFile.UploadFile(fileName, source);
}
}
1、API层的实现
AIP层主要的接口有 上传 和 根据ID下载文件,以文件流对象返回数据。
/// <summary>
/// MongoDB文件上传
/// </summary>
public class FileUploadController : BaseApiController
{
private IFileService serviceFile;
public FileUploadController(IFileService fileService)
{
serviceFile = fileService;
}
/// <summary>
/// 上传文件
/// </summary>
/// <returns></returns>
[HttpPost, Route("UploadFileAsync")]
[DisableRequestSizeLimit]
[DisableFormValueModelBinding]
public async Task<AjaxResultPageModel> UploadFileAsync()
{
var rspModel = new AjaxResultPageModel();
var fileIds = new List<string>();
//检查ContentType
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
rspModel.Warning("内容类型不能为空");
}
else
{
var _defaultFormOptions = new FormOptions();
var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
//把Form的栏位內容逐一取出
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out ContentDispositionHeaderValue contentDisposition);
if (hasContentDispositionHeader)
{
//按文件和键值对分类处理
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
FileMultipartSection currentFile = section.AsFileSection();
//存储文件到Mongo
var id = await serviceFile.UploadFile(currentFile.FileName, section.Body);
fileIds.Add(id.ToString());
}
}
section = await reader.ReadNextSectionAsync();
}
rspModel.Success(fileIds);
}
return rspModel;
}
/// <summary>
/// 下载文件
/// </summary>
[HttpGet,Route("Download")]
public async Task<IActionResult> Download(string id)
{
var fileInfo = await serviceFile.GetFileById(id);
if (fileInfo == null)
{
return NotFound();
}
else
{
return File(await serviceFile.DownloadFileStream(fileInfo.Id), "application/octet-stream", fileInfo.Filename);
}
}
}
前端实现:
前端使用vue组件
<template>
<div class="home">
<el-card class="box-card-top" shadow="never">
<el-row :gutter="100">
<el-col :span="2">
<div class="user-avatar">
<p class="user-avatar-text">{{ avatar }}</p>
</div>
</el-col>
<el-col :span="22">
<div class="text">
<h3>{{ holler }} {{ username }}</h3>
<p>
桂树青春百里疆,鹧鸪啼彻午阴凉. 延平津上峰如削,剑去江空水自长.
--剑道·尘心
</p>
<p>
活在当下,着眼未来.
没有一往无前的觉悟.就不配握紧手中的双刀.空有大志,
却没有实际行动.天下万般兵刃.唯有过往,伤人最深.如果真相带来痛苦.
谎言只会雪上加霜. --LOL·亚索
</p>
</div>
</el-col>
</el-row>
<!-- <h2>首页界面</h2>
<div class="hello"> -->
<!-- <el-input v-model="user" type="text" /> -->
<!-- <div id="message" v-html="remsg"></div> -->
<!-- <div id="el-input">
<el-input id="chatbox" @keyup.native.enter="handle" type="textarea" :rows="1" placeholder="请输入内容" v-model="msg"></el-input>
</div>
<el-button size="small" style="display:inline-block;" icon="el-icon-s-promotion" type="suceess" @click="handle" plain></el-button> -->
<!-- </div> -->
</el-card>
<el-row :gutter="20">
<el-col :span="4">
<el-card class="box-card-center" shadow="never">
<el-upload
:action="action"
:file-list="modeList1"
:http-request="modeUpload1"
:multiple="true"
:before-remove="handleRemove1"
>
<el-button size="small" type="primary">上传</el-button>
</el-upload>
<el-button @click="upload1">点击上传文件</el-button>
<br/>
<el-button @click="fileUpload">点击上传文件1</el-button>
</el-card>
</el-col>
<el-col :span="4">
<el-card class="box-card-center" shadow="never"></el-card>
</el-col>
<el-col :span="4">
<el-card class="box-card-center" shadow="never"></el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import axios from 'axios'
import config from "../../config/index";
import service from "../../api/UploadFiles/index";
export default {
data() {
return {
username: null,
holler: "欢迎回来 — ",
avatar: "",
action: "", //上传文件的接口
mode1: {},
modeList1:[],
fd1: new FormData()
};
},
created() {
this.username = JSON.parse(
sessionStorage.getItem(config.localStorageKey)
).userName;
this.avatar = this.username.slice(0, 1);
},
methods: {
modeUpload1: function(item) {
// console.log(item.file);
// this.mode1 = item.file
// const isIMG =
// item.file.type === 'image/jpg' ||
// item.file.type === 'image/jpeg' ||
// item.file.type === 'image/png'
// const isLt = item.file.size / 1024 / 2000 <= 1
// if (!isIMG) {
// this.error='文件格式有误\n请上传后缀为jpg\\png的图片'
// }
// if (!isLt) {
// // console.log(file.size)
// this.error='上传头像图片大小不能超过500KB!'
// }
// if(isIMG&&isLt){
this.fd1.append('files', item.file); //这里是自己把文件对象获取到,并存储起来
//console.log("modeUpload1-> addFile:",item.file);
//}
},
upload1: function() {
console.log("upload1-> 11,files.length=",this.fd1.getAll('files').length);
if(this.fd1.getAll('files').length===0){//formdata.getAll()返回的是一个file的数组(当然这里是你之前的参数得是文件)
this.error='请先上传文件'
}else {
console.log("upload1-> post");
axios.post('https://localhost:5001/adminapi/FileUpload/UploadFileAsync', this.fd1, { //
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(response => {
console.log("upload1->",response.data);
})
}
},
handleRemove1(file, fileList) {
let files = this.fd1.getAll('files');
this.fd1.delete('files');
let len = files.length
for(var i=0;i<len;i++){
if(files[i].uid!=file.uid){ //uid应该是可以唯一标志文件的,如果不能,就是我,会死
this.fd1.append('files',files[i])
}
}
},
//上传方法
fileUpload(uploader) {
//let form = new FormData();
//form.append("files", uploader.file);
//console.log(this.fd1);
//console.log("--------------------------");
//console.log(uploader)
service.uploadFlie(this.fd1
// onUploadProgress: (progressEvent) => {
// //这一步是展示上传的进度条,不展示也行,根据自身需求决定
// let percent = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
// uploader.onProgress({ percent: percent }); //调用uploader的进度回调
// },
).then((res) => {
console.log(res);
if (res.success) {
this.$message({
message: "上传成功",
type: "success",
});
} else {
this.$message.error("上传失败,"+res.message);
}
})
.catch((err) => {
this.$message.error(err);
});
},
},
};
/* import * as signalR from "@microsoft/signalr";
let hubUrl = "https://localhost:44360/chatHub";//"https://localhost:44367/chatHub";//https://localhost:44318/chatHub; https://localhost:44367/chatHub
//.net core 版本中默认不会自动重连,需手动调用 withAutomaticReconnect
const connection = new signalR.HubConnectionBuilder().withAutomaticReconnect().withUrl(hubUrl).build();
connection.start().catch(err => alert(err.message));
export default {
name: "Im",
mounted() {
var _this = this;
console.log('hubUrl:',hubUrl);
//实现Show方法 OK
connection.on("Show", function(username, message) {
_this.remsg = _this.remsg + "<br>" + username + ":" + message;
console.log('Show:',message);
});
//实现ConnectResponse方法
//connection.on("ChatHubReceiveMsg", function(username, message) {
// _this.remsg = _this.remsg + "<br>" + username + ":" + message;
// console.log('ChatHubReceiveMsg:',message);
//});
//实现DisconnectResponse方法
//connection.on("DisconnectResponse", function(username, message) {
// _this.remsg = _this.remsg + "<br>" + username + ":" + message;
// console.log('DisconnectResponse:',message);
//});
},
data() {
return {
user: "cjh",
msg: "",
remsg: ""
};
},
methods: {
handle: function() {
if(this.msg.trim()==""){
alert("不能发送空白消息");
return;
}
//调用后端方法 SendMsg 传入参数 OK
connection.invoke("SendMsg", this.user, this.msg);
//connection.invoke("PublicSendMsg", this.msg);
this.msg = "";
}
}
}; */
</script>
<style lang="less" scoped>
.home {
margin: 20px;
.box-card-top {
min-height: 160px;
.user-avatar {
width: 100px;
height: 100px;
margin-left: 50px;
margin-top: 5px;
border-radius: 50%;
background-image: linear-gradient(#cdeefa, #b4b4fc);
// background-color: #b4b4fc;
.user-avatar-text {
font-size: 40px;
text-align: center;
padding-top: 22px;
color: #383838;
}
}
.text h3 {
margin-top: 20px;
margin-bottom: 10px;
font-size: 20px;
}
.text p {
font-size: 16px;
user-select: none;
}
}
.box-card-center {
width: 350px;
height: 300px;
margin-top: 20px;
}
}
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
#el-input {
display: inline-block;
width: 96%;
float: left;
}
#message {
overflow-y: auto;
text-align: left;
border: #42b983 solid 1px;
height: 500px;
}
</style>
跨域问题处理。
requestFile.js
注意上图的类型只能是这个类型,只能一个类型,不能多。
//interceptors.js
// vue axios配置 发起请求加载loading请求结束关闭loading
// http request 请求拦截器,有token值则配置上token值
import axios from 'axios'
import common from '../utils/common'
// import { API_BASE } from '../config/config';
// axios.defaults.baseURL = API_BASE;
// api base_url,设置前缀不存在
// const BASE_URL="";
const service = axios.create({
baseURL: '',
timeout: 60000, // 请求超时时间
headers: {
// Authorization: Authorization,
'Content-Type': 'multipart/form-data'
}
});
// Content-Type: application/wasmsql-wasm.wasm
// http请求拦截器
service.interceptors.request.use(
config => {
let token = common.getToken();
//console.log('token:', token);
if (token) {
// bus.$emit('toggleloading', true)//显示loading
// Loading.service(options);
//如果token存在
config.headers['Authorization'] = `Bearer ${token}`;
//console.log('token 1:', token);
}
return config;
},
error => {
Promise.reject(error);
}
)
// http response 服务器响应拦截器,
// 这里拦截401错误,并重新跳入登页重新获取token
service.interceptors.response.use(
response => {
if (response.status === 200) {
//通讯成功
// Toast.clear();
// console.log(response);
/*************
* response.data.status === 0 错误
* response.data.status === 100 成功
* response.data.status === 401 token过期
*
* *************/
// response.data.head.errorCode
// bus.$emit('toggleloading', false)//隐藏loading
if (response.data.state == 401) {//未授权
//如果是token过期,跳转至登录
// console.log("401");
//common.goLogin();
message.error("未授权 请联系管理员!");
//store.commit('setToken', '');
}
else if (response.data.state == 0) {
// Message.error(response.data.message);
return response.data;
} else {
// util.goLogin();
return response.data;
}
}
},
error => {
//请求失败
// ;
const response = error.response;
if (response.status === 401) {
// Toast.fail(response.data.message);
message.error("未授权 请联系管理员!");
//util.goLogin();
} else if (response.status === 403) {
$router.push({
name: '403'
});
} else {
// Toast.fail(response.data.message ? response.data.message : '系统错误请联系管理员');
// message.error({
// message: '无服务,请联系管理员'
// });
}
return Promise.reject(error);
}
);
export default service;
API js
import http from '../../utils/http'
export default {
// 上传文件
uploadFileAsync:(params) => {
return http.postMongoDBFile("adminapi/FileUpload/UploadFileAsync",params);
},
// 下载文件
download:(params) => {
return http.postMongoDBdownloadFile("adminapi/FileUpload/Download?id="+params)
}
};
完成。
END