简介
本文主要以文件上传为demo,介绍了一些 springboot web
开发的入门的技术栈。
对应刚接触 springboot
的可以参考下。
主要包括文件md5比对、生成图片缩略图、数据库迁移、文件记录持久化、请求全局异常处理等功能。
准备工作
- 在
idea
中创建项目,java8
,springboot 2
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>
- 创建项目目录
目录意义如下:
- 修改
application.yml
配置:
---
# 开发环境的配置
server:port: 9090
spring:controller:api-prefix: /apiflyway:enabled: true #开启数据迁移table: flyway_schema_history #用于存储迁移历史记录的表名,默认为flyway_schema_historybaseline-on-migrate: true #当迁移数据库存在但没有元数据的表时,自动执行基准迁移,新建flyway_schema_history表locations: classpath:db/migration #数据库迁移脚本的位置,默认为classpath:db/migration,classpath 羡慕resources目录clean-disabled: true #用于控制是否禁用 Flyway 的 clean 操作。datasource:username: rootpassword: rooturl: jdbc:mysql://127.0.0.1:3306/java_iot?serverTimezone=GMT%2b8config:activate:on-profile: dev #开发环境servlet:multipart:enabled: true # 允许文件上传max-file-size: 20971520 # 单文件最大限制 20Mmax-request-size: 52428800 # 单次请求最大限制 50M
file:upload:path: E:\project-java\java-upload # 文件上传保存服务器绝对路径suffix: jpg,jpeg,png,bmp,xls,xlsx,pdf # 文件上传保存路径is-thumb: true # 是否开启缩略图 true falseproportion: 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
。并写入以下内容
<?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前缀@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix(pathPrefix, c -> c.isAnnotationPresent(RestController.class));}//静态资源图片上传,虚拟路径返回@Overridepublic 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;@AutowiredHttpServletRequest request;@AutowiredFileMapper fileMapper;//文件上传@Overridepublic 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;}//获取文件md5String 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;}//生成缩率图@Overridepublic 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 {@AutowiredFileServer 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同名