手把手将基于spring boot的文件上传(含进度、大文件)的功能封装到一个jar包里

本文介绍如何使用SpringBoot实现文件上传功能,包括配置依赖、进度监听、自定义解析器及封装为Jar包的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 现文件上传功能

(1)、建立一个spring boot的工程

单击“Next”,进入工程命名对话框。

单击“Next”,进入工程依赖配置对话框。

选中Spring Web。单击“Next”,进入工程目录设置对话框。

根据自己实际情况将工程保存到某个目录下。单击“Finish”。

(2)、配置POM

手动添加下面的依赖。

<!-- 渲染前端页面 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- 文件上传 -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

thymeleaf是页面渲染框架,因为要写一个表单提交来上传文件。文件上传功能利用apache的commons-fileupload组件,因此要添加它已经它自身的依赖。commons-fileupload组件可以很方便的实现上传进度的监控。

(3)、实现上传功能

建立进度监听类SgFileUploaderListener。

@Component
public class SgFileUploaderListener implements ProgressListener {
    private HttpSession session;

    public SgFileUploaderListener() {
    }

    public void setSession(HttpSession session) {
        this.session = session;
        session.setAttribute("uploadPercent", 0);
    }

    public void update(long pBytesRead, long pContentLength, int pItems) {
        int percent = (int)((double)pBytesRead * 100.0D / (double)pContentLength);
        this.session.setAttribute("uploadPercent", percent);
    }
}

可以看出,文件上传的进度是依靠session来保存和读取的。组件底层会根据上传进程自动调用update函数,而update函数将当前文件的进度值计算出来保存在uploadPercent这个session对象里。

实现含监听进度的自定义CommonsMultipartResolver类

public class SgFileUploaderMultipartResolver extends CommonsMultipartResolver {
    @Autowired
    private SgFileUploaderListener listener;

    public SgFileUploaderMultipartResolver() {
    }

    protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
        String encoding = this.determineEncoding(request);
        FileUpload fileUpload = this.prepareFileUpload(encoding);
        fileUpload.setProgressListener(this.listener);
        this.listener.setSession(request.getSession());

        try {
            List fileItems = ((ServletFileUpload)fileUpload).parseRequest(request);
            return this.parseFileItems(fileItems, encoding);
        } catch (FileUploadException var5) {
            throw new MultipartException("Failed to parse multipart servlet request", var5);
        }
    }
}

Spring boot自身的CommonsMultipartResolver就是用来处理文件上传的,它自身没有进度监听功能,因此我们派生一个子类,然后在parseRequest方法里假如自己的监听对象,从而实现文件上传进度的监视。

文件上传类SgFileUploader

