当前位置:网站首页>57:第五章:开发admin管理服务:10:开发【从MongoDB的GridFS中,获取文件,接口】;(从GridFS中,获取文件的SOP)(不使用MongoDB的服务,可以排除其自动加载类)
57:第五章:开发admin管理服务:10:开发【从MongoDB的GridFS中,获取文件,接口】;(从GridFS中,获取文件的SOP)(不使用MongoDB的服务,可以排除其自动加载类)
2022-08-01 19:29:00 【小枯林】
说明:
(1)本篇博客开发内容:开发【从MongoDB的GridFS中,获取文件,接口】;
目录
二:开发【从MongoDB的GridFS中,获取文件,接口】;
1.在【api】接口工程中的FileUploaderControllerApi接口中,定义【从MongoDB的GridFS中,获取文件,接口】;
2.在【files】文件服务的FileUploaderController类中,去实现【从MongoDB的GridFS中,获取文件,接口】;
四:【从MongoDB的GridFS中,获取文件】需要管理员登录,才能操作;(在InterceptorConfig中,把"AdminTokenInterceptor"拦截器,也作用到该接口上)
(1)在【api】接口工程中,创建AdminCookieTokenInterceptor拦截器;
一:本篇博客合理性说明;
本篇博客,就是开发从GridFS中,获取文件;
即:
二:开发【从MongoDB的GridFS中,获取文件,接口】;
1.在【api】接口工程中的FileUploaderControllerApi接口中,定义【从MongoDB的GridFS中,获取文件,接口】;
/** * 【从MongoDB的GridFS中,获取文件】 * @param faceId * @param request * @param response * @return * @throws Exception */ @GetMapping("/readInGridFS") //设置路由,这个是需要前后端约定好的; public void readInGridFS(@RequestParam String faceId, HttpServletRequest request, HttpServletResponse response) throws Exception;说明:
(1)改接口的请求方式、url、参数,都不是瞎写的,前后端需要保持一致;
(2)这个接口不需要返回值;其主要作用是,从GridFS中读取文件,把文件放到Response中即可;
2.在【files】文件服务的FileUploaderController类中,去实现【从MongoDB的GridFS中,获取文件,接口】;
/** * 【从MongoDB的GridFS中,获取文件,接口】 * @param faceId * @return * @throws Exception */ @Override public void readInGridFS(String faceId, HttpServletRequest request, HttpServletResponse response) throws Exception { // 0.如果前端传过来的FaceId是空,或者是"null";直接抛一个"你所查看的文件不存在!"的自定义的MyCustomException异常; if (StringUtils.isBlank(faceId) || faceId.equalsIgnoreCase("null")) { GraceException.display(ResponseStatusEnum.FILE_NOT_EXIST_ERROR); } // 1. 从GridFS中读取文件; File file = readGridFSByFaceId(faceId); // 2. 把文件放到response中; FileUtils.downloadFileByStream(response, file); } /** * 工具方法:【通过id,从GridFS中,获取文件】→【保存到服务器本地】→【返回该文件,在本地服务器的file对象】 * @param faceId * @return */ private File readGridFSByFaceId(String faceId) throws Exception { /** * 1.通过gridFSBucket.find()方法,从GridFS中,查询文件; * 第一个参数"_id"表示,我们此次查询,是根据id查询; * 第二个参数是,我们具体此次查询的id号; * PS:这个查询结果可能包含多个结果(比如我们根据其他条件查询的时候,结果可能有多个),而且其是可以进行循环的; */ GridFSFindIterable gridFSFindIterable = gridFSBucket.find(Filters.eq("_id", new ObjectId(faceId))); // 2. 获取查询到的单个文件的对象;因为,我们心里清楚,上面根据id去查询,最多只能查到一个;所以,可以直接只看查到结果的第一个; GridFSFile gridFSFile = gridFSFindIterable.first(); // 3. 如果查询的结果为空,直接抛一个"你所查看的文件不存在!"的自定义的MyCustomException异常; if (gridFSFile == null) { GraceException.display(ResponseStatusEnum.FILE_NOT_EXIST_ERROR); } // 4.获得文件名; String fileName = gridFSFile.getFilename(); // 5.把文件保存到服务器本地; // 5.1 创建服务器本地保存图片文件的文件夹 File fileTemp = new File("e:/temp_face"); if (!fileTemp.exists()) { //如果上面的路径不存在,就去创建; fileTemp.mkdirs(); } // 5.2 获得文件流 File myFile = new File("e:/temp_face/" + fileName); // 5.3 创建文件输出流 OutputStream os = new FileOutputStream(myFile);//这个会报FileNotFoundException异常; /** * 5.4 利用gridFSBucket.downloadToStream()方法,把文件保存到服务器本地; * 第一个参数:文件保存到本地的名称;我们这儿以"文件在GridFS中的id",作为"文件保存到本地的名称"; * 第二个参数:文件是输出流; */ gridFSBucket.downloadToStream(new ObjectId(faceId), os);//downloadToStream()方法有其他的重构方式,利用其他的重构的方法也是可以的; // 6. 返回该文件,在本地服务器的file对象; return myFile; }说明:
(1)首先,判断前端传过来的FaceId是空,或者是"null";
(2)然后,调用编写的工具方法readGridFSByFaceId(faceId),从GridFS中,获取文件;
(2.1)根据id,去GridFS中查询;
● GridFS中的id,需要是ObjectId(irg.bson.types)类型的;
● 然后,在查询的时候,其id类型也需要是ObjectId(irg.bson.types)类型的;(PS:这儿其实不是特别清楚)
(2.2)然后,从查询结果中,获得单个文件对象;以通过这个文件对象,获得文件名;
(2.3)创建一个文件流、输出流;然后,会利用(2.2)中查询的文件名,来命名文件保存到本地后的文件名;
(2.4)通过输出流,从GridFS中,下载文件到服务器;(其实,"把文件保存到本地服务器"不是必须的;只是,有的时候为了方便排查问题,也可以顺手把文件保存到本地服务器)
(2.5)返回,该文件的file对象;
(3)把文件放到response中;
(3.1)在【common】通用工程中,创建一个FileUtils工具类:该类主要有两个方法:【把一个文件流,写入到response中】、【把文件流的文件,转成Base64编码的字符串】;我们这儿,会用到第一个方法;
package com.imooc.utils; import sun.misc.BASE64Encoder; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLDecoder; public class FileUtils { /** * 文件流下载,在浏览器展示 * @param response * @param file 文件从盘符开始的完整路径 */ public static void downloadFileByStream(HttpServletResponse response, File file) { String filePath = file.getPath(); System.out.println("filePath = " + filePath); // 对encode过的filePath处理 if (filePath.contains("%")) { try { filePath = URLDecoder.decode(filePath, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } ServletOutputStream out = null; FileInputStream in = null; try { in = new FileInputStream(file); String[] dir = filePath.split("/"); String fileName = dir[dir.length - 1]; String[] array = fileName.split("[.]"); String fileType = array[array.length - 1].toLowerCase(); // 设置文件ContentType类型 if ("jpg,jepg,gif,png".contains(fileType)) { // 判断图片类型 response.setContentType("image/" + fileType); } else if ("pdf".contains(fileType)) { // 判断pdf类型 response.setContentType("application/pdf"); } else { // 设置multipart response.setContentType("multipart/form-data"); } out = response.getOutputStream(); // 读取文件流 int len = 0; byte[] buffer = new byte[1024 * 10]; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } out.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); in.close(); } catch (NullPointerException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } /** * 文件转换为base64 * @param file * @return */ public static String fileToBase64(File file) {//将图片文件转化为字节数组字符串,并对其进行Base64编码处理 InputStream in = null; byte[] fileData = null; // 读取文件字节数组 try { in = new FileInputStream(file); fileData = new byte[in.available()]; in.read(fileData); in.close(); } catch (IOException e) { e.printStackTrace(); } // 对字节数组Base64编码并且返回 BASE64Encoder encoder = new BASE64Encoder(); return encoder.encode(fileData); } }(3.2)调用FileUtils工具类的downloadFileByStream()方法,把文件写入到response中;
三:一个问题:在启动【admin】管理服务和【user】用户服务的时候,报了"com.mongodb.MongoSocketOpenException: Exception opening socket";(解决办法,在【admin】和【user】启动时,在自动装配时,排除MongoDB即可)
(1)错误展示;
(2)原因分析;
其实在【43:第四章:开发文件服务:4:创建子工程【imooc-news-dev-service-files】,文件服务模块;】中,遇到过这类问题;
● 我们在【model,模型子模块】中,引入了MongoDB的依赖;
● 而我们这个工程,【model,模型子模块】引入了【common,通用子模块】,【api,接口子模块】引入了【model,模型子模块】,【admin,管理服务】引入了【api,接口子模块】,【user,用户服务】引入了【api,接口子模块】;所以,就相当于在【admin,管理服务】和【user,用户服务】中,引入了MongoDB依赖;
● 即,【admin,管理服务】和【user,用户服务】的Spring Boot在启动的时候,会做一个自动装配,在这个过程中会去扫描yml配置文件;如果我们没有在配置文件中,去配置MongoDB,其就会报错;
(3)解决办法;
方法1:在【admin,管理服务】和【user,用户服务】中,也去配置MongoDB;(这种方式,不太好)
方法2:在【admin,管理服务】和【user,用户服务】中,通过【@SpringBootApplication(exclude = MongoAutoConfiguration.class)】,启动Spring Boot项目的时候,排除MongoDB自动转配;
(4)效果:OK了,没有报错了;
四:【从MongoDB的GridFS中,获取文件】需要管理员登录,才能操作;(在InterceptorConfig中,把"AdminTokenInterceptor"拦截器,也作用到该接口上)
五:效果;
(1)install一下整个项目;(2)然后,启动【admin】管理服务和【files】文件服务;(3)记得启动前端,使用SwitchHost开启虚拟域名;
启动【user】、【admin】、【files】这几个服务;
六:一个前端的问题:"cookie中的信息是否放在请求中"时,后端如何准确的判断"用户的登录状态"(这个问题,是可以泛化的)
1.问题描述;
现在的情况:前端请求,在请求后端的时候,会主动把【cookie中的aid和atoken】放在请求头中,送到后端;
如此一来,我们前面编写的AdminTokenInterceptor拦截器,在拦截【从MongoDB的GridFS中,获取文件,接口】的时候,是可以从请求头中获取aid和atoken的;
……………………………………………………
即,此时,我们在拦截该接口,从请求头中获取aid和atoken;让该接口只有在管理员登录的时,才能调用;是OK的;
但是,如果前端请求,在请求后端的时候,没有把【cookie中的aid和atoken】放在请求头中,就直接来请求【从MongoDB的GridFS中,获取文件,接口】接口了;
那么此时,【我们前面编写的AdminTokenInterceptor拦截器,在拦截【从MongoDB的GridFS中,获取文件,接口】的时候,无法从请求头中获取aid和atoken的;;;即,该拦截器会判定,当前是管理员未登录状态】→【但是,此时我们,明明是有管理员登录的啊】→【为此,我们就需要一种补救策略:如果请求头中没有aid和atoken的话,我们就需要直接尝试从cookie中获取aid和atoken】→【自然,这儿的逻辑是,如果前端已经这样了,我们在后端如何补救和兼容;;;后续,在条件允许的时候,前后端协调后,是可以进行改善的】
2.补救策略;
(1)在【api】接口工程中,创建AdminCookieTokenInterceptor拦截器;
package com.imooc.api.interceptors; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AdminCookieTokenInterceptor extends BaseInterceptor implements HandlerInterceptor { /** * 拦截请求,在访问controller调用之前 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String adminUserId = getCookie(request, "aid"); String adminUserToken = getCookie(request, "atoken"); boolean run = verifyUserIdToken(adminUserId, adminUserToken, REDIS_ADMIN_TOKEN); return run; } }说明:
(1)我们在BaseInterceptor中,编写了一个工具方法"直接从cookie中获取值";
(2)这个拦截器,是直接从请求中,获取某个cookie的值;然后,再去调用BaseInterceptor中的"工具方法:验证前端的登录信息是否OK;"
(2)使用;(存在疑问……待完善)
如果一个接口是这样的:【该接口需要验证登录状态】、【该系统,会把登录信息,保存在cookie中】;
● 情况1:策略:然后,如果前端请求,在请求后端的时候,会主动把【cookie中的aid和atoken】放在请求头中,送到后端;那么直接使用前面的AdminTokenInterceptor拦截器,去处理就行了;
● 情况2:策略:然后,如果前端请求,在请求后端的时候,没有把【cookie中的aid和atoken】放在请求头中,就直接来请求接口了;那么,我们就可以使用AdminCookieTokenInterceptor拦截器,来处理了;
● 情况3:策略1:然后,如果前端请求,在请求后端的时候,无法确定其"会主动把【cookie中的aid和atoken】放在请求头中"还是"没有把【cookie中的aid和atoken】放在请求头中";那么,我们似乎可以这样处理:使用一个拦截器,只是把上面两个拦截器的逻辑,放在一个拦截器中;
● 情况3:策略2:然后,如果前端请求,在请求后端的时候,无法确定其"会主动把【cookie中的aid和atoken】放在请求头中"还是"没有把【cookie中的aid和atoken】放在请求头中";那么,我们似乎可以这样处理:保留两个拦截器,使用这两个拦截器处理这个请求;
但是,经过一些思考后,发现似乎没必要这样做;因为这样做,会变的在逻辑上有些不太合理、不太适合;
如有需要可以参考【Spring MVC拦截器2:拦截器入门二:拦截器使用技巧;(<mvc:exclude-mapping path=““/>:设置不拦截的url;多个拦截器执行顺序;preHandle()方法的返回值)】;
边栏推荐
- Flowable-based upp (unified process platform) running performance optimization
- #yyds dry goods inventory# Interview must brush TOP101: the last k nodes in the linked list
- BN BatchNorm + BatchNorm的替代新方法KNConvNets
- 【神经网络】一文带你轻松解析神经网络(附实例恶搞女友)
- 【服务器数据恢复】服务器Raid5阵列mdisk组中多块磁盘离线的数据恢复案例
- 【周赛复盘】LeetCode第304场单周赛
- Selenium在远程中的截图
- 使用常见问题解答软件的好处有哪些?
- What are the application advantages of SaaS management system?How to efficiently improve the digital and intelligent development level of food manufacturing industry?
- 安全作业7.25
猜你喜欢

明日盛会|ApacheCon Asia 2022 Pulsar 技术议题一览

Flowable-based upp (unified process platform) running performance optimization

Win11如何开启剪贴板自动复制?Win11开启剪贴板自动复制的方法

MySQL开发技巧——存储过程

OSPO 五阶段成熟度模型解析

Try compiling QT test on Allwinner V853 development board

Website construction process

Mobile Zero of Likou Brush Questions

Win11怎么安装语音包?Win11语音包安装教程

shell脚本专题(07):文件由cfs到bos
随机推荐
[Server data recovery] Data recovery case of offline multiple disks in mdisk group of server Raid5 array
A simple Flask PIN
【周赛复盘】LeetCode第304场单周赛
从普通进阶成优秀的测试/开发程序员,一路过关斩将
Prometheus's Recording rules practice
CMake教程——Leeds_Garden
工作5年,测试用例都设计不好?来看看大神的用例设计总结
Screen: GFF, OGS, Oncell, Incell of full lamination process
Pytorch模型训练实用教程学习笔记:一、数据加载和transforms方法总结
To drive efficient upstream and downstream collaboration, how can cross-border B2B e-commerce platforms release the core value of the LED industry supply chain?
ExcelPatternTool: Excel form-database mutual import tool
TestNG multiple xml for automated testing
分享一个适用于MCU项目的代码框架
The life cycle and scope
AcWing 797. 差分
有序双向链表的实现。
#yyds dry goods inventory# Interview must brush TOP101: the last k nodes in the linked list
When installing the GBase 8c database, the error message "Resource: gbase8c already in use" is displayed. How to deal with this?
安装win32gui失败,解决问题
Win11如何开启剪贴板自动复制?Win11开启剪贴板自动复制的方法
































