目前OSS服务有很多,比如阿里云、腾讯等都有提供,当然这些都是要收费的,费用一般是按流量来收费,不贵。但是在前期一般不需要使用OSS来存储和获取静态文件,所以一般文件都是保存到后台服务器。下面我写的文件服务大概实现步骤和原理。
一、文件上传,一般文件是由后台上传,系统需要使用到的文件主要是图片、文档(word,excel,pdf)、视频等,其中图片有缩略图的使用,很多地方要使用图片,后台上传时是不知道会要用到哪些尺寸的缩略图的,所以在上传的时候生成缩略图不现实(早期很多商城系统是在上传的时候就生成了一些固定尺寸的缩略图),我这里采用的是使用时自动生成。文件上传实现还比较简单,后端接口直接获取File读取内容进行保存处理即可,前端使用VUE的话也简单,代码如下:
前端代码,我这里上传的文件类型,包括图片、文档、视频,type就是文件类型1 图片 2 视频 5 文档。视频和文档需要上传封面图片,并且可以指定授权访问权限,如果非公开访问
的文件需要授权申请才可以访问,在前端上传时我踩到了一个坑,使用VUE上传文件,后端获取不到文件内容,原因是nginx反向代理将文件流的内容过滤掉了,接口获取不到文件流的内
容,我这里采取了另外一个方法上传图片,就是将图片转换为base64编码,通过字符串的参数传到接口,接口对base64编码进行解码,将base64编码解码成字节流,然后保存到服务器

