一、图片文件上传

1、文件上传原理

将文件作为一个 HTTP 请求体发送。HTTP 请求体通常包括文件的内容、文件名、文件类型等信息。服务器接收到请求后,会根据请求中的内容、文件名等信息,找到并读取文件,并将其存储到服务器上相应的位置。

在HTML中使用表单上传文件的步骤

在 HTML 中使用表单上传文件,可以遵循以下步骤:

  • 在 HTML 表单中添加一个 type="file" 的" '<input'>' "标签,以便用户可以选择上传的文件。
  • 设置表单的 enctype 属性为 multipart/form-data,这将表单数据编码成多个部分,其中包括上传的文件。
  • 将表单的 method 属性设置为 POST,以便将表单数据和上传的文件作为 POST 请求发送到服务器。
  • 在表单中添加其他需要提交的表单数据,例如文本字段等。
  • 在服务器端处理接收到的 POST 请求,提取上传的文件和其他表单数据,并进行处理。

2、上载图片文件功能

(1)前端页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>文件上传</h1>
<!-- 文件上传表单method必须为POST enctype必须设置为multipart/form-data,否则不能传递文件信息 -->
<form action="/upload" method="post" enctype="multipart/form-data">
    <label>上传图片:<input type="file" name="imageFile" accept="image/jpg,image/png"></label>
    <input type="submit" value="上传">
</form>
</body>
</html>

(2)controller层:

@RestController
public class UploadFormController {

    private static final Logger logger = LoggerFactory.getLogger(UploadFormController.class);

    @PostMapping(value = "/upload", consumes = "multipart/form-data")
    public String upload(MultipartFile imageFile) {
        String name = imageFile.getName();
        logger.info("表单名:{}", name);
        //MultipartFile可以接受表单提交的文件
        String fileName = imageFile.getOriginalFilename();
        logger.info("文件名:{}", fileName);
        //创建保存图片的目录
        File path = new File("F:/upload");
        if (!path.exists()) {
            path.mkdirs();
        }
        //保存文件

        try {
            imageFile.transferTo(new File(path, fileName));
            return "上传成功";
        } catch (IOException e) {
            e.printStackTrace();
            return "上传失败";
        }

    }
}

(3) 自定义的文件上传配置

在application-dev.yml中添加自定义配置

#文件上传配置 自定义的配置
upload:
  location: D:/upload #window系统配置
  # location: /opt/coolsharkhub/upload linux系统配置
  suffixes: .jpg,.jpeg,.png,.gif #支持的图片后缀 ","是为了后面易于分割

(4)服务层

/**
 * @document: 文件上传服务层接口
 * @Author:SmallG
 * @CreateTime:2023/9/8+14:18
 */

public interface UploadService {
    /**
     * 保存上传的文件
     * @param file
     * @return
     */
    String saveUploadFile(MultipartFile file);
}

实现类:

UUID能成一组32位 16进制的字符串

@Override
public String saveUploadFile(MultipartFile file) {
    //创建上传文件的目录
    logger.info("文件上传的目录:{}", path);
    if (!path.exists()) {
        path.mkdirs();
    }
    //检查是否创建了文件
    if (file == null || file.isEmpty()) {
        logger.warn("文件为空,请检查是否选择了文件");
        throw new BlankParameterException("文件为空,或者文件上传出现异常");
    }
    String fileName = file.getOriginalFilename();
    logger.info("获取到文件名:{}", fileName);
    //获取文件的扩展名 .png
    String suffix = fileName.substring(fileName.toString().lastIndexOf(".")).toLowerCase();
    //检查上传的文件是否合法(文件的扩展名是不是在配置之内)
    if (!suffixes.contains(suffix)) {
        logger.warn("文件不合法:{}", suffix);
        throw new FileSuffixNotAllowedException("文件扩展名不合法");
    }
    //改造文件名 声明一段随机名字
    //UUID是生成一组32位 16进制的字符串
    fileName = UUID.randomUUID() + suffix;
    //获取当前日期
    LocalDate date = LocalDate.now();

    //格式化日期
    String datePath = date.format(DateTimeFormatter.ofPattern("/yyyy/MM/dd"));
    //创建存储文件的目录
    File dirFile = new File(path, datePath);
    if (!dirFile.exists()) {
        dirFile.mkdirs();
    }
    logger.info("文件存储目录:{}", dirFile.getAbsolutePath());
    // 保存文件
    try {
        file.transferTo(new File(dirFile,fileName));
        logger.info("文件保存成功:{}",datePath+fileName);
        return datePath+fileName; //返回文件存储路径
    } catch (IOException e) {
        e.printStackTrace();
        logger.error("文件保存失败");
        throw new FileSaveException("文件保存失败");
    }
}

(5)Controller层

/**
 * @document: 文件上传控制器
 * @Author:SmallG
 * @CreateTime:2023/9/8+15:21
 */

