• springboot基础及上传组件封装


    简介

    本文主要以文件上传为demo,介绍了一些 springboot web 开发的入门的技术栈。

    对应刚接触 springboot 的可以参考下。

    主要包括文件md5比对、生成图片缩略图、数据库迁移、文件记录持久化、请求全局异常处理等功能。

    准备工作

    • idea 中创建项目,java8 , springboot 2

    • maven 所需依赖
    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        <groupId>com.bimccgroupId>
        <artifactId>iotartifactId>
        <version>0.0.1-SNAPSHOTversion>
        <name>iotname>
        <description>iotdescription>
    
        <properties>
            <java.version>1.8java.version>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
            <spring-boot.version>2.6.13spring-boot.version>
        properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starterartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            
            <dependency>
                <groupId>org.flywaydbgroupId>
                <artifactId>flyway-coreartifactId>
                <version>5.2.4version>
            dependency>
    
            <dependency>
                <groupId>com.baomidougroupId>
                <artifactId>mybatis-plus-boot-starterartifactId>
                <version>3.5.2version>
            dependency>
    
            <dependency>
                <groupId>com.mysqlgroupId>
                <artifactId>mysql-connector-jartifactId>
                <scope>runtimescope>
            dependency>
    
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-validationartifactId>
                <version>2.7.8version>
            dependency>
    
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
    
            <dependency>
                <groupId>com.google.guavagroupId>
                <artifactId>guavaartifactId>
                <version>20.0version>
            dependency>
    
            
            <dependency>
                <groupId>cn.hutoolgroupId>
                <artifactId>hutool-allartifactId>
                <version>5.4.0version>
            dependency>
    
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-dependenciesartifactId>
                    <version>${spring-boot.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
            dependencies>
        dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.pluginsgroupId>
                    <artifactId>maven-compiler-pluginartifactId>
                    <version>3.8.1version>
                    <configuration>
                        <source>1.8source>
                        <target>1.8target>
                        <encoding>UTF-8encoding>
                    configuration>
                plugin>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                    <version>${spring-boot.version}version>
                    <configuration>
                        <mainClass>com.bimcc.iot.IotApplicationmainClass>
                        <skip>trueskip>
                    configuration>
                    <executions>
                        <execution>
                            <id>repackageid>
                            <goals>
                                <goal>repackagegoal>
                            goals>
                        execution>
                    executions>
                plugin>
            plugins>
        build>
    
    project>
    
    
    • 创建项目目录

    在这里插入图片描述

    目录意义如下:

    请添加图片描述

    • 修改 application.yml 配置:
    
    ---
    # 开发环境的配置
    server:
      port: 9090
    spring:
      controller:
        api-prefix: /api
      flyway:
        enabled: true #开启数据迁移
        table: flyway_schema_history #用于存储迁移历史记录的表名,默认为flyway_schema_history
        baseline-on-migrate: true #当迁移数据库存在但没有元数据的表时,自动执行基准迁移,新建flyway_schema_history表
        locations: classpath:db/migration #数据库迁移脚本的位置,默认为classpath:db/migration,classpath 羡慕resources目录
        clean-disabled: true #用于控制是否禁用 Flyway 的 clean 操作。
      datasource:
        username: root
        password: root
        url: jdbc:mysql://127.0.0.1:3306/java_iot?serverTimezone=GMT%2b8
      config:
        activate:
          on-profile: dev #开发环境
      servlet:
        multipart:
          enabled: true # 允许文件上传
          max-file-size: 20971520 # 单文件最大限制 20M
          max-request-size: 52428800 # 单次请求最大限制 50M
    file:
      upload:
        path: E:\project-java\java-upload  # 文件上传保存服务器绝对路径
        suffix: jpg,jpeg,png,bmp,xls,xlsx,pdf  # 文件上传保存路径
        is-thumb: true  # 是否开启缩略图 true false
        proportion: 5  # 缩略图缩放比例
        path-pattern: uploads  # 访问虚拟目录
    log:
      level: INFO # INFO DEBUG ERROR
    ---
    
    # 当前启用的配置
    spring:
      application:
        name: iot # 应用平台
      profiles:
        active: dev   # 当前环境
    
    • 创建数据表迁移文件

    在这里插入图片描述

    写入以下内容:

    CREATE TABLE sys_file
    (
        id         INT AUTO_INCREMENT COMMENT 'id',
        file_name  VARCHAR(255) NOT NULL COMMENT '文件名称',
        ip         VARCHAR(255) COMMENT '上传ip',
        file_path  VARCHAR(255) NOT NULL COMMENT '文件路径',
        thumb_path  VARCHAR(255) COMMENT '缩略图文件路径',
        file_size  INT COMMENT '字节大小',
        file_type  VARCHAR(255) COMMENT '文件类型',
        file_ext   CHAR(36) COMMENT '文件后缀',
        file_md5   VARCHAR(255) COMMENT '文件md5',
        created_at DATETIME COMMENT '创建时间',
        updated_at DATETIME COMMENT '修改时间',
        deleted_at DATETIME COMMENT '删除时间',
        PRIMARY KEY (id)
    ) ENGINE = InnoDB DEFAULT CHARSET = UTF8MB4;
    

    创建上传实体类

    • dto目录 创建 BaseEntity FileEntity 实体类

    BaseEntity 写入以下内容:

    
    // 省略 package import 
    
    @Data
    public class BaseEntity implements Serializable {
    
        @TableId(value = "id",type = IdType.AUTO)
        private Integer id;
    
    
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") //格式化时间,空值不会格式化
        @TableField(value = "created_at",fill = FieldFill.INSERT)
        @JsonProperty("created_at") //json格式化显示字段
        private Date createdAt;
    
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        @TableField(value = "updated_at",fill = FieldFill.INSERT_UPDATE)
        @JsonProperty("updated_at")
        private Date updatedAt;
    
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        @JsonProperty("deleted_at")
        @TableField(value = "deleted_at")
        @TableLogic(value = "null",delval = "now()")
        private Date deletedAt;
    
    }
    
    

    FileEntity 写入以下内容:

    // 省略 package import 
    @Data
    @TableName("sys_file")
    public class FileEntity extends BaseEntity {
        @TableField(value = "file_name")
        private String fileName;
        @TableField(value = "ip")
        private String ip;
        @TableField(value = "file_path")
        private String filePath;
        @TableField(value = "thumb_path")
        private String thumbPath;
        @TableField(value = "file_size")
        private Long fileSize;
        @TableField(value = "file_type")
        private String fileType;
        @TableField(value = "file_ext")
        private String fileExt;
        @TableField(value = "file_md5")
        private String fileMd5;
    }
    
    
    
    • 创建 mapper , 在 mapper 目录创建 FileMapper 接口类
    // 省略 package import 
    
    @Mapper
    public interface FileMapper extends BaseMapper<FileEntity> {
    
        FileEntity queryByMd5(String md5);
    }
    
    
    • 创建 mapper xml 文件。在 resources 目录下面创建 mapper 目录,然后再创建 FileMapper.xml。并写入以下内容
    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.bimcc.iot.mapper.FileMapper">
    
        
        <resultMap id="queryFile" type="com.bimcc.iot.dto.FileEntity">
            <id property="id" column="id"/>
            <result property="fileName" column="file_name"/>
            <result property="ip" column="ip"/>
            <result property="filePath" column="file_path"/>
            <result property="fileSize" column="file_size"/>
            <result property="fileType" column="file_type"/>
            <result property="fileExt" column="file_ext"/>
            <result property="fileMd5" column="file_md5"/>
            <result property="createdAt" column="created_at"/>
            <result property="updatedAt" column="updated_at"/>
            <result property="deletedAt" column="deleted_at"/>
        resultMap>
    
        <select id="queryByMd5" resultMap="queryFile">
            select * from sys_file where file_md5 = #{md5} and deleted_at is null
        select>
    
    
    mapper>
    

    全局异常处理

    • exceptin 目录里面创建 ServiceException 类,编写如下代码:
    // 省略 package import 
    
    //专用于处理业务层的异常基类
    public class ServiceException extends RuntimeException{
        public ServiceException() {
            super();
        }
    
        public ServiceException(String message) {
            super(message);
        }
    
        public ServiceException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public ServiceException(Throwable cause) {
            super(cause);
        }
    
        protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
    
    
    • controller 目录里面创建 BaseController 基类,后续 controller 继承他
    // 省略 package import 
    
    @ControllerAdvice
    public class BaseController {
        public static final int OK = 200;
    
        /**
         * 全局手动抛出异常处理
         * 1.当出现了value内的异常之一,就会将下方的方法作为新的控制器方法进行执行
         *   因此该方法的返回值也同时是返回给前端的页面
         * 2.此外还自动将异常对象传递到此方法的参数列表中,这里使用Throwable e来接收
         **/
        @ExceptionHandler(ServiceException.class) //统一处理抛出的异常
        public ResJson<Void> handleException(Throwable e){
            ResJson<Void> result = new ResJson<>(e);
            result.setCode(5000); //数据库或服务器有问题
            return result;
        }
    
    
    }
    
    

    注册配置

    config 目录创建 GlobalControllerPathPrefixConfig 类,写入以下内容:

    // 省略 package import 
    
    //群集统一配置接口前缀
    @Configuration
    public class GlobalControllerPathPrefixConfig implements WebMvcConfigurer {
    
    
        @Value("${spring.controller.api-prefix}")
        private String pathPrefix;
    
        @Value("${file.upload.path-pattern}")
        private String pathPattern;
    
        @Value("${file.upload.path}")
        private String fileUploadPath;
    
        //全局接口注册 api前缀
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            configurer.addPathPrefix(pathPrefix, c -> c.isAnnotationPresent(RestController.class));
        }
    
        //静态资源图片上传,虚拟路径返回
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            //将匹配上/files/**虚拟路径的url映射到文件上传到服务器的目录,获取静态资源
            registry.addResourceHandler("/" +pathPattern + "/**").addResourceLocations("file:" + fileUploadPath+File.separator);
            WebMvcConfigurer.super.addResourceHandlers(registry);
        }
    
    
    }
    

    创建工具类

    • 创建一个全局返回类。在 utils 里面创建一个 ResJson 类型
    // 省略 import
    
    @Data
    public class ResJson<E> implements Serializable {
        /**
         * 状态码
         */
        private Integer code;
        /**
         * 提示信息
         */
        private String message;
        /**
         * 返回数据
         */
        private E data;
    
        public ResJson(Integer code) {
            this.code = code;
        }
        public ResJson(Integer code,String message) {
            this.code = code;
            this.message = message;
        }
    
        public ResJson(Throwable e) {
            this.message = e.getMessage();
        }
    
        public ResJson(Integer code,String message,E data) {
            this.code = code;
            this.data = data;
            this.message = message;
        }
    
    }
    
    
    • 创建一个 md5 加密类。在 utils 里面创建一个 PasswordEncryptedUtils 类型
    // 省略 package import 
    public class PasswordEncryptedUtils {
    
        public static String getPasswordByMD5(String pwd,String salt){
            for (int i = 0; i < 3 ; i++) {
                //md5加密算法的调用
                pwd =  DigestUtils.md5DigestAsHex((salt + pwd + salt).getBytes()).toUpperCase();
            }
            //返回经过加密的结果
            return pwd;
        }
    }
    
    

    创建上传服务

    • server目录里面创建FileServer 接口类
    // 省略 package import 
    
    public interface FileServer {
    
        FileEntity upload(MultipartFile file);
    
        String createThumb(String fileDir,String filePath,String fileName,String suffix);
    
    }
    
    
    • server目录里面创建impl目录并在里面创建FileServerImpl类实现上面的接口功能
    
    // 省略 package import 
    
    @Service
    public class FileServerImpl implements FileServer {
    
        public static final int maxWidth = 100;
    
        //拦截的url,虚拟路径
        public String pathPattern = "uploads";
    
        //文件磁盘路径
        @Value("${file.upload.path}")
        private String fileUploadPath;
    
        @Value(value = "${file.upload.suffix:jpg,jpeg,png,bmp,xls,xlsx,pdf}")
        private String fileUploadSuffix;
    
        @Value(value = "${file.upload.is-thumb}")
        private Boolean isThumb;
    
        @Value(value = "${file.upload.proportion}")
        private Integer proportion;
    
        @Autowired
        HttpServletRequest request;
    
        @Autowired
        FileMapper fileMapper;
    
        //文件上传
        @Override
        public FileEntity upload(MultipartFile file) {
            FileEntity fileRes = new FileEntity();
    
            if (file.isEmpty()) {
                // log.error("the file to be uploaded is empty");
                return fileRes;
            }
            List<String> suffixList = Lists.newArrayList(fileUploadSuffix.split(","));
    
            try {
                //校验文件后缀
                String originalFilename = file.getOriginalFilename();
                //获取文件类型
                String type = FileUtil.extName(originalFilename);
                //文件后缀
                String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
                if (!suffixList.contains(suffix)) {
                    //log.error("unsupported file format");
                    return fileRes;
                }
    
                //获取文件md5
                String md5 = SecureUtil.md5(file.getInputStream());
    
                // 从数据库查询是否存在相同的记录
                FileEntity dbFiles = fileMapper.queryByMd5(md5);
                if (dbFiles != null) { // 文件已存在
                    return dbFiles;
                }
    
    
                String year = new SimpleDateFormat("yyyy").format(new Date());
                String month = new SimpleDateFormat("MM").format(new Date());
                String day = new SimpleDateFormat("dd").format(new Date());
    
                String fileDir = fileUploadPath;
                String filePath = File.separator + year + File.separator + month + File.separator + day + File.separator;
    
                //首次需生成目录
                File folder = new File(fileDir + filePath);
                if (!folder.exists()) {
                    folder.mkdirs();
                }
    
                AtomicInteger counter = new AtomicInteger(0);
                String uniqueString = String.valueOf(Instant.now().toEpochMilli());
    
                String fileName = uniqueString + "." + suffix;
                String absolutePath = fileDir + filePath + fileName;
                file.transferTo(new File(absolutePath));
                //网页路径
                String dataFilePath = pathPattern + "/" + year + "/" + "/" + month + "/" + day + "/" + fileName;
    
                fileRes.setFilePath(dataFilePath);
                fileRes.setFileName(fileName);
                fileRes.setIp(request.getRemoteAddr());
                fileRes.setFileSize(file.getSize() / 1024);
                fileRes.setFileType(type);
                fileRes.setFileExt(suffix);
                fileRes.setFileMd5(md5);
    
                //判断是否生成缩率图
                if (isThumb) {
                    createThumb(fileDir, filePath, uniqueString, suffix);
                    String dataFileThumbPath = pathPattern + "/" + year + "/" + "/" + month + "/" + day + "/" + uniqueString + "_thumb." + suffix;
                    fileRes.setThumbPath(dataFileThumbPath);
                }
                fileMapper.insert(fileRes);
            } catch (IOException e) {
                e.printStackTrace();
                throw new ServiceException(e.getMessage());
            }
            return fileRes;
        }
    
        //生成缩率图
        @Override
        public String createThumb(String fileDir, String filePath, String fileName, String suffix) {
            String localPath = fileDir + filePath + fileName + "." + suffix;
            String thumbPath = fileDir + filePath + fileName + "_thumb." + suffix;
    
            //判断缩略图是否存在
            Path path = Paths.get(thumbPath);
            if (Files.exists(path)) {
                return filePath + fileName + "_thumb." + suffix;
            }
    
            File originalFile = new File(localPath);
            try {
                BufferedImage originalImage = ImageIO.read(originalFile);
                int imageWidth = originalImage.getWidth();
                int imageHeight = originalImage.getHeight();
    
                double thumbWidth = 0;
                double thumbHeight = 0;
                if (imageWidth > maxWidth || imageHeight > maxWidth) {
                    thumbWidth = (double) imageWidth / (double) proportion;
                    thumbHeight = (double) imageHeight / (double) proportion;
                }
                if (thumbHeight > 0) {
                    // 创建缩略图
                    BufferedImage thumbnail = new BufferedImage((int) thumbWidth, (int) thumbHeight, BufferedImage.TYPE_INT_RGB);
                    Graphics graphics = thumbnail.createGraphics();
                    graphics.drawImage(originalImage.getScaledInstance((int) thumbWidth, (int) thumbHeight, Image.SCALE_SMOOTH), 0, 0, null);
                    graphics.dispose();
    
                    // 输出到文件
                    ImageIO.write(thumbnail, suffix, new File(thumbPath));
                    return filePath + fileName + "_thumb." + suffix;
                }
            } catch (IOException e) {
                e.printStackTrace();
                throw new ServiceException(e.getMessage());
            }
            return "";
        }
    
    
    }
    
    

    上传

    • 创建一个上传控制器UploadController,写入以下内容:
    // 省略 package import 
    
    @RestController
    public class UploadController extends BaseController {
    
        @Autowired
        FileServer fileServer;
    
    
        @PostMapping("/upload")
        public ResJson<FileEntity> upload(@RequestParam MultipartFile file){
           FileEntity fileRes= fileServer.upload(file);
           return new ResJson<>(OK,"上传成功!",fileRes);
        }
    
    }
    
    
    • 测试

    通过 postman 接口测试,调用上面的上传接口

    查看application.yml里面配置的上传目录,是否有文件

    通过网络路径访问图片

    总结

    本文适合 springboot 入门的初学者。

    以文件上传为demo,衍生出了一些常用功能,包含:文件上传,入库,请求。异常处理,api前缀,数据迁移,生成缩略图,等功能。

    希望能对初学者有一个参考的作用。

    – 欢迎点赞、关注、转发、收藏【我码玄黄】,gonghao同名

  • 相关阅读:
    Git Commit Message 应该怎么写?
    SpringBoot + xxl-job 多数据源异构数据增量同步
    中秋节静态HTML网页作业作品 大学生中秋网页设计制作成品 简单DIV CSS布局网站
    I.MX6U-ALPHA开发板(PWM实验)
    Unity微信小游戏登录授权获取用户信息
    格密码学: LWE\SIS for PKE\SIG\FHE
    STM32实战总结:HAL之触摸按键
    OSF--网络类型
    使用uniapp设置tabbar的角标和移除tabbar的角标
    浏览器开发者工具打开检测
  • 原文地址:https://blog.csdn.net/qq_30333063/article/details/139356635