1 <el-form ref="form" label-width="134px" :model="form" size="small" :rules="rules"> 2 <el-form-item :key="1" label="本地资料:" v-if="type === 5" prop="documentSrc"> 3 <el-upload show-word-limit list-type="picture-card" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx" 4 :class="{ 'upload-max': form.documentList.length >= 1 }" :file-list="form.documentList" 5 :on-change="documentChange" action="#" multiple :limit="1" :auto-upload="false"> 6 <i class="el-icon-plus" slot="trigger"></i> 7 <div slot="file" slot-scope="{file}"> 8 <video :src="file.url" :autoplay="false" 9 style="width:148px;height:148px;background:rgba(0,0,0, .85);"></video> 10 <i class="el-icon-delete video-delete" @click="documentDelete"></i> 11 </div> 12 <div slot="tip" class="el-upload__tip"> 13 资料文档大小不超过30M,支持pdf,word,excel,ppt格式 14 </div> 15 </el-upload> 16 </el-form-item> 17 <el-form-item label="名称:" :key="5" prop="documentName" v-if="type === 5"> 18 <el-col :span="7"> 19 <el-input v-model="form.documentName" v-trim maxlength="20" show-word-limit /> 20 </el-col> 21 </el-form-item> 22 <el-form-item :key="1" label="本地视频:" v-if="type === 2" prop="videoSrc"> 23 <el-upload show-word-limit list-type="picture-card" accept="video/mp4,video/3gp" 24 :class="{ 'upload-max': form.videoList.length >= 1 }" :file-list="form.videoList" :on-change="videoChange" 25 action="#" multiple :limit="1" :auto-upload="false"> 26 <i class="el-icon-plus" slot="trigger"></i> 27 <div slot="file" slot-scope="{file}"> 28 <video :src="file.url" :autoplay="false" 29 style="width:148px;height:148px;background:rgba(0,0,0, .85);"></video> 30 <i class="el-icon-delete video-delete" @click="videoDelete"></i> 31 </div> 32 <div slot="tip" class="el-upload__tip"> 33 视频大小不超过30M,支持mp4,3gp格式 34 </div> 35 </el-upload> 36 </el-form-item> 37 <el-form-item label="名称:" :key="2" prop="videoName" v-if="type === 2"> 38 <el-col :span="7"> 39 <el-input v-model="form.videoName" v-trim maxlength="20" show-word-limit /> 40 </el-col> 41 </el-form-item> 42 <el-form-item label="分组:"> 43 <el-col :span="7"> 44 <el-select v-model="selCategory.id" class="dialog-select"> 45 <el-option v-for="(item, index) in category" :key="index" :label="item.name" :value="item.id"> 46 </el-option> 47 </el-select> 48 </el-col> 49 </el-form-item> 50 <el-form-item :key="3" label="选择本地图片: " required v-if="type === 1"> 51 <el-upload ref="imageUpload" list-type="picture-card" :class="{ 'upload-max': imageFileList.length >= 100 }" 52 accept="image/jpeg,image/gif,image/png,image/bmp,image/webp" :on-remove="imageChange" 53 :on-exceed="imageExceed" :on-change="imageChange" :action="imageUploadUrl" multiple :limit="100" 54 :auto-upload="false" :file-list="imageFileList" :data="imageUploadData" :headers="headers" 55 :before-upload="beforeImageUpload" :on-success="uploadImageSuccess"> 56 <i class="el-icon-plus" slot="trigger"></i> 57 <div slot="tip" class="el-upload__tip"> 58 仅支持png, jpg, gif, bmp 4种格式,大小不超过3.0MB 59 </div> 60 </el-upload> 61 </el-form-item> 62 <el-form-item label="访问权限设置:" v-if="type === 2 || type == 5"> 63 <el-col :span="7"> 64 <el-select v-model="form.accessControl" class="dialog-select"> 65 <el-option label="公开访问" value="0"></el-option> 66 <el-option label="申请授权访问" value="1"></el-option> 67 <el-option label="不允许访问" value="2"></el-option> 68 </el-select> 69 </el-col> 70 </el-form-item> 71 <!-- 视频封面专用 --> 72 <el-form-item label="封面:" :key="4" v-if="type === 2" prop="videoPoster"> 73 <el-upload list-type="picture-card" accept="image/jpeg,image/gif,image/png,image/bmp" 74 :class="{ 'upload-max': form.videoPosterList.length >= 1 }" :on-remove="videoPosterRemove" 75 :on-change="videoPosterChange" :action="imageUploadUrl" :limit="1" :auto-upload="false" 76 :file-list="form.videoPosterList"> 77 <i class="el-icon-plus" slot="trigger"></i> 78 <div slot="tip" class="el-upload__tip"> 79 建议尺寸:800*800 像素,支持jpg、gif、png3种格式,大小不超过3MB 80 </div> 81 </el-upload> 82 </el-form-item> 83 <!-- 资料封面图 --> 84 <el-form-item label="封面:" :key="4" v-if="type === 5" prop="documentPoster"> 85 <el-upload list-type="picture-card" accept="image/jpeg,image/gif,image/png,image/bmp" 86 :class="{ 'upload-max': form.documentPosterList.length >= 1 }" :on-remove="documentPosterRemove" 87 :on-change="documentPosterChange" :action="imageUploadUrl" :limit="1" :auto-upload="false" 88 :file-list="form.documentPosterList"> 89 <i class="el-icon-plus" slot="trigger"></i> 90 <div slot="tip" class="el-upload__tip"> 91 建议尺寸:800*800 像素,支持jpg、gif、png3种格式,大小不超过3MB 92 </div> 93 </el-upload> 94 </el-form-item> 95 </el-form> 96 <template slot="footer"> 97 <el-button size="small" @click="showDialog = false">取 消</el-button> 98 <el-button size="small" type="primary" @click="handleConfirmUpload" :loading="uploading">确 定</el-button> 99 </template> 100 // JS代码,上传实现,其中UploadVideo和UploadDocument分别是上传视频和上传文档的接口定义 101 handleConfirmUpload() { 102 this.$refs['form'].validate((valid) => { 103 if (valid) { 104 if (this.type === 2) { 105 if (!this.beforeVideoUpload(this.form.videoList[0])) { 106 return 107 } 108 109 if (this.form.videoPosterList[0]) { 110 const isLt3M = this.form.videoPosterList[0].size / 1024 / 1024 < 3 111 if (!isLt3M) { 112 return this.$message('error', '上传的封面大小不能超过 3.0MB!') 113 } 114 } 115 116 this.uploading = true 117 const formData = new FormData() 118 formData.append('name', this.form.videoName) 119 formData.append('categoryId', this.selCategory.id) 120 formData.append('video', this.form.videoList[0].raw) 121 formData.append('cover', this.form.videoPosterList[0] ? this.form.videoPosterList[0].raw : '') 122 formData.append('accessControl', this.form.accessControl) 123 UploadVideo(formData).then((res) => { 124 this.showDialog = false 125 this.form.videoName = '' 126 this.form.videoList = [] 127 this.uploading = false 128 this.form.videoPosterList = [] 129 this.loadCategory() 130 }) 131 } else if (this.type === 5) { 132 if (!this.beforeDocumentUpload(this.form.documentList[0])) { 133 return 134 } 135 136 if (this.form.documentPosterList[0]) { 137 const isLt3M = this.form.documentPosterList[0].size / 1024 / 1024 < 3 138 if (!isLt3M) { 139 return this.$message('error', '上传的封面大小不能超过 3.0MB!') 140 } 141 } 142 143 this.uploading = true 144 const formData = new FormData() 145 formData.append('name', this.form.documentName) 146 formData.append('categoryId', this.selCategory.id) 147 formData.append('document', this.form.documentList[0].raw) 148 formData.append('cover', this.form.documentPosterList[0] ? this.form.documentPosterList[0].raw : '') 149 formData.append('accessControl', this.form.accessControl) 150 UploadDocument(formData).then((res) => { 151 this.showDialog = false 152 this.form.documentName = '' 153 this.form.documentList = [] 154 this.uploading = false 155 this.form.documentPosterList = [] 156 this.loadCategory() 157 }) 158 } else { 159 if (this.imageFileList.length === 0) { 160 return this.$message('error', '请至少选择一张图片') 161 } 162 this.uploading = true 163 this.$refs.imageUpload.submit() 164 } 165 } 166 }) 167 }
base64版前端代码,base64版本主要是多了一个步骤,将图片文件转换为base64编码,在el-upload组件里面加上onchange事件,调用getFile方法即可