@RestController
public class UploadController {
    private static final Logger logger = LoggerFactory.getLogger(UploadController.class);

    @Autowired
    private UploadServiceImpl uploadService;

    /**
     * 上传文件,请求路径为/api/upload/images
     *
     * @param imageFile
     * @return
     */
    @PostMapping(value = "/api/upload/images", consumes = "multipart/form-data")
    @ResponseStatus(HttpStatus.CREATED) //201 添加成功
    public String uploadImages(MultipartFile imageFile) {
        String result = uploadService.saveUploadFile(imageFile);
        return result;
    }
}

测试类:

@SpringBootTest
@AutoConfigureMockMvc
class UploadControllerTest {
    private static final Logger logger = LoggerFactory.getLogger(UploadControllerTest.class);

    @Autowired(required = false)
    MockMvc mockMvc;

    @Test
    void uploadImages() throws Exception {
        //MockMultipartFile可以模拟一个文件
        //第一个参数是表单名 第二个参数是文件名 第三个参数是文件的格式 第四个参数是文件的内容
        MockMultipartFile file = new MockMultipartFile("imageFile", "test.jpg", "image/jpeg", "Hello World".getBytes());
        String url = "/api/upload/images";
        //发送请求
        String result = mockMvc.perform(multipart(url) //请求地址
                        .file(file)) //传递的文件
                .andExpect(status().isCreated())//状态码201
                .andReturn().getResponse().getContentAsString();//返回的结果
        logger.info("result:{}", result);
    }
}

测试结果展示:

模拟上传文件效果展示

3、Element UI文件上载

表单方式上载和Ajax文件上载

Ajax文件上传的好处:

  • 无页面刷新:使用Ajax方式提交文件可以无需重新加载整个页面,提升用户体验。
  • 实时上传进度展示:Ajax方式可以实时展示文件上传的进度,提供更好的用户体验。
  • 可以方便的限制文件大小:使用Ajax方式可以轻松地控制文件的大小,增强了系统的安全性。
Ajax上载与Element UI上载控件

前端html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- import CSS -->
    <link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.15.9/theme-chalk/index.css">
    <style>
      /* 上传按钮的样式 */
      .picture {
        width: 556px;
        height: 276px;
        border: 1px solid #ddd;
        border-radius: 6px;
        /* 设置行高和高度相同,文字图标就上下居中了*/
        line-height: 276px;
        text-align: center;
        /* 设置overflow: hidden; 上载图标超出边界就不显示了 */
        overflow: hidden;
      }
      /* 上传按钮的图标样式 */
      .picture-icon {
        /* 设置大一些的图标,看着醒目 */
        font-size: 38px;
      }
    </style>
</head>
<body>
    <div id="app">
      <h1>ajax上传图片</h1>
      <el-form label-width="100">
        <el-form-item label="上传图片">
          <el-upload
                  action="/api/upload/images"
                  name="imageFile"
                  class="picture"
                  :show-file-list="false"
                  :on-success="handleSuccess"
                  :before-upload="beforeImageUpload">
            <!-- 显示上传以后的图片 -->
            <img v-if="image" :src="image" class="product">
            <!-- 上传按钮,设置overflow: hidden; 超出边界就不显示了 -->
            <i class="el-icon-plus picture-icon"></i>
          </el-upload>
        </el-form-item>
      </el-form>
    </div>
    <!-- import Vue before Element -->
    <script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
    <!-- import JavaScript -->
    <script src="https://cdn.staticfile.org/element-ui/2.15.9/index.min.js"></script>
    <!-- 引入axios -->
    <script src="https://cdn.staticfile.org/axios/0.21.4/axios.min.js"></script>
    <script type="text/javascript">
      var vue = new Vue({
        el: "#app",
        data: function (){
          return {
            // 上传成功后返回的图片地址
            image: ''
          }
        },
        methods: {
          handleSuccess(res, file) {
            console.log("上传成功");
            // 上传成功后将返回的图片地址赋值给image,然后在页面上显示图片
            this.image = res;
            console.log(res);
            console.log(file);
          },
          beforeImageUpload(file) {
            console.log("上传前");
            console.log(file);
            const isJPG = file.type === 'image/jpeg';
            const isPNG = file.type === 'image/png';
            const isLt2M = file.size / 1024 / 1024 < 2;
            if (!isJPG && !isPNG) {
              this.$message.error('上传图片只能是 JPG 或 PNG 格式');
            }
            if (!isLt2M) {
              this.$message.error('上传图片大小不能超过 2MB');
            }
            return (isJPG || isPNG) && isLt2M;
          }
        }
      })
    </script>
</body>
</html>
设置静态资源目录显示远程图片
spring:
  web:
    resources:
      static-locations: classpath:/static/,file:/D:/upload  #静态文件资源映射

