首页 > 代码库 > [Bootstrap-插件使用]Jcrop+fileinput组合实现头像上传功能

[Bootstrap-插件使用]Jcrop+fileinput组合实现头像上传功能

很久没有更新博客了,再不写点东西都烂了。

这次更新一个小内容,是两个插件的组合使用,实现头像上传功能。

业务需求:


  • 头像上传功能,要对上传的文件进行剪切,且保证头像到服务器时必须是正方形的。
  • 优化<input type="file">的显示样式,基础的样式实在太难看了。
  • 上传的头像需要进行质量压缩跟大小裁剪,以减缓浏览器的压力。

成果预览:

技术分享

 

使用到的技术插件


  • Jcrop:用于前端“裁剪”图片

  • bootstrap-fileinput:用于前端优化上传控件样式
  • ARTtemplate:JS版的JSTL?反正就是一个腾讯的模板化插件,很好用,真心。
  • bootstrap-sco.modal.js:这个是bootstrap的一个模态插件

  • SpringMVC:使用框架自带的MultipartFile来获取文件(效率能够大大的提高)

  • Image:这个是Java的内置类,用于处理图片很方便。

原理说明


 

  首先是Jcrop这个前端JS插件,这个插件很好用,其实在各大网站中也很常见,先上图:

技术分享

没错,这个一脸懵逼的就是我。。。

  说说原理,实际上,Jcrop并没有在客户端帮我们把图片进行裁剪,只是收集了用户的“裁剪信息”,然后传到后端,最后的裁剪和压缩,还是要依靠服务器上的代码来进行。

  我们可以看到这个插件在图片上显示出了8个控制点,让用户选择裁剪区域,当用户选择成功后,会自动的返回“裁剪信息”,所谓的裁剪信息,其实就是选框的左上角原点坐标,及裁剪框的宽度和高度,通过这四个值,在后端就可以进行裁剪工作了。

  但是我们要注意,用户在上传图片的时候,长度宽度都是不规则的,当然我们可以用bootstap-fileinput这个插件去限制用户只能上传指定宽高的图片,但这就失去了我们“裁剪”的意义,而且用户的体验就非常差劲。然而jcrop所返回的坐标值及宽高,并不是基于所上传图片自身的像素,而是如图中所示,是外层DIV的宽高。举一个例子,上图我实际放入的个人照片宽度是852px,但是Jcrop的截取宽度是312px,这个312px并不是真正图片上的实际宽度,是经过缩放后的宽度,所以我们后端一定需要重新对这个312px进行一次还原,还原到照片实际比例的宽度。

  好啦,原理就是这样子。接下来,就是上代码了。

HTML


 

 1 <script id="portraitUpload" type="text/html">
 2     <div style="padding: 10px 20px">
 3         <form role="form" enctype="multipart/form-data" method="post">
 4             <div class="embed-responsive embed-responsive-16by9">
 5                 <div class="embed-responsive-item pre-scrollable">
 6                     <img alt="" src="${pageContext.request.contextPath}/img/showings.jpg" id="cut-img"
 7                          class="img-responsive img-thumbnail"/>
 8                 </div>
 9             </div>
10             <div class="white-divider-md"></div>
11             <input type="file" name="imgFile" id="fileUpload"/>
12             <div class="white-divider-md"></div>
13             <div id="alert" class="alert alert-danger hidden" role="alert"></div>
14             <input type="hidden" id="x" name="x"/>
15             <input type="hidden" id="y" name="y"/>
16             <input type="hidden" id="w" name="w"/>
17             <input type="hidden" id="h" name="h"/>
18         </form>
19     </div>
20 </script>

  这个就是一个ArtTemplate的模板代码,就写在</body>标签上方就行了,因为text/html这个类型,不会被识别,所以实际上用Chrome调试就可以看得到,前端用户是看不到这段代码的。

  简单解释一下这个模板,这个模板是我最后放入模态窗口时用的模板,就是把这段代码,直接丢进模态弹出来的内容部分。因为是文件上传,自然需要套一个<form>标签,然后必须给form标签放入 enctype="multipart/form-data",否则后端Spring就无法获取这个文件。

  "embed-responsive embed-responsive-16by9"这个类就是用来限制待编辑图片加载后的宽度大小,值得注意的是,我在其内种,加了一个