这个类实现2个核心功能,一是处理文件上传,一是获取进度。

     public Map<String, Object> upload(MultipartFile imgFile,
                 HttpServletRequest request,String fileType, String dir,
                 String fileName) throws IOException {
        Map<String, Object> mapRet = new HashMap<String, Object>();
        ServletContext context=request.getSession().getServletContext();

        String pageCtx = filePath;   // 保存上传文件的总路径
        String relPath = context.getRealPath(filePath);
        if (null == imgFile) {
            mapRet.put("code", 1);
            mapRet.put("message", "上传的文件无效.");
            return mapRet;
        }

        StringBuilder tempPath = new StringBuilder(relPath);
        tempPath.append(dir);
        String saveDir = tempPath.toString();
        File folder = new File(saveDir);
        if(!folder.exists()) {
            folder.mkdirs();
        }
        if(!folder.isDirectory() || !folder.canWrite()) {
            mapRet.put("code", 2);
            mapRet.put("message", "文件夹不存在或者没有读写权限.");
            return mapRet;
        }

        String fl = imgFile.getOriginalFilename();
        String fileExt = fl.substring(fl.lastIndexOf(".")+1);
        if(!Arrays.<String>asList(extMap.get(fileType).split(",")).contains(fileExt)){
            mapRet.put("error", 4);
            mapRet.put("message", "不允许上传的文件格式:" + extMap.get(fileType));
            return mapRet;
        }
        long maxSize = sizeMap.get(fileType);
        if (imgFile.getSize() > maxSize) {
            mapRet.put("code", 5);
            String size = null;
            if(maxSize < 1024) {
                size = maxSize + "B";
            }
            if(maxSize > 1024 && maxSize < 1024 * 1024){
                size = maxSize/1024 + "KB";
            }
            if(maxSize > 1024 * 1024){
                size = maxSize/(1024 * 1024) + "MB";
            }
            mapRet.put("message", "文件尺寸太大: " + size);
            return mapRet;
        }

        // 如果没有指定文件名,则按随机命名
        if (fileName == null || fileName == "") {
            UUID uuid = UUID.randomUUID();
            fileName = uuid.toString();
            fileName = fileName.replace("-", "");
        }

        tempPath.append("/").append(fileName).append(".").append(fileExt);
        String savePath = tempPath.toString().replaceAll("\\\\",
                "/");

        // 读取上传数据流,并上传
        InputStream src = imgFile.getInputStream();
        OutputStream tarOs = null;
        try {
            File tarFile = new File(savePath);
            tarOs = new FileOutputStream(tarFile);
            byte[] buffer = new byte[4096];
            int n = 0;
            long nRead = 0;
            long totalSize = imgFile.getSize();
            DecimalFormat    df   = new DecimalFormat("######0.00");
            while (-1 != (n = src.read(buffer))) {
                nRead += n;
                double dRate = (double)nRead / totalSize * 100;
                String rate = df.format(dRate);

                tarOs.write(buffer, 0, n);
            }
        } catch (IOException e) {
            mapRet.put("code", 6);
            mapRet.put("message", "Copy File is Fali, Because "+e);
        } finally {
            try {
                if (null != src) {
                    src.close();
                }
                if (null != tarOs) {
                    tarOs.close();
                }
            } catch (IOException e) {
                mapRet.put("code", 7);
                mapRet.put("message", "Close Stream is Fail, Because "+e);
            }
        }

        // 返回上传文件的完整路径
        StringBuilder url = new StringBuilder(pageCtx);
        url.append(dir).append("/").append(fileName).append(".").append(fileExt);
        mapRet.put("code", 0);    // 返回0表示没有错误
        mapRet.put("url", url.toString());
        return mapRet;
    }

这里fileType参数指明了文件类型,不同的类型可以限制不同的上传文件大小。dir参数指明文件保存的子文件夹。

    public String getProgress(HttpServletRequest request){
        HttpSession se = request.getSession();
        Object o = se.getAttribute("uploadPercent");
        String rateRet = "";
        if (o != null) {
            rateRet = o.toString();
        }
        return rateRet;
    }

获取进度就是读取uploadPercent这个session值。

(4)、测试上传功能

在templates目录下建立html测试页面upload.html,并建立一个controller来测试上传类的接口。

 TestController类代码如下:

@Controller
@RequestMapping(value = "/main")
public class TestController {

    @RequestMapping("/test") 
    public String test(){
        return "upload";
    }

    @RequestMapping("/upload")
    @ResponseBody
    public Map<String, Object> upload(MultipartFile file, HttpServletRequest request,
                                      String fileType, String dir) {
        Map<String, Object> mapRet = new HashMap<String, Object>();
        SgFileUploader sfu = new SgFileUploader();
        try {
            mapRet = sfu.upload(file, request, fileType, dir, "");
        }
        catch (Exception e){
            mapRet.put("code", -1);
            mapRet.put("message", "上传文件失败:"  + e.toString());
            e.printStackTrace();
        }
        return  mapRet;
    }
}

建立配置类SgUploaderMultipartConfig,告诉spring boot,处理表单交给我们自定义的SgFileUploaderMultipartResolver来处理。