1 getFile(file, fileList) { 2 this.filename = file.name 3 if (!this.$isImage(this.filename)) { 4 return this.$message('error', '请选择图片文件') 5 } 6 this.list[this.uploadIndex].thumbnails = this.getFilesCode(fileList) 7 console.log(this.list[this.uploadIndex].thumbnails) 8 }, 9 getFilesCode(fileList) { 10 var thumbnailBase64code = [] 11 fileList.forEach((item, index) => { 12 var filecodeobj = { name: item.name, code: '' } 13 this.getBase64(item.raw).then(res => { 14 filecodeobj.code = res 15 }) 16 thumbnailBase64code.push(filecodeobj) 17 }) 18 return thumbnailBase64code 19 }, 20 getBase64(file) { 21 return new Promise(function (resolve, reject) { 22 const reader = new FileReader() 23 let imgResult = '' 24 reader.readAsDataURL(file) 25 reader.onload = function () { 26 imgResult = reader.result 27 } 28 reader.onerror = function (error) { 29 reject(error) 30 } 31 reader.onloadend = function () { 32 resolve(imgResult) 33 } 34 }) 35 }
后端代码如下,base64版本稍有区别,就将文件对像换成了字符串类型,处理时多了一个步骤,将base64的字符串转换为字节流

1 /// <summary> 2 /// 上传图片 3 /// </summary> 4 /// <param name="command">上传实体</param> 5 /// <returns></returns> 6 [HttpPost("UploadImage")] 7 [RequestSizeLimit(10_000_000)] 8 public IActionResult UploadImage([FromForm] UploadImageCommand command) 9 { 10 var stream = command.File.OpenReadStream(); 11 var fileName = command.File.FileName; 12 var result = application.CreateImage(command.Category, fileName, command.Info, stream); 13 return SuccessResult(result); 14 } 15 16 /// <summary> 17 /// 上传视频 18 /// </summary> 19 /// <param name="command">上传视频的实体</param> 20 /// <returns></returns> 21 [HttpPost("UploadVideo")] 22 [RequestSizeLimit(100_000_000)] 23 public IActionResult UploadVideo([FromForm] UploadVideoCommand command) 24 { 25 var fileData = application.CreateVideo(command); 26 return SuccessResult(fileData); 27 } 28 /// <summary> 29 /// 上传视频 30 /// </summary> 31 /// <param name="command">上传视频的实体</param> 32 /// <returns></returns> 33 [HttpPost("UploadDocument")] 34 [RequestSizeLimit(100_000_000)] 35 public IActionResult UploadDocument([FromForm] UploadDocumentCommand command) 36 { 37 var fileData = application.CreateDocumnet(command); 38 return SuccessResult(fileData); 39 }
文件处理代码如下,文件保存我使用了注入机制,可以通过配置文件配置使用的文件保存方式 ,可以使用阿里云OSS等服务,也可以自定义,目前我使用的是自定义的服务

