springboot基础及上传组件封装

简介: springboot基础及上传组件封装

简介

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

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

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

准备工作

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

image.png

image.png

image.png

  • maven 所需依赖
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
    <groupId>com.bimcc</groupId>
    <artifactId>iot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>iot</name>
    <description>iot</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.6.13</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--数据库迁移 -->
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
            <version>5.2.4</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--请求验证-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>2.7.8</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
        </dependency>
        <!--工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.bimcc.iot.IotApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>


  • 创建项目目录

image.png

目录意义如下:

image.png

  • 修改 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   # 当前环境
  • 创建数据表迁移文件

image.png

写入以下内容:

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。并写入以下内容
<?xml version="1.0" encoding="UTF-8"?>
<!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">
    <!-- 询的结果集字段和实体类的user属性名不一致,自定义查询结果集的映射规则   -->
    <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 接口测试,调用上面的上传接口

image.png

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

image.png

通过网络路径访问图片

image.png

总结

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

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

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


相关文章
|
28天前
|
Java
springboot将list封装成csv文件
springboot将list封装成csv文件
27 4
|
2月前
|
Java API Spring
springBoot:注解&封装类&异常类&登录实现类 (八)
本文介绍了Spring Boot项目中的一些关键代码片段,包括使用`@PathVariable`绑定路径参数、创建封装类Result和异常处理类GlobalException、定义常量接口Constants、自定义异常ServiceException以及实现用户登录功能。通过这些代码,展示了如何构建RESTful API,处理请求参数,统一返回结果格式,以及全局异常处理等核心功能。
|
4月前
|
前端开发 小程序 Java
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
本文详细介绍了如何在SpringBoot项目中统一处理接口返回结果及全局异常。首先,通过封装`ResponseResult`类,实现了接口返回结果的规范化,包括状态码、状态信息、返回信息和数据等字段,提供了多种成功和失败的返回方法。其次,利用`@RestControllerAdvice`和`@ExceptionHandler`注解配置全局异常处理,捕获并友好地处理各种异常信息。
1429 0
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
|
4月前
|
JSON 前端开发 Java
SpringBoot3怎么做统一结果封装?
在Spring Boot应用中,统一结果封装有助于团队协作,确保一致的API响应格式,提升代码质量和维护性。主要优点包括:简化前端集成工作,减少后端重复编码,以及增强接口的可维护性。实现上,首先定义`Result`类来封装响应状态码、消息、数据及时间戳;其次,通过`ResultCode`枚举类标准化状态信息。示例代码展示了如何构建这些类,并通过一个简单的控制器方法演示了如何使用它们返回JSON格式的响应结果。
127 2
|
5月前
|
JSON Java fastjson
Spring Boot返回Json数据及数据封装
本文详细介绍了如何在Spring Boot项目中处理JSON数据的传输 Spring Boot默认使用Jackson作为JSON处理器,并通过`spring-boot-starter-web`依赖自动包含相关组件。文章还展示了如何配置Jackson处理null值,使其转换为空字符串。此外,文章比较了Jackson和FastJson的特点,并提供了FastJson的配置示例,展示了如何处理null值以适应不同应用场景。
|
6月前
|
Java
springboot封装RedisTemplate
springboot封装RedisTemplate
|
6月前
|
搜索推荐 前端开发 JavaScript
SpringBoot静态资源访问控制和封装集成方案
该文档描述了对基于SpringBoot的项目框架进行优化和整合的过程。原先采用前后端分离,后端兼做前端,但随着项目增多,升级维护变得复杂。因此,决定整合后台管理页面与后端代码,统一发布。设计上,框架包含后台管理资源,项目则配置具体业务页面,项目可通过覆盖框架资源实现个性化。关键步骤包括:自定义静态资源访问路径、解决图标与字体文件访问问题、设定自定义欢迎页面和页面图标,以及确保项目能正确访问框架静态资源。通过扫描jar包、解压和拷贝资源到项目目录,实现了框架静态资源的动态加载。此外,调整静态资源访问优先级,保证正确加载。最终实现支持jar和war包的项目结构优化。
112 4
|
6月前
|
运维 监控 Java
SpringBoot-ElasticJob封装快速上手使用(分布式定时器)
SpringBoot-ElasticJob封装快速上手使用(分布式定时器)
|
7月前
|
JSON 前端开发 Java
Spring Boot3统一结果封装
Spring Boot3统一结果封装
166 1
|
7月前
|
JSON 前端开发 Java
Springboot前后端分离项目统一封装返回结果
Springboot前后端分离项目统一封装返回结果