@Configuration
public class SgUploaderMultipartConfig {

    @Bean(name = "multipartResolver")
    public MultipartResolver multipartResolver()
    {
        return new SgFileUploaderMultipartResolver();
    }
}

   为了限制总的上传文件的大小,在程序启动类SguploaderTestApplication添加配置函数。

@SpringBootApplication(scanBasePackages = "com")
public class SguploaderTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(SguploaderTestApplication.class, args);
    }

    /**
     * 文件上传配置
     * @return
     */
    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        //设置内存阀值,超过后写入临时文件
        factory.setFileSizeThreshold(DataSize.parse("10240KB"));  // 10M
        //文件最大
        factory.setMaxFileSize(DataSize.parse("102400KB")); //100MB
        /// 设置总上传数据总大小
        factory.setMaxRequestSize(DataSize.parse("1024000KB"));   // 1G
        return factory.createMultipartConfig();
    }
}

    注意,如果启动工程找不到某些类,写上scanBasePackages = "com",这样凡是com下的包都可以扫描到。

启动测试接口,测试文件上传。

2、将文件上传相关类导出到jar包

为了让别的项目能更加简洁方便的调用上传功能,现在将相关类导出到一个jar包,这里以idea为例。

先编译下工程。如果没有错误,则单击File->Project Structure。单击Artifacts,再单击三米高的+号,新建一个空的jar包。

然后取号要导出的包的名称,并按包的文件夹层次关系建立好相关目录。

目录层次要和包的目录层次一样,方法就是单击中间工具栏的第一个建立文件夹图标。如果目录层次不一样,导出去的jar包被别人调用时可能会出现找不到类的情况。

然后单击utils目录,再单击带下拉箭头的+号按钮,将经过编译的相关类文件(扩展名为class)都添加到相应目录。

Idea工程编译后会生成target目录,因此去target目录下找。

添加好后,如下图:

     然后选中jar包文件,点“Create Mainifest…”创建jar包的部署文件。在弹出的对话框里选中一个文件夹,我这里还是选定的target文件夹下的utils文件夹,和class文件一起。

然后单击“Apply”,再单击”OK”。进入到电脑资源管理器刚才选定的目录,手动编辑MANIFEST.MF文件并保存。

Premain-Class就是包的路径。

然后通过菜单Bulid-》Build Artifacts…,弹出编译jar菜单窗口。

选中要打包的jar包名,选中子菜单Build进行打包。打包成功后,默认是在工程目录的out子目录下。

3、建立测试工程调用jar包

新建测试工程sg-file-uploader-test。和建立工程sg-file-uploader类似,也是基于spring boot创建,pom文件,配置类SgUploaderMultipartConfig均一样。

(1)、导入jar包

在工程的src目录下创建lib子目录。将上个工程导出的jar包拷贝进来。然后再pom文件里添加自己的jar包依赖。

     <!-- 自己封装的jar包 -->
        <dependency>
            <groupId>com.wave12</groupId>
            <artifactId>sguploader</artifactId>
            <version>1.1.0</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/lib/sg-file-uploader-1.1.0.jar</systemPath>
        </dependency>

(2)、测试调用jar包功能

效果和上面的工程一样。可以看到,在测试工程的src文件夹,已经看不到utils子包,即上传功能的代码不用呈现在工程代码里,因为它已经封装在一个jar包里,只需要在pom文件里添加好依赖即可。然后在需要调用jar包功能的地方,写好import语句,就可以调用其中的类和方法了。

本教程的完整代码,可以参考这里:

http://kbase.wave12.com/java/doc/detail?id=5d4d29df0d4f4dfa8558b33a9b11795a

封装jar包的目的就是使得现有工程的代码架构更加简洁,调用者也不会不小心改动到jar包里的代码,而调用不受到影响。在企业里,很多公司都会把一些重用度非常高的代码封装成jar包,在多个项目里调用。

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wave12_mp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值