<div class="embed-responsive-item pre-scrollable">

  pre-scrollable这个类,会让加载的图片不会因为太大而“变形”,因为我外层通过embed-responsive-16by9限制死了图片的宽高,图片本身又加了img-responsive这个添加响应式属性的类,为了防止图片缩放,导致截图障碍,所以就给内层加上pre-scrollable,这个会给图片这一层div加上滚动条,如果图片高度太高,超过了外层框,则会出现滚动条,而这不会影响图片截取,同时又保证了模态窗口不会“太长”,导致体验糟糕(尤其在移动端)。

  底下四个隐藏域相信大家看他们的name值也就知道个大概,这个就是用于存放Jcrop截取时所产生的原点坐标和截取宽高的值。

JS


$(document).ready(function () {
    new PageInit().init();
});


function PageInit() {
    var api = null;
    var _this = this;
    this.init = function () {
        $("[name=‘upload‘]").on(‘click‘, this.portraitUpload)
    };


    this.portraitUpload = function () {
        var model = $.scojs_modal({
                title: ‘头像上传‘,
                content: template(‘portraitUpload‘),
                onClose: refresh
            }
        );
        model.show();
        var fileUp = new FileUpload();
        var portrait = $(‘#fileUpload‘);
        var alert = $(‘#alert‘);
        fileUp.portrait(portrait, ‘/file/portrait‘, _this.getExtraData);
        portrait.on(‘change‘, _this.readURL);
        portrait.on(‘fileuploaderror‘, function (event, data, msg) {
            alert.removeClass(‘hidden‘).html(msg);
            fileUp.fileinput(‘disable‘);
        });
        portrait.on(‘fileclear‘, function (event) {
            alert.addClass(‘hidden‘).html();
        });
        portrait.on(‘fileloaded‘, function (event, file, previewId, index, reader) {
            alert.addClass(‘hidden‘).html();
        });
        portrait.on(‘fileuploaded‘, function (event, data) {
            if (!data.response.status) {
                alert.html(data.response.message).removeClass(‘hidden‘);
            }
        })
    };

    this.readURL = function () {
        var img = $(‘#cut-img‘);
        var input = $(‘#fileUpload‘);
        if (input[0].files && input[0].files[0]) {
            var reader = new FileReader();
            reader.readAsDataURL(input[0].files[0]);
            reader.onload = function (e) {
                img.removeAttr(‘src‘);
                img.attr(‘src‘, e.target.result);
                img.Jcrop({
                    setSelect: [20, 20, 200, 200],
                    handleSize: 10,
                    aspectRatio: 1,
                    onSelect: updateCords
                }, function () {
                    api = this;
                });
            };
            if (api != undefined) {
                api.destroy();
            }
        }
        function updateCords(obj) {
            $("#x").val(obj.x);
            $("#y").val(obj.y);
            $("#w").val(obj.w);
            $("#h").val(obj.h);
        }
    };

    this.getExtraData = http://www.mamicode.com/function () {
        return {
            sw: $(‘.jcrop-holder‘).css(‘width‘),
            sh: $(‘.jcrop-holder‘).css(‘height‘),
            x: $(‘#x‘).val(),
            y: $(‘#y‘).val(),
            w: $(‘#w‘).val(),
            h: $(‘#h‘).val()
        }
    }
}

  这个JS是上传页面的相关逻辑。会JS的人都看得懂它的意义,我就简单说一下几个事件的意义:

1  portrait.on(‘fileuploaderror‘, function (event, data, msg) {
2             alert.removeClass(‘hidden‘).html(msg);
3             fileUp.fileinput(‘disable‘);
4         });

  这个事件,是用于bootstrap-fileinput插件在校验文件格式、文件大小等的时候,如果不符合我们的要求,则会对前面HTML代码中有一个

<div id="alert" class="alert alert-danger hidden" role="alert"></div>

  进行一些错误信息的显示操作。

1 portrait.on(‘fileclear‘, function (event) {
2             alert.addClass(‘hidden‘).html();
3         });

  这部分代码,是当文件移除时,隐藏错误信息提示区,以及清空内容,当然这是符合我们的业务逻辑的。

1  portrait.on(‘fileloaded‘, function (event, file, previewId, index, reader) {
2             alert.addClass(‘hidden‘).html();
3         });

  这部分代码是当选择文件时(此时还没进行文件校验),隐藏错误信息,清空错误内容,这么做是为了应对如果上一次文件校验时有错误,而重新选择文件时,肯定要清空上一次的错误信息,再显示本次的错误信息。

1 portrait.on(‘fileuploaded‘, function (event, data) {
2             if (!data.response.status) {
3                 alert.html(data.response.message).removeClass(‘hidden‘);
4             }
5         })

  这部分是当文件上传后,后端如果返回了错误信息,则需要进行相关的提示信息处理。

 1  this.getExtraData = http://www.mamicode.com/function () {
 2         return {
 3             sw: $(‘.jcrop-holder‘).css(‘width‘),
 4             sh: $(‘.jcrop-holder‘).css(‘height‘),
 5             x: $(‘#x‘).val(),
 6             y: $(‘#y‘).val(),
 7             w: $(‘#w‘).val(),
 8             h: $(‘#h‘).val()
 9         }
10     }

  这部分代码是获取上传文件时,附带需要发往后端的参数,这里面可以看到,x、y自然是Jcrop截取时,选框的左上角原点坐标,w、h自然就是截取的宽高,但是刚才我说了,这个是经过缩放后的宽高,不是依据图片实际像素的宽高。而sw、sh代表的是scaleWidth、scaleHeight,就是缩放宽高的意思。这个.jcrop-holder的对象是当Jcrop插件启用后,加载的图片外层容器的对象,只需要获取这个对象的宽高,就是图片被压缩的宽高,但是因为我限制了图片的宽度和高度,宽度的比例是定死的(不是宽高定死,只是比例定死,bootstrap本身就是响应式框架,所以不能单纯的说宽高定死,宽高会随着使用终端的变化而变化),高度是根据宽度保持16:4,可是我又加了pre-scrollable这个类让图片过高时以滚动条的方式不破坏外层容器的高度,所以我们实际能拿来计算缩放比例的,是宽度,而不是高度,但是这里我一起传,万一以后有其他的使用场景,要以高度为准也说不定。

  好了,然后我需要贴上bootstrap-fileinput插件的配置代码:

 1 this.portrait = function (target, uploadUrl, data) {
 2         target.fileinput({
 3             language: ‘zh‘, //设置语言
 4             maxFileSize: 2048,//文件最大容量
 5             uploadExtraData: data,//上传时除了文件以外的其他额外数据
 6             showPreview: false,//隐藏预览
 7             uploadAsync: true,//ajax同步
 8             dropZoneEnabled: false,//是否显示拖拽区域
 9             uploadUrl: uploadUrl, //上传的地址
10             allowedFileExtensions: [‘jpg‘],//接收的文件后缀
11             showUpload: true, //是否显示上传按钮
12             showCaption: true,//是否显示标题
13             browseClass: "btn btn-primary", //按钮样式
14             previewFileIcon: "<i class=‘glyphicon glyphicon-king‘></i>",
15             ajaxSettings: {//这个是因为我使用了SpringSecurity框架,有csrf跨域提交防御,所需需要设置这个值
16                 beforeSend: function (xhr) {
17                     xhr.setRequestHeader(header, token);
18                 }
19             }
20         });
21     }

  这个代码有写了注释,我就不多解释了。关于Ajax同步,是因为我个人认为,上传文件这个还是做成同步比较好,等文件上传完成后,js代码才能继续执行下去。因为文件上传毕竟是一个耗时的工作,有的逻辑又确实需要当文件上传成功以后才执行,比如刷新页面,所以为了避免出现问题,还是做成同步的比较好。还有就是去掉预览,用过bootstrap-fileinput插件的都知道,这个插件的图片预览功能很强大,甚至可以单独依靠这个插件来制作相册管理。但是因为我们这次要结合Jcrop,所以要割掉这部分功能。

SpringMVC-Controller获取文件


 

 1 @ResponseBody
 2     @RequestMapping(value = "http://www.mamicode.com/portrait", method = {RequestMethod.POST})
 3     public JsonResult upload(HttpServletRequest request) throws Exception {
 4         Integer x = Integer.parseInt(MyStringTools.checkParameter(request.getParameter("x"), "图片截取异常:X!"));
 5         Integer y = Integer.parseInt(MyStringTools.checkParameter(request.getParameter("y"), "图片截取异常:Y!"));
 6         Integer w = Integer.parseInt(MyStringTools.checkParameter(request.getParameter("w"), "图片截取异常:W!"));
 7         Integer h = Integer.parseInt(MyStringTools.checkParameter(request.getParameter("h"), "图片截取异常:H!"));
 8         String scaleWidthString = MyStringTools.checkParameter(request.getParameter("sw"), "图片截取异常:SW!");
 9         int swIndex = scaleWidthString.indexOf("px");
10         Integer sw = Integer.parseInt(scaleWidthString.substring(0, swIndex));
11         String scaleHeightString = MyStringTools.checkParameter(request.getParameter("sh"), "图片截取异常:SH!");
12         int shIndex = scaleHeightString.indexOf("px");
13         Integer sh = Integer.parseInt(scaleHeightString.substring(0, shIndex));
14 
15 
16         //获取用户ID用于指向对应文件夹
17         SysUsers sysUsers = HttpTools.getSessionUser(request);
18         int userID = sysUsers.getUserId();
19         //获取文件路径
20         String filePath = FileTools.getPortraitPath(userID);
21 
22         CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(
23                 request.getSession().getServletContext());
24 
25         String path;
26         //检查form中是否有enctype="multipart/form-data"
27         if (multipartResolver.isMultipart(request)) {
28             //将request变成多部分request
29             MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
30             //获取multiRequest 中所有的文件名
31             Iterator iterator = multiRequest.getFileNames();
32             while (iterator.hasNext()) {
33                 //一次遍历所有文件
34                 MultipartFile multipartFile = multiRequest.getFile(iterator.next().toString());
35                 if (multipartFile != null) {
36                     String[] allowSuffix = {".jpg",".JPG"};
37                     if (!FileTools.checkSuffix(multipartFile.getOriginalFilename(), allowSuffix)) {
38                         throw new BusinessException("文件后缀名不符合要求!");
39                     }
40                     path = filePath + FileTools.getPortraitFileName(multipartFile.getOriginalFilename());
41                     //存入硬盘
42                     multipartFile.transferTo(new File(path));
43                     //图片截取
44                     if (FileTools.imgCut(path, x, y, w, h, sw, sh)) {
45                         CompressTools compressTools = new CompressTools();
46                         if (compressTools.simpleCompress(new File(path))) {
47                             return JsonResult.success(FileTools.filePathToSRC(path, FileTools.IMG));
48                         } else {
49                             return JsonResult.error("图片压缩失败!请重新上传!");
50                         }
51                     } else {
52                         return JsonResult.error("图片截取失败!请重新上传!");
53                     }
54                 }
55             }
56         }
57         return JsonResult.error("图片获取失败!请重新上传!");
58     }

Image图片切割


 

 1 /**
 2      * 截图工具,根据截取的比例进行缩放裁剪
 3      *
 4      * @param path        图片路径
 5      * @param zoomX       缩放后的X坐标
 6      * @param zoomY       缩放后的Y坐标
 7      * @param zoomW       缩放后的截取宽度
 8      * @param zoomH       缩放后的截取高度
 9      * @param scaleWidth  缩放后图片的宽度
10      * @param scaleHeight 缩放后的图片高度
11      * @return 是否成功
12      * @throws Exception 任何异常均抛出
13      */
14     public static boolean imgCut(String path, int zoomX, int zoomY, int zoomW,
15                                  int zoomH, int scaleWidth, int scaleHeight) throws Exception {
16         Image img;
17         ImageFilter cropFilter;
18         BufferedImage bi = ImageIO.read(new File(path));
19         int fileWidth = bi.getWidth();
20         int fileHeight = bi.getHeight();
21         double scale = (double) fileWidth / (double) scaleWidth;
22 
23         double realX = zoomX * scale;
24         double realY = zoomY * scale;
25         double realW = zoomW * scale;
26         double realH = zoomH * scale;
27 
28         if (fileWidth >= realW && fileHeight >= realH) {
29             Image image = bi.getScaledInstance(fileWidth, fileHeight, Image.SCALE_DEFAULT);
30             cropFilter = new CropImageFilter((int) realX, (int) realY, (int) realW, (int) realH);
31             img = Toolkit.getDefaultToolkit().createImage(
32                     new FilteredImageSource(image.getSource(), cropFilter));
33             BufferedImage bufferedImage = new BufferedImage((int) realW, (int) realH, BufferedImage.TYPE_INT_RGB);
34             Graphics g = bufferedImage.getGraphics();
35             g.drawImage(img, 0, 0, null);
36             g.dispose();
37             //输出文件
38             return ImageIO.write(bufferedImage, "JPEG", new File(path));
39         } else {
40             return true;
41         }
42     }

  缩放比例scale一定要用double,并且宽高也要转换成double后再相除,否则会变成求模运算,这样会降低精度,别小看这里的精度下降,最终的截图效果根据图片的缩放程度,误差可是有可能被放大的很离谱的。

 

图片压缩


  1 package com.magic.rent.tools;
  2 
  3 /**
  4  * 知识产权声明:本文件自创建起,其内容的知识产权即归属于原作者,任何他人不可擅自复制或模仿.
  5  * 创建者: wu   创建时间: 2016/12/15
  6  * 类说明: 缩略图类(通用) 本java类能将jpg、bmp、png、gif图片文件,进行等比或非等比的大小转换。 具体使用方法
  7  * 更新记录:
  8  */
  9 
 10 import com.magic.rent.exception.custom.BusinessException;
 11 import com.sun.image.codec.jpeg.JPEGCodec;
 12 import com.sun.image.codec.jpeg.JPEGImageEncoder;
 13 import org.slf4j.Logger;
 14 import org.slf4j.LoggerFactory;
 15 
 16 import javax.imageio.ImageIO;
 17 import java.awt.*;
 18 import java.awt.image.BufferedImage;
 19 import java.io.File;
 20 import java.io.FileOutputStream;
 21 
 22 public class CompressTools {
 23     private File file; // 文件对象
 24     private String inputDir; // 输入图路径
 25     private String outputDir; // 输出图路径
 26     private String inputFileName; // 输入图文件名
 27     private String outputFileName; // 输出图文件名
 28     private int outputWidth = 100; // 默认输出图片宽
 29     private int outputHeight = 100; // 默认输出图片高
 30     private boolean proportion = true; // 是否等比缩放标记(默认为等比缩放)
 31     private static Logger logger = LoggerFactory.getLogger(CompressTools.class);
 32 
 33 
 34     public CompressTools() {
 35     }
 36 
 37     public CompressTools(boolean proportion) {
 38         this.proportion = proportion;
 39     }
 40 
 41     /**
 42      * 设置输入参数
 43      *
 44      * @param inputDir
 45      * @param inputFileName
 46      * @return
 47      */
 48     public CompressTools setInputInfo(String inputDir, String inputFileName) {
 49         this.inputDir = inputDir;
 50         this.inputFileName = inputFileName;
 51         return this;
 52     }
 53 
 54     /**
 55      * 设置输出参数
 56      *
 57      * @param outputDir
 58      * @param outputFileName
 59      * @param outputHeight
 60      * @param outputWidth
 61      * @param proportion
 62      * @return
 63      */
 64     public CompressTools setOutputInfo(String outputDir, String outputFileName, int outputHeight, int outputWidth, boolean proportion) {
 65         this.outputDir = outputDir;
 66         this.outputFileName = outputFileName;
 67         this.outputWidth = outputWidth;
 68         this.outputHeight = outputHeight;
 69         this.proportion = proportion;
 70         return this;
 71     }
 72 
 73 
 74     // 图片处理
 75     public boolean compress() throws Exception {
 76         //获得源文件
 77         file = new File(inputDir);
 78         if (!file.exists()) {
 79             throw new BusinessException("文件不存在!");
 80         }
 81         Image img = ImageIO.read(file);
 82         // 判断图片格式是否正确
 83         if (img.getWidth(null) == -1) {
 84             System.out.println(" can‘t read,retry!" + "<BR>");
 85             return false;
 86         } else {
 87             int newWidth;
 88             int newHeight;
 89             // 判断是否是等比缩放
 90             if (this.proportion) {
 91                 // 为等比缩放计算输出的图片宽度及高度
 92                 double rate1 = ((double) img.getWidth(null)) / (double) outputWidth + 0.1;
 93                 double rate2 = ((double) img.getHeight(null)) / (double) outputHeight + 0.1;
 94                 // 根据缩放比率大的进行缩放控制
 95                 double rate = rate1 > rate2 ? rate1 : rate2;
 96                 newWidth = (int) (((double) img.getWidth(null)) / rate);
 97                 newHeight = (int) (((double) img.getHeight(null)) / rate);
 98             } else {
 99                 newWidth = outputWidth; // 输出的图片宽度
100                 newHeight = outputHeight; // 输出的图片高度
101             }
102             long start = System.currentTimeMillis();
103             BufferedImage tag = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
104             /*
105              * Image.SCALE_SMOOTH 的缩略算法 生成缩略图片的平滑度的
106              * 优先级比速度高 生成的图片质量比较好 但速度慢
107              */
108             tag.getGraphics().drawImage(img.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH), 0, 0, null);
109             FileOutputStream out = new FileOutputStream(outputDir);
110 
111             // JPEGImageEncoder可适用于其他图片类型的转换
112             JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
113             encoder.encode(tag);
114             out.close();
115             long time = System.currentTimeMillis() - start;
116             logger.info("[输出路径]:" + outputDir + "\t[图片名称]:" + outputFileName + "\t[压缩前大小]:" + getPicSize() + "\t[耗时]:" + time + "毫秒");
117             return true;
118         }
119     }
120 
121 
122     /**
123      * 简单压缩方法,压缩后图片将直接覆盖源文件
124      *
125      * @param images
126      * @return
127      * @throws Exception
128      */
129     public boolean simpleCompress(File images) throws Exception {
130         setInputInfo(images.getPath(), images.getName());
131         setOutputInfo(images.getPath(), images.getName(), 300, 300, true);
132         return compress();
133     }
134 
135     /**
136      * 获取图片大小,单位KB
137      *
138      * @return
139      */
140     private String getPicSize() {
141         return file.length() / 1024 + "KB";
142     }
143 
144     public static void main(String[] args) throws Exception {
145         CompressTools compressTools = new CompressTools();
146         compressTools.setInputInfo("/Users/wu/Downloads/background.jpg", "background.jpg");
147         compressTools.setOutputInfo("/Users/wu/Downloads/background2.jpg", "background2.jpg", 633, 1920, false);
148         compressTools.compress();
149     }
150 
151 }

  我专门把图片压缩写成了一个类。

  其中可以看到一些关于文件路径的方法,其实没有什么特别的,就是截取后缀获取路径之类的,我这边也贴出来吧,免得有些朋友看的云里雾里的。

  1 package com.magic.rent.tools;
  2 
  3 import com.magic.rent.exception.custom.BusinessException;
  4 
  5 import javax.imageio.ImageIO;
  6 import java.awt.*;
  7 import java.awt.image.BufferedImage;
  8 import java.awt.image.CropImageFilter;
  9 import java.awt.image.FilteredImageSource;
 10 import java.awt.image.ImageFilter;
 11 import java.io.File;
 12 import java.util.ArrayList;
 13 
 14 /**
 15  * 知识产权声明:本文件自创建起,其内容的知识产权即归属于原作者,任何他人不可擅自复制或模仿.
 16  * 创建者: wu   创建时间: 2016/11/25
 17  * 类说明:
 18  * 更新记录:
 19  */
 20 public class FileTools {
 21 
 22     public static final int IMG = 1;
 23 
 24     /**
 25      * 获取项目根目录
 26      *
 27      * @return 根目录
 28      */
 29     public static String getWebRootPath() {
 30         return System.getProperty("web.root");
 31     }
 32 
 33     /**
 34      * 获取头像目录,若不存在则直接创建一个
 35      *
 36      * @param userID 用户ID
 37      * @return
 38      */
 39     public static String getPortraitPath(int userID) {
 40         String realPath = getWebRootPath() + "img/portrait/" + userID + "/";
 41         File file = new File(realPath);
 42         //判断文件夹是否存在,不存在则创建一个
 43         if (!file.exists() || !file.isDirectory()) {
 44             if (!file.mkdirs()) {
 45                 throw new BusinessException("创建头像文件夹失败!");
 46             }
 47         }
 48         return realPath;
 49     }
 50 
 51     /**
 52      * 重命名头像文件
 53      *
 54      * @param fileName 文件名
 55      * @return
 56      */
 57     public static String getPortraitFileName(String fileName) {
 58         // 获取文件后缀
 59         String suffix = getSuffix(fileName);
 60         return "portrait" + suffix;
 61     }
 62 
 63     /**
 64      * 判断文件后缀是否符合要求
 65      *
 66      * @param fileName    文件名
 67      * @param allowSuffix 允许的后缀集合
 68      * @return
 69      * @throws Exception
 70      */
 71     public static boolean checkSuffix(String fileName, String[] allowSuffix) throws Exception {
 72         String fileExtension = getSuffix(fileName);
 73         boolean flag = false;
 74         for (String extension : allowSuffix) {
 75             if (fileExtension.equals(extension)) {
 76                 flag = true;
 77             }
 78         }
 79         return flag;
 80     }
 81 
 82 
 83     public static String getSuffix(String fileName) {
 84         return fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
 85     }
 86 
 87     /**
 88      * 将文件地址转成链接地址
 89      *
 90      * @param filePath 文件路径
 91      * @param fileType 文件类型
 92      * @return
 93      */
 94     public static String filePathToSRC(String filePath, int fileType) {
 95         String hrefhttp://www.mamicode.com/= "";
 96         if (null != filePath && !filePath.equals("")) {
 97             switch (fileType) {
 98                 case IMG:
 99                     if (filePath.contains("/img/")) {
100                         int index = filePath.indexOf("/img/");
101                         href =http://www.mamicode.com/ filePath.substring(index);
102                     } else {
103                         hrefhttp://www.mamicode.com/= "";
104                     }
105                     return href;
106             }
107         }
108         return href;
109     }
110 
111     /**
112      * 获取指定文件或文件路径下的所有文件清单
113      *
114      * @param fileOrPath 文件或文件路径
115      * @return
116      */
117     public static ArrayList<File> getListFiles(Object fileOrPath) {
118         File directory;
119         if (fileOrPath instanceof File) {
120             directory = (File) fileOrPath;
121         } else {
122             directory = new File(fileOrPath.toString());
123         }
124 
125         ArrayList<File> files = new ArrayList<File>();
126 
127         if (directory.isFile()) {
128             files.add(directory);
129             return files;
130         } else if (directory.isDirectory()) {
131             File[] fileArr = directory.listFiles();
132             if (null != fileArr && fileArr.length != 0) {
133                 for (File fileOne : fileArr) {
134                     files.addAll(getListFiles(fileOne));
135                 }
136             }
137         }
138 
139         return files;
140     }
141 
142 
143     /**
144      * 截图工具,根据截取的比例进行缩放裁剪
145      *
146      * @param path        图片路径
147      * @param zoomX       缩放后的X坐标
148      * @param zoomY       缩放后的Y坐标
149      * @param zoomW       缩放后的截取宽度
150      * @param zoomH       缩放后的截取高度
151      * @param scaleWidth  缩放后图片的宽度
152      * @param scaleHeight 缩放后的图片高度
153      * @return 是否成功
154      * @throws Exception 任何异常均抛出
155      */
156     public static boolean imgCut(String path, int zoomX, int zoomY, int zoomW,
157                                  int zoomH, int scaleWidth, int scaleHeight) throws Exception {
158         Image img;
159         ImageFilter cropFilter;
160         BufferedImage bi = ImageIO.read(new File(path));
161         int fileWidth = bi.getWidth();
162         int fileHeight = bi.getHeight();
163         double scale = (double) fileWidth / (double) scaleWidth;
164 
165         double realX = zoomX * scale;
166         double realY = zoomY * scale;
167         double realW = zoomW * scale;
168         double realH = zoomH * scale;
169 
170         if (fileWidth >= realW && fileHeight >= realH) {
171             Image image = bi.getScaledInstance(fileWidth, fileHeight, Image.SCALE_DEFAULT);
172             cropFilter = new CropImageFilter((int) realX, (int) realY, (int) realW, (int) realH);
173             img = Toolkit.getDefaultToolkit().createImage(
174                     new FilteredImageSource(image.getSource(), cropFilter));
175             BufferedImage bufferedImage = new BufferedImage((int) realW, (int) realH, BufferedImage.TYPE_INT_RGB);
176             Graphics g = bufferedImage.getGraphics();
177             g.drawImage(img, 0, 0, null);
178             g.dispose();
179             //输出文件
180             return ImageIO.write(bufferedImage, "JPEG", new File(path));
181         } else {
182             return true;
183         }
184     }
185 }

  顺便一提:getWebRootPath这个方法,要生效,必须在Web.xml中做一个配置:

1  <context-param>
2         <param-name>webAppRootKey</param-name>
3         <param-value>web.root</param-value>
4     </context-param>

  否则是无法动态获取项目的本地路径的。这个配置只要跟在Spring配置后面就行了,应该就不会有什么大碍,其实就是获取本地路径然后设置到系统参数当中。

  好了,这就是整个插件的功能了。

[Bootstrap-插件使用]Jcrop+fileinput组合实现头像上传功能