zoukankan      html  css  js  c++  java
  • 个人技术总结

    个人技术总结

    ——使用JSON传输图片

    这个作业属于哪个课程 2021春软件工程实践|S班
    这个作业要求在哪里 软件工程实践总结&个人技术博客
    这个作业的目标 分析描述并总结擅长的相关技术
    其他参考文献 见文末

    1技术概述

    一种后端使用JSON接收前端上传图片的技术,适用于前端使用JSON提交文件的情况,且该方法相较于其他传输方法有适用平台广、更符合RESTful思想的特点,难点在于对图片信息进行Base64格式的编解码。

    2技术详述

    2.1主要思想

    本文介绍的图片上传方式,与Base64编解码密接相关,主要思路如下:

    1.前端将图片转换为Base64格式字符串(因为在团队开发中前端使用微信小程序技术,直接将文件转换Base64的图片上传组件能很容易找到),并提取必要的图片信息;

    2.将该字符串和图片信息根据接口文档填充到JSON中,并发送给后端;

    3.后端接收前端JSON,解析JSON内容,将图片Base64字符串转换为字节数组;

    4.根据前端提供的图片信息将字节数组保存为相应格式文件。

    状态图

    2.2后端实现过程

    接下来主要讲解后端对图片的处理过程

    1.在Controller层中,从JSON数据中获取图片的Base64字符串

      @PostMapping("/imgupload")
      public Result saveImage(@RequestBody ImageRequest imageRequest) {
          
          /*可在此鉴权,并进行其他初始化工作*/
          
          String base64Source = imageRequest.getBase64Str();
          Pair<ExceptionInfo,String> info = imageService.saveImage(base64Source);//交由Service层处理
          
          /*......*/
          
      }
    

    2.在Service层中,为图片生成文件名

    一个前端传来的Base64字符串的例子:

    data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABTMAAAOtCAYAAABdcvHGAAASFnRFWHRteGZpbGUAJTNDbXhmaWxlJTIwaG9zdCUzRCUyMnd3dy5mcmVlZG
    

    首先判断文件后缀名。根据字符串前端包含的Mime类型信息,判断文件格式,对应相应的扩展名(如上例中的image/png对应的就是.png扩展名)。

    /*为方便从字符串中提取信息,这里的prefix包含第一个,前的所有内容。
      Map<String,String> mimeTypeMap中包含类似("data:image/png;base64",".png"),("data:image/jpeg;base64",".jpeg"),("data:image/gif;base64",".gif")这样的(key,value)键值对
    /**
      * 根据Base64字符串前缀生成文件后缀名
      *
      * @param prefix Base64字符串前缀,类似data:image/png;base64
      * @return 文件后缀名
      */
    public static String generateFileSuffixByBase64Prefix(String prefix) {
        String suffix = "";
        if (prefix != null) {
            if(mimeTypeMap.containsKey(prefix)){
                suffix = mimeTypeMap.get(prefix);
            }
        }
        return suffix;
    }
    

    之后,为图片生成文件名前缀,这里使用UUID减少名字重复的可能。

    String uuid = UUID.randomUUID().toString().replace("-","");//去除原本UUID中的横杠“-”
    

    最后,将前缀和后缀拼接形成文件名。

    String backendFileName = uuid + suffix;
    

    3.解码Base64并保存。使用Java8及以上的Base64标准类库java.util.Base64中的解码器将Base64字符串转换为字节数组。再保存到文件中。

    Decoder decoder = Base64.getDecoder();//获取解码器
    try (OutputStream outputStream = new FileOutputStream(Paths.get(folderName,backendFileName).toString())) {
        byte[] bytes = decoder.decode(source);//使用解码器解码Base64字符串为字节数组
        for (int i = 0;i < bytes.length;i++) {//矫正偏移
            if (bytes[i] < 0) {
                bytes[i] += 256;
            }
        }
        outputStream.write(bytes);//写入文件
    }catch (FileNotFoundException e) {
        
        /*处理异常*/
        
        e.printStackTrace();
    }catch (IOException e) {
        
        /*处理异常*/
        
        e.printStackTrace();
    }
    

    4.经过以上步骤,前端图片就上传到后端并保存到服务器中了。服务器经过进一步设置,前端就可访问图片资源。

    base64解码流程

    3技术使用中遇到的问题和解决过程

    问题:先前使用的sun.misc.BASE64Decoder提供的解码器会在Eclipse或者VSCode中报告错误

    Access restriction: The type 'BASE64Decoder' is not API
    

    并且经过进一步了解,使用这种解码器的效率不高

    解决过程:通过网上搜索资料,一开始查找的是如何解决报错问题,但是找到解决方法后,考虑到其他队友的配置问题遂放弃这条思路,转变为查找更多Java中用于Base64解码的API,在网上的一篇文章中找到了Java8及以上版本可以使用java.util.Base64包对Base64进行编解码,并且效率是sun.misc的11倍以上,并且无需多余的配置,直接引包即可使用,不会报错,问题圆满解决。

    import java.util.Base64;
    import java.util.Base64.Decoder;//解码器
    import java.util.Base64.Encoder;//编码器
    

    4总结

    使用JSON传输图片有好处也有坏处。

    好处是相对于其他文件传输方式更易于理解,和处理其它接口一样,都是使用JSON与后端交互,后端可以有一个统一的处理方法,处理方法也更加灵活,可以加入鉴权等功能,而且可以隐藏用户文件的隐私细节,只需要图片的格式信息即可;此外,JSON格式的请求处理并没有限制后端使用的技术,后端使用其他框架,其他语言,前端无需作出修改也能够比较好的实现后端文件的接收,更加符合REST风格,并且也降低了对前端的要求,前端一般都可以都可以实现图片到Base64的转换。

    坏处是增加了编码和解码的两个环节,增加了处理时间,降低了效率;更糟糕的是使用Base64表示文件数据会使数据量增大,增加了33.3%的字节数,我想这可能就是这种方法没有其他文件传输方法常用的原因。

    5参考文献与参考博客

    1.base64 - 廖雪峰的官方网站 (liaoxuefeng.com)

    2.Base64编码为什么会使数据量变大? - 尘恍若梦 - 博客园 (cnblogs.com)

    3.eclipse报Access restriction: The type 'BASE64Decoder' is not API处理方法_蝈蝈的博客-CSDN博客

    4.Java实现Base64加解密的方式_小菜鸟入门-CSDN博客

    6主题提炼 ——一个简单的例子

    前端发送json的一个例子

    {
    "base64Str":"data:image/jpg;base64,/9j/4RXrRXhpZgAATU0AKgAAAAgADQEAAAMAAAABAuoAAAEBAAMAAAABAuoAAAECAAMAAAADAAAAqgEGAAMAAAABAAIAAAESAAMAAAABAAEAAAEVAAMAAAABAA"/*省略了之后的部分*/
    }
    

    1.后端Controller处理代码

    @PostMapping("/imgupload")
      public Result savePostImage(@RequestBody Map<String,Object> requestMap
          , HttpServletRequest request) {
          String base64Source = (String)requestMap.get("base64Str");//分离Base64字符串
          Result result;
          if (base64Source != null && fileName != null) {
              Pair<ExceptionInfo,String> info = imageService.saveImage(base64Source);//交由Service层处理
              if (info.getKey().equals(ExceptionInfo.OK)) {
                  result = Result.success(info.getValue());
              } else {
                  result = Result.error(info.getKey().getCode(), info.getKey().getMessage());
              }
          }else {
              result = Result.error(ExceptionInfo.POST_IMAGE_CONTENT_EMPTY.getCode()
                      ,ExceptionInfo.POST_IMAGE_CONTENT_EMPTY.getMessage());
          }
          return result;
      }
    

    2.Service层处理方法saveImage

    public Pair<ExceptionInfo,String> saveImage(String source, String fileName) {
        Pair<ExceptionInfo,String> info = new Pair<>(ExceptionInfo.POST_IMAGE_STORE_FAIL,"");
        
        //分离字符串中前部的信息串data:image/jpg;base64和以/9j/4RXrRXhp开始的可以解码的Base64字符串
        String[] sourceOrigin = source.split(",");
        
        if (sourceOrigin.length == 2) {
            
          //使用工具类Base64Util根据信息头生成文件名后缀
          String suffix = Base64Util.generateFileSuffixByBase64Prefix(sourceOrigin[0]);
            
          if (!StringUtils.isBlank(suffix)) {
            String uuid = UUID.randomUUID().toString().replace("-","");//使用UUID生成文件名
            String backendFileName = uuid + suffix;
              
            //使用工具类Base64Util解码并保存图片文件
            int result = Base64Util.decryptByBase64AndSave(sourceOrigin[1],backendFileName);
              
            if (result == 0) {
              info = new Pair<>(ExceptionInfo.OK,backendFileName);
            }else if (result == 1 ) {
              info = new Pair<>(ExceptionInfo.POST_IMAGE_FOLDER_NOT_CREATED,"");
            }else if (result == 2) {
              info = new Pair<>(ExceptionInfo.POST_IMAGE_CONTENT_EMPTY,"");
            }else if (result == 3) {
              info = new Pair<>(ExceptionInfo.POST_IMAGE_STORE_PATH_NOT_FOUND,"");
            }
          }
        }
        return info;
      }
    

    3.工具类Base64Util的实现

    /**
     * Base64图片解码工具类
     *
     * <p>
     * 用于解析前端发送的Base64图片数据,并保存到程序根目录Base64Decoded/文件夹下
     * </p>
     *
     * @author Tars
     * @since 2021-5-2
     */
    public class Base64Util {
    
        private static boolean isFolderExist;
        private static String folderName="static";//存放在根目录该文件夹下
        private static Map<String,String> mimeTypeMap;
    
        //初始化信息头与文件名后缀的映射
        static {
            Map<String,String> typeMap = new HashMap<>();
            typeMap.put("data:image/png;base64",".png");//png格式图片
            typeMap.put("data:image/jpeg;base64",".jpeg");//jpeg格式图片
            typeMap.put("data:image/gif;base64",".gif");//gif格式图片
            typeMap.put("data:image/bmp;base64",".bmp");//bmp格式图片
            typeMap.put("data:image/x-icon;base64",".ico");//ico格式图片
            setMimeTypeMap(typeMap);
            
            folderName = Paths.get(System.getProperty("user.dir"),folderName).toString();
            File file = new File(folderName);
            
            //存放创建图片的文件夹,存放在根目录static文件夹下
            if (!file.exists()) {
                isFolderExist = file.mkdir();
                if (isFolderExist) {
                    System.out.println("文件夹创建成功");
                }else {
                    System.out.println("文件夹创建失败");
                }
            }else {
                isFolderExist = true;
            }
        }
    
        
        public static String getFolderName(){
            return Base64Util.folderName;
        }
        
        /**
         * 解码Base64字符串,并以指定文件名存储到Base64图片存储位置
         *
         * @param source   要解码的Base64字符串
         * @param saveName 存储文件名
         * @return the int 结果代码:0:成功,1:文件夹未创建,:2:Base64字符串或存储文件名为空
         *                      ,3:未找到存储位置,4:文件写入失败
         */
        public static int decryptByBase64AndSave(String source,String saveName) {
            int result = 0;
            if (!isFolderExist) {
             result = 1;
            }else if (StringUtils.isBlank(source) || StringUtils.isBlank(saveName)) {
                result = 2;
            }else {
                Decoder decoder = Base64.getDecoder();//获取解码器
                try (OutputStream outputStream = new FileOutputStream(Paths.get(folderName,saveName).toString())) {
                    byte[] bytes = decoder.decode(source);//使用解码器解码Base64字符串为字节数组
                    for (int i = 0;i < bytes.length;i++) {//矫正偏移
                        if (bytes[i] < 0) {
                            bytes[i] += 256;
                        }
                    }
                    outputStream.write(bytes);//写入文件
                }catch (FileNotFoundException e) {
                    result = 3;
                    e.printStackTrace();
                }catch (IOException e) {
                    result = 4;
                    e.printStackTrace();
                }
            }
            return result;
        }
    
        /**
         * 根据Base64字符串前缀生成文件后缀名
         *
         * @param prefix Base64字符串前缀,类似data:image/png;base64
         * @return 文件后缀名
         */
        public static String generateFileSuffixByBase64Prefix(String prefix) {
            String suffix = "";
            if (prefix != null) {
                if(mimeTypeMap.containsKey(prefix)){
                    suffix = mimeTypeMap.get(prefix);
                }
                for (Map.Entry<String,String> mimePair : mimeTypeMap.entrySet()) {
                    if (prefix.equals(mimePair.getKey())) {
                        suffix = mimePair.getValue();
                        break;
                    }
                }
            }
            return suffix;
        }
    
        public static void setMimeTypeMap(Map<String, String> mimeTypeMap) {
            Base64Util.mimeTypeMap = mimeTypeMap;
        }
    }
    
  • 相关阅读:
    数据库读现象 数据库锁
    Mysql索引
    视图 触发器 内置函数 流程控制 事务 存储过程
    pymysql模块
    表相关操作
    初识数据库
    python 进程
    python常见错误和异常
    python课程设计--学生管理系统
    python-类的多态的理解
  • 原文地址:https://www.cnblogs.com/tarsss/p/14944578.html
Copyright © 2011-2022 走看看