二、管理功能

1、添加轮播图

(1)Mapper层

接口:

/**
 * 保存轮播图
 * @param banner 轮播图对象
 * @return 返回受影响的行数
 */
int insert(Banner banner);

sql代码:

<insert id="insert">
    INSERT INTO banner
        (image, order_num)
    values (#{image}, #{orderNum})
</insert>

测试类:

@Test
void insert() {
    //创建轮播图对象
    Banner banner = new Banner(null,"测试轮播图1",10,null,null);
    logger.info("保存的轮播图对象:{}",banner);
    int result = bannerMapper.insert(banner);
    logger.info("result:{}",result);
}

添加轮播图mapper层测试

(2)Service层

接口:

/**
 * 保存轮播图
 * @param bannerDTO
 */
void addBanner(BannerDTO bannerDTO);

实现类:

@Override
public void addBanner(BannerDTO bannerDTO) {
    logger.info("业务层保存轮播图");
    if (bannerDTO == null) {
        throw new BlankParameterException("轮播图信息不能为空");
    }
    if (bannerDTO.getImage() == null || bannerDTO.getOrderNum() == null) {
        throw new BlankParameterException("轮播图URL不能为空");
    }
    //创建轮播图实体对象
    Banner banner = new Banner(null, bannerDTO.getImage(), bannerDTO.getOrderNum(), null, null);
    //保存轮播图
    int result = bannerMapper.insert(banner);
    if (result<1){
        throw new EntityNotFoundException("保存轮播图失败");
    }

测试类:

@Test
@Transactional
void addBanner() {
    BannerDTO bannerDTO = new BannerDTO("测试轮播图1",10);
    bannerService.addBanner(bannerDTO);
}

测试效果展示:

保存轮播图业务层测试

(3)controller层

/**
 * 保存轮播图
 */
@PostMapping("/api/banners")
@ResponseStatus(HttpStatus.CREATED) //201
public String addBanner(@RequestBody BannerDTO bannerDTO){
    logger.info("控制层执行保存轮播图");
    //保存轮播图
    bannerService.addBanner(bannerDTO);
    return "保存成功";
}

测试类:

@Test
void addBanner() throws Exception {
    //创建请求体参数
    String json = """
            {
                "image":"测试轮播图1",
                "orderNum":10
            }
            """;
    String url = "/api/banners";
    //发送post请求
    String result = mockMvc.perform(post(url)
                    .contentType("application/json") //以json的格式发送请求体
                    .content(json)
            ).andExpect(status().isCreated())//201
            .andReturn().getResponse().getContentAsString();
    logger.info("result:{}", result);

}

测试效果展示:

添加轮播图Controller层效果

2、修改轮播图

mapper层

mapper中添加方法:

/**
 * 根据轮播图id修改轮播图数据
 * @param banner
 * @return 返回受影响的行数
 */
int updateById(Banner banner);

sql代码:

<!--根据轮播图id修改轮播图数据-->
<update id="updateById">
    update banner
    <set>
        <if test="image != null and image !=''">
            image = #{image},
        </if>
        <if test="orderNum != null and orderNum > 0">
            order_num=#{orderNum}
        </if>
    </set>
    where id = #{id}
</update>

测试类:

@Test
@Transactional
void updateById() {
    Banner banner = new Banner(1L, "修改测试", 2, null, null);
    int i = bannerMapper.updateById(banner);
    logger.info("修改了{}行数据", i);
}

测试效果展示:

修改轮播图mapper层测试

service层

接口省略,实现类:

@Override
public void updateById(Long id, BannerDTO bannerDTO) {
    logger.info("业务层修改轮播图");
    if (bannerDTO == null) {
        throw new BlankParameterException("轮播图的图片不能为空");
    }
    if (bannerDTO.getImage() == null || bannerDTO.getOrderNum() == null) {
        throw new BlankParameterException("轮播图参数不能为空");
    }
    //bannerDTO转换为Banner对象
    Banner banner = new Banner(id,
            bannerDTO.getImage(),
            bannerDTO.getOrderNum(), null, null);
    int i = bannerMapper.updateById(banner);
    if (i<1){
        throw new EntityNotFoundException("修改失败");
    }
}

测试类:

@Test
@Transactional
void updateById() {
    logger.info("轮播图修改");
    BannerDTO bannerDTO = new BannerDTO("测试轮播图", 10);
    bannerService.updateById(1L, bannerDTO);
    logger.info("轮播图修改成功");
}

controller层

@PutMapping("/api/banners/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT) //204 修改成功
public String updateById(@PathVariable Long id, @RequestBody BannerDTO bannerDTO) {
    logger.info("控制层修改轮播图");
    bannerService.updateById(id, bannerDTO);
    return "修改成功";
}
最后修改:2023 年 09 月 11 日
如果觉得我的文章对你有用,请随意赞赏