1 /// <summary> 2 /// 添加图片文件 3 /// </summary> 4 public FileResult CreateImage(int categoryId, string fileName, string info, Stream fileStream, bool compressImage = false, long supplierId = 0) 5 { 6 if(!AllowExtension(fileName)) 7 throw new AVEException("不支持的文件格式"); 8 if(fileStream.Length > 0x300000)//3M 9 throw new AVEException("文件大小不能超过3M"); 10 string fileext = Path.GetExtension(fileName).ToLower(); 11 if(fileName.Length > 50) 12 { 13 14 fileName = fileName.Substring(0, 50 - fileext.Length) + fileext; 15 } 16 var file = new FileItem 17 { 18 CategoryId = categoryId, 19 Type = FileType.Image, 20 Name = fileName, 21 Info = info, 22 Size = fileStream.Length, 23 SupplierId = supplierId, 24 CompressImage = compressImage 25 }; 26 file.Extension = fileext; 27 (file.Path, file.ETag) = PutImage(file.Extension, fileStream); //保存图片 28 fileStream.Position = 0; //将文件流重新定位到开始,如果不写后面加载图片会报错 29 var image = Image.Load(fileStream); 30 file.Info = $"{image.Width}X{image.Height}"; 31 repository.AddFile(file); 32 return file.Map<FileResult>(); 33 }
二、文件获取,如果是简单的获取文件服务器地址+文件路径即可,我这里主要是实现缩略图即时获取的功能,以及视频和文档等文件的权限控制
1. 缩略图的即时生成,很多地方使用到缩略图,但是尺寸可能会变化,所以需要即时生成缩略图,但是也不能一直生成新的图片,所以需要判断图片是否存在,存在则直接访问,否则调用接口生成,如何判断图片是否存在呢,前端可以通过自定义指定判断,图片是否存在,不存在则调用接口获取,但是自定义指定在小程序中是无法使用的,所以我这边采用了后端接口处理,使用了.net6的拦截机制,将404请求拦截并且 判断请求的是不是图片,如果是图片并且是缩略图的请求,则将请求重写向到缩略图生成的接口地址,代码如下

1 // 拦截404请求 2 app.Use((context, next) => 3 { 4 var res = next(context); 5 if(context.Response.StatusCode == 404 && context.Request.Method == "GET") 6 { 7 var path = context.Request.Path.ToString(); 8 var ext = Path.GetExtension(path); 9 Console.WriteLine(ext); 10 bool isRedirect = false; 11 var redirectUrl = FileNotFoundExtensions.Get404ResourceUrl(context, out isRedirect); 12 if(!isRedirect) 13 { 14 context.Response.StatusCode = 200; 15 context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("404 页面没有找到" + context.Request.ContentType)); 16 } 17 else 18 { 19 context.Response.Redirect(redirectUrl); 20 } 21 } 22 23 return res; 24 }); 25 // 404请求扩展类,处理404请求 26 public static class FileNotFoundExtensions 27 { 28 /// <summary> 29 /// 获取图片缩略图尺寸 30 /// </summary> 31 /// <param name="filenameArr"></param> 32 /// <param name="path"></param> 33 /// <returns></returns> 34 private static List<int> GetThumSizes(string[] filenameArr, out string filename) 35 { 36 char[] splits = { '*', '_', '-', ',', ' ' }; 37 filename = string.Empty; 38 var thumsizelist = new List<int>(); 39 var sizeArr = new List<string>(); 40 //取最后两个可能是尺寸的值 41 if(filenameArr.Length > 2) 42 { 43 sizeArr.Add(filenameArr[filenameArr.Length - 2]); 44 sizeArr.Add(filenameArr[filenameArr.Length - 1]); 45 } 46 else 47 { 48 sizeArr.Add(filenameArr[filenameArr.Length - 1]); 49 } 50 for(var i = 1; i < filenameArr.Length - sizeArr.Count; i++) 51 { 52 filename += "_" + filenameArr[i]; 53 } 54 //两个值都是数字 55 if(sizeArr.Count == 2) 56 { 57 if(sizeArr[0].ToInt() > 0 && sizeArr[1].ToInt() > 0) //两个值都是数字 58 { 59 thumsizelist.Add(sizeArr[0].ToInt()); 60 thumsizelist.Add(sizeArr[1].ToInt()); 61 } 62 else if(sizeArr[1].ToInt() > 0) //最后一个是数字 63 { 64 thumsizelist.Add(sizeArr[1].ToInt()); 65 filename += "_" + sizeArr[0]; 66 } 67 else //最后一个不是数字,但是有可能是组合的尺寸值 68 { 69 filename += "_" + sizeArr[0]; 70 var sublist = sizeArr[1].Split(splits); 71 if(sublist.Length == 2 && sublist[0].ToInt() > 0 && sublist[1].ToInt() > 0) 72 { 73 thumsizelist.Add(sublist[0].ToInt()); 74 thumsizelist.Add(sublist[1].ToInt()); 75 } 76 } 77 } 78 else 79 { 80 if(sizeArr[0].ToInt() > 0) 81 thumsizelist.Add(sizeArr[0].ToInt()); 82 else 83 { 84 var sublist = sizeArr[0].Split(splits); 85 if(sublist.Length == 2 && sublist[0].ToInt() > 0 && sublist[1].ToInt() > 0) 86 { 87 thumsizelist.Add(sublist[0].ToInt()); 88 thumsizelist.Add(sublist[1].ToInt()); 89 } 90 } 91 } 92 return thumsizelist; 93 } 94 public static string Get404ResourceUrl(HttpContext context, out bool isRedirect) 95 { 96 97 isRedirect = false; 98 var path = context.Request.Path.ToString(); 99 var ext = Path.GetExtension(path); 100 Console.WriteLine(ext); 101 var resourceUrl = string.Empty; 102 if(FileHelper.IsImageExt(ext)) 103 { 104 resourceUrl = "/static/common/images/404.png"; 105 var pathArr = path.Split('/'); 106 var filename = pathArr[pathArr.Length - 1].UrlDecodeToNullString(); 107 var filenameArr = filename.Replace(ext, "").Split('_'); 108 109 //如果是图片文件请求,并且带有缩略图大小 110 string newpath = ""; 111 string newfilename = ""; 112 if(filenameArr.Length > 1) 113 { 114 var thumsizelist = GetThumSizes(filenameArr, out newfilename); 115 //不包含尺寸,说明不是缩略图 116 if(thumsizelist.IsEmpty()) 117 { 118 isRedirect = true; 119 return resourceUrl; 120 } 121 122 var thumsize = string.Join(",", thumsizelist); 123 for(var i = 0; i < pathArr.Length - 1; i++) 124 newpath += (newpath.IsEmptyString() ? "" : "avepathsplit") + pathArr[i]; 125 newpath += "avepathsplit" + filenameArr[0] + newfilename + ext; 126 isRedirect = true; 127 return "/WebApi/common/" + thumsize + "/" + newpath; 128 129 } 130 else 131 { 132 isRedirect = true; 133 return resourceUrl; 134 } 135 } 136 else if(FileHelper.IsVideoExt(ext)) 137 { 138 isRedirect = true; 139 return "/static/common/video/404.mp4"; 140 } 141 else if(FileHelper.IsDocumentExt(ext)) 142 { 143 isRedirect = true; 144 return "/static/common/images/404.png"; 145 146 } 147 return ""; 148 } 149 }
缩略图生成方式,首先判断原图是否存在,如果不存在则返回404的图片,然后获取缩略图的尺寸,默认为指定宽度,如果需要同时指定宽度和高度可以使用*-_, 等字符分隔,比如400*800,400_800等表示宽400高800,如果要访问images/a.jpg,400*400的缩略图,可以使用 http://www.abc.com/webapi/common/400/images%2Fa.jpg,注意:如果路径中包含了/在请求接口时,接口可能会无法识别为参数,会识别为路径,所以需要将路径使用UrlEncode编码后再传给接口,使用nginx的需要注意,这里有一个坑,即使使用UrlEncode编码了路径 ,nginx仍然会将编码的路径解码,然后再转发给接口,接口仍然获取不到路径,我想了一个解决办法,就是不进行UrlEncode,面是将路径中的/替换为指定的字符串,然后接口中再替换回来,指定的字符串可以自定义,但是需要保存一般的路径中不会出现,否则可能会出现找不到文件的问题,接口代码如下:

1 /// <summary> 2 /// 访问图片 3 /// </summary> 4 /// <param name="size">所访问图片的大小,生成后图片的长和度都不会超过指定的大小,如果要指定宽度,可以宽度和高度之间使用-分隔,默认为长和宽大小都为指定大小</param> 5 /// <param name="name">所要访问图片的名称或者相对地址</param> 6 /// <returns>图片</returns> 7 [HttpGet] 8 [Route("{size}/{name}")] 9 public IActionResult GetImage(string size, string name) 10 { 11 int thumWidth = 0, thumHeight = 0; 12 char[] splits = { '*', '_', '-', ',', ' ' }; 13 size = size.UrlDecodeToNullString(); 14 var sizeArr = size.Split(splits); 15 // 16 if(sizeArr.Length > 1) 17 { 18 thumWidth = sizeArr[0].ToInt(); 19 thumHeight = sizeArr[1].ToInt(); 20 } 21 else 22 { 23 thumWidth = sizeArr[0].ToInt(); 24 thumHeight = thumWidth; 25 } 26 var errorImage = "/static/common/images/404.png";//没有找到图片 27 if(name.IsEmptyString()) 28 { 29 Logger.LogInformation($"图片名称为空:{name},size:{size}"); 30 name = errorImage; 31 } 32 else 33 { 34 name = HttpUtility.UrlDecode(name, Encoding.GetEncoding("utf-8")); 35 name = name.Replace("avepathsplit", "/").Replace("|", "/").Replace("/", "\\"); 36 if(name.StartsWith("\\")) { name = name.Substring(1); } 37 } 38 var contentTypeStr = "image/jpeg"; 39 var ext = Path.GetExtension(name); 40 //未知的图片类型 41 if(!FileHelper.IsImageExt(ext)) 42 { 43 Logger.LogInformation($"不允许的扩展名:{ext},size:{size},name:{name}"); 44 name = errorImage; 45 ext = ".ext"; 46 } 47 else 48 { 49 contentTypeStr = FileHelper.GetContentType(ext); 50 } 51 bool isThum = false;//是否是缩略图 52 var thumname = name;//缩略图路径 53 bool saveThum = false; 54 //原图 55 if(thumWidth <= 0 && thumHeight <= 0) 56 { 57 using(var sw = objectStorage.GetObject(name)) 58 { 59 var bytes = new byte[sw.Length]; 60 sw.Read(bytes, 0, bytes.Length); 61 sw.Close(); 62 Logger.LogInformation($"宽度和宽度小于等于0,返回原图"); 63 return new FileContentResult(bytes, contentTypeStr); 64 } 65 } 66 else 67 { 68 if(thumWidth == thumHeight) 69 { 70 thumname = name.Replace(ext, "_" + thumWidth + ext); 71 } 72 else 73 { 74 thumname = name.Replace(ext, "_" + thumWidth + "-" + thumHeight + ext); 75 } 76 77 isThum = true; 78 } 79 80 if(name != errorImage && !objectStorage.ExistObject(name)) 81 { 82 Logger.LogInformation($"图片不存在:{name},size:{size}"); 83 name = errorImage; 84 } 85 if(objectStorage.ExistObject(thumname)) 86 { 87 using(var thumstream = objectStorage.GetObject(name)) 88 { 89 var bytes = thumstream.ToByteArray(); 90 thumstream.Close(); 91 return new FileContentResult(bytes, contentTypeStr); 92 } 93 } 94 if(isThum && !objectStorage.ExistObject(thumname)) 95 { 96 saveThum = true; 97 } 98 99 //缩小图片 100 using(var stream = objectStorage.GetObject(name)) 101 { 102 var imgBmp = Image.Load(stream); 103 //计算尺寸 104 var oWidth = imgBmp.Width; 105 var oHeight = imgBmp.Height; 106 Tuple<int, int> sizes = GetThumSize(oWidth, oHeight, thumWidth, thumHeight); 107 108 stream.Position = 0; 109 var ms = ImageHelper.GetThumbnailStream(stream, sizes.Item1, sizes.Item2, Path.GetExtension(name)); 110 111 var bytes = ms.ToByteArray(); 112 if(saveThum) 113 { 114 ms.Position = 0; 115 objectStorage.PutObject(thumname, ms); 116 } 117 else 118 { 119 Logger.LogInformation($"不保存缩略图name:{name},thumname:{thumname},size:{size}"); 120 } 121 stream.Close(); 122 ms.Close(); 123 var obj = objectStorage.GetObject(thumname); 124 return new FileContentResult(obj.ToByteArray(), contentTypeStr); 125 } 126 }
2. 视频和文档的访问控制实现,如果视频或者文件指定了需要授权才能访问,所以请求的时候不能直接返回地址,需要加鉴权的控制,如果没有授权则只能查看封面图片,授权之后可以查看内容,但是授权之后的返回的也不是具体的地址,不然将地址发给别人就无法控制了,我采用了token授权,token加了有效时间和会员身份的验证,即使用访问地址发给别人也无法访问,实现代码如下:

1 /// <summary> 2 /// 访问文件 3 /// </summary> 4 /// <param name="token">所要访问文件Id</param> 5 /// <returns>文件内容</returns> 6 [HttpGet] 7 [Route("File/{token}")] 8 public IActionResult GetFile(string token) 9 { 10 long memberId = tokenHelper.GetUserTokenValue(token, "MemberId").ToLong(); 11 long id = tokenHelper.GetUserTokenValue(token, "RequestKey").ToLong(); 12 var fileitem = fileApplication.GetFileItem(id); 13 if(fileitem == null) 14 { 15 return new BadRequestResult(); 16 } 17 if(fileitem.AccessControl != 0) 18 { 19 if(memberId > 0) 20 { 21 var accessapply = fileApplication.GetFileAccessApply(fileitem.Id, memberId); 22 if(accessapply == null || accessapply.Status != Service.Common.Models.AccessApplyStatus.Audited) 23 { 24 return new BadRequestResult(); 25 } 26 } 27 else 28 { 29 return new BadRequestResult(); 30 } 31 } 32 var contentTypeStr = "image/jpeg"; 33 var ext = Path.GetExtension(fileitem.Path); 34 contentTypeStr = FileHelper.GetContentType(ext); 35 using(var sw = objectStorage.GetObject(fileitem.Path)) 36 { 37 var bytes = new byte[sw.Length]; 38 sw.Read(bytes, 0, bytes.Length); 39 sw.Close(); 40 return new FileContentResult(bytes, contentTypeStr); 41 } 42 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!