今日学习(2024-12-23):Lombok、SpringBoot+Vue3实现图片上传到minio对象存储服务器

1.Lombok插件

在这里插入图片描述

概述:
Lombok 是一个 Java 库,通过使用其提供的注解来自动生成 Java 类中的一些常用方法,如构造函数、Getter 和 Setter 方法、equals 和 hashCode 方法等,从而减少样板代码,提高代码的简洁性和可读性。

@Getter和@Setter

用于自动生成类中成员变量的 Getter 和 Setter 方法。
例如:

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Person {
    private String name;
    private int age;
    //自动生成name和Getter 和 Setter 方法,无需手动编写。
}

@ToString

自动生成类的toString方法,方便在打印对象时输出对象的属性值。
例如:

import lombok.ToString;

@ToString
public class Person {
    private String name;
    private int age;
}
/*
使用@ToString注解后,会生成一个toString方法,默认会输出类名以及所有成员变量的名称和值,例如Person(name=John, age=30)。
*/

@EqualsAndHashCode

自动生成equals和hashCode方法,用于比较对象是否相等和计算对象的哈希码。
示例:

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class Person {
    private String name;
    private int age;
}
/*
生成的equals方法会比较对象的所有非静态、非 transient 成员变量的值是否相等,hashCode方法会根据成员变量的值计算出一个哈希码。
*/

其他:

  1. @NoArgsConstructor:生成一个包含所有成员变量的构造函数。
  2. @AllArgsConstructor:
  3. @RequiredArgsConstructor
    @RequiredArgsConstructor和@NonNull注解是成员变量的构造函数。
    示例:
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.NonNull;

@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
public class Person {
    private String name;
    @NonNull private int age;
    private final String address;
}
/*
Lombok 会分别生成无参构造函数、包含所有成员变量的构造函数以及包含age和address的构造函数。
*/

@Data(常用、重点)

是一个组合注解,相当于同时使用了@Getter、@Setter、@ToString、@EqualsAndHashCode和@RequiredArgsConstructor注解,能够自动生成 Getter、Setter、toString、equals、hashCode以及包含所有final修饰的成员变量和@NonNull注解的成员变量的构造函数。
示例:

import lombok.Data;

@Data
public class Person {
    private String name;
    private int age;
}
/*
使用@Data注解后,会自动生成上述提到的各种方法,大大简化了代码编写
*/

2.SpringBoot+Vue3实现图片上传到minio服务器

在这里插入图片描述
在这里插入图片描述

2.1.minio配置搭建(这里使用docker来进行安装)

2.1.1 安装docker(已经有docker的可以跳过)

更新系统包管理器和安装一些必要的工具:
sudo yum update
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
添加 Docker 软件仓库:
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

安装 Docker:
sudo yum install docker-ce
启动 Docker 服务并设置它在启动时自动启动:
sudo systemctl start docker
sudo systemctl enable docker
验证 Docker 安装:
docker -v

2.1.2 安装/搭建minio

在这里插入图片描述

1. 下载镜像(可以找我私聊取)
2. 加载镜像
docker load < Minio.tar.gz
docker images:检查镜像是否加载完成
3. 启动镜像
第一步:mkdir -p /root/hdp/minio/data
第二步:chmod -R 777 /root/hdp/minio/data
第三步:
docker run -it -d --name minio \
   -p 9000:9000 \
   -p 9001:9001 \
   -v /root/hdp/minio/data:/data \
   --privileged=true \
   --env "MINIO_ROOT_USER=root" \
   --env "MINIO_ROOT_PASSWORD=root1122" \
   bitnami/minio:latest
4. 访问minio地址
http://xxx.xxx.xx:9000
用户名:root 密码:root1122

在这里插入图片描述
点击创建Bucket名称可以自定义:
在这里插入图片描述
在这里插入图片描述
修改Bucket的权限,将private修改为public(以免后续上传图片时看不到图片)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1.3整合到项目中

后端(Spring boot项目的yml文件中):
在这里插入图片描述
前端:
在这里插入图片描述

2.2 开发后端实现图片上传的接口

DAO层:

mapper中的SQL语句:

<?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.person.blog.partal2.db.dao.UserDao">
<!--    注册-->
    <!--查询传过来的 用户名 再数据库表中有没有重名的-->
    <select id="usernameCheck" parameterType="String" resultType="Integer">
        select id from `user` where username = #{username}
    </select>

    <!--如果没有,那就正常注册插入注册记录并且上传图片-->
    <insert id="register" parameterType="com.person.blog.partal2.pojo.User">
        insert into `user`(username,password,email,avatar,role,create_time)
        values (#{username},#{password},#{email},#{avatar},${role},#{create_time})
    </insert>
</mapper>

接口方法:

public interface UserDao {
int register(User user);   //注册操作
}

service层:

接口:

public interface UserService {
    Map<String, Object> register(User User);
    String uploadAvatar(MultipartFile file);
    }

实现类:

package com.person.blog.partal2.service.impl;
import cn.hutool.core.date.DateUtil;
import com.person.blog.partal2.exception.GlobalException;
import com.person.blog.partal2.pojo.User;
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import com.person.blog.partal2.db.dao.UserDao;
import com.person.blog.partal2.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;


	@Service
	@Slf4j
	public class UserServiceImpl implements UserService{

	    @Autowired
	    private UserDao userDao;
	    
	    /*
		将minio相关配置引进来
		*/
	    @Value("${minio.endpoint}")
	    private String endpoint;
	    @Value("${minio.access-key}")
	    private String accessKey;
	    @Value("${minio.secret-key}")
	    private String secretKey;
	    @Value("${minio.bucket-name}")
	    private String bucketName;
	
		@Override
	    public Map<String, Object> register(User user) {
	        Map<String, Object> resultMap = new HashMap<>();
	        String username = user.getUsername();
	        String password = user.getPassword();
	
	        Integer checkCount = userDao.usernameCheck(username);

	//        MD5 md5 = MD5.create();
	//        String temp = md5.digestHex(username);
	//        String tempStart = StrUtil.subWithLength(temp, 0, 6);
	//        String tempEnd = StrUtil.subSuf(temp,temp.length()-3);
	//        password = md5.digestHex(tempStart + password + tempEnd);
	        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
	        password = encoder.encode(password);
	        user.setRole(0);  //角色信息
	
	
	        user.setPassword(password);
	        if (checkCount == null) {
	            int rows = userDao.register(user);
	            if (rows == 1) {
	                resultMap.put("result", "注册成功");
	                return resultMap;
	            }else {
	                resultMap.put("result", "注册失败");
	                return resultMap;
	            }
	
	        }else {
	            resultMap.put("result", "用户名重复,不能重复添加");
	            return resultMap;
	        }
	    }

	/*
	上传图片方法
	*/
	@Override
    @Transactional
    public String uploadAvatar(MultipartFile file) {

        /*
         * .replace("-", ""):
         * 第一个参数移除字符串中所有的连字符"-"。
         * 第二个参数:替换第一个参数指定的字符或子字符串的新字符或子字符串:它是""(一个空字符串),
         * 意味着不用任何字符来替换连字符,而是直接移除它们。
         * */
        String date = DateUtil.format(new Date(), "yyyyMMdd") + "/";
        String filename = date+"blog-" + UUID.randomUUID().toString().replace("-", "") + file.getOriginalFilename();
        if (file != null) {
            try {
                new InputStreamReader(file.getInputStream(), "UTF-8");
                 // 定义支持的图片格式列表
                List<String> supportedFormats = Arrays.asList(".png", ".jpg");

               // 获取上传文件的原始文件名
                String originalFilename = file.getOriginalFilename();
                // 获取文件名后缀(包含点号)
                String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
                if (originalFilename !=null) {
                    if (supportedFormats.contains(fileExtension.toLowerCase())){
                        String contentType = filename.toLowerCase().endsWith(".png") ? "image/png" : "image/jpeg";
                        //获取minio连接
                        MinioClient minioClient = MinioClient.builder().endpoint(endpoint)
                                .credentials(accessKey, secretKey)
                                .build();
                        boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
                        if (!found) {
                            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                        }else {
                            log.info("Bucket 'blog' already exists");
                        }

                        //打印到控制台
                        System.out.println(filename);
                        log.info(filename);

                        //指定上传图片的格式和大小
                        minioClient.putObject(PutObjectArgs.builder()
                                .bucket(bucketName)
//                        .object("blog/"+filename)   //上传后的名称
                                .object(filename)
                                .stream(file.getInputStream(),-1,5 * 1024 * 1024)
                                .contentType(contentType).build());
                        System.out.println("文件上传成功");
                    }
                }else{
                    // 处理不支持的文件格式情况,比如抛出异常或者记录日志提示用户等
                    log.error("不支持的文件格式:{}", fileExtension);
                    throw new IllegalArgumentException("不支持的文件格式");
                }
            }catch (Exception e) {
                log.error("照片更新失败");
                throw new GlobalException("照片更新失败");
            }
        }else {
            log.error("文件上传失败");
            throw new GlobalException("文件上传失败");
        }
        return endpoint+"/"+bucketName+"/"+filename;  //返回控制层拿到,传到注册方法中进行数据库更新
    }

}

controller层:

package com.person.blog.partal2.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.json.JSONUtil;
import com.person.blog.partal2.common.CommonResult;
import com.person.blog.partal2.controller.form.LoginForm;
import com.person.blog.partal2.controller.form.RegisterFrom;
import com.person.blog.partal2.pojo.User;
import com.person.blog.partal2.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;
import java.util.*;

	@RestController
	@RequestMapping("/user")
	@CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
	@Tag(name = "UserController", description = "用户管理接口")
	public class UserController {
	    @Autowired
	    private UserService userService;
		@PostMapping("/register")
	    @Operation(summary = "用户注册")
	    public CommonResult userRegister(@Valid @RequestBody RegisterFrom registerFrom) {
	        User user = new User();
	
	        user.setUsername(registerFrom.getUsername());
	        user.setPassword(registerFrom.getPassword());
	        user.setEmail(registerFrom.getEmail());
	        user.setAvatar(registerFrom.getAvatar());
	
	        Map<String, Object> temp = userService.register(user);
	        String result = (String) temp.get("result");
	        return CommonResult.ok().put(CommonResult.RETURN_RESULT, result);
	    }
    
    @PostMapping("/uploadAvatar")
    @Operation(summary = "更新头像")
    @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
    public CommonResult uploadAvatar(@Param("file") MultipartFile file) {
        String filename = userService.uploadAvatar(file);
        return CommonResult.ok().put("result",filename);
    }
}

相应的form表单:

package com.person.blog.partal2.controller.form;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

@Data
@Schema(description = "注册表单")
public class RegisterFrom {

    @Schema(description = "用户名")
    @NotBlank(message = "姓名不能为空")
    private String username;


    @Schema(description = "密码")
    @NotBlank(message = "密码不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{4,16}$", message = "密码不符合格式要求")
    private String password;

    @Schema(description = "邮箱")
    @NotBlank(message = "邮箱不能为空")
    @Pattern(regexp = "^([A-z0-9]{6,18})+@[A-z0-9]+\\.([A-z]{2,5})$", message = "邮箱不符合格式要求")
    private String email;

    @Schema(description = "角色")
    private int role;

    @Schema(description = "头像")
    @NotBlank(message = "头像不能为空")
    private String avatar;

}

2.3 vue项目中的图片上传代码

template部分:

<template>
  <div class="a1">
    <el-form ref="registerFormRef" :model="registerForm" :rules="rules" class="demo-ruleForm">
      <!-- 输入项:用户名 -->
      <el-form-item prop="username">
        <el-input style="padding-top: 20px" placeholder="用户名" v-model="registerForm.username" clearable :prefix-icon="User"></el-input>
      </el-form-item>
      <!-- 输入项:密码 -->
      <el-form-item prop="password">
        <el-input
          placeholder="密码"
          type="password"
          v-model="registerForm.password"
          clearable
          :prefix-icon="Lock"
        ></el-input>
      </el-form-item>
      <!-- 输入项:确认密码 -->
      <el-form-item prop="confirmPassword">
        <el-input
          placeholder="确认密码"
          type="password"
          v-model="registerForm.confirmPassword"
          clearable
          :prefix-icon="Lock"
        ></el-input>
      </el-form-item>
      <!-- 输入项:邮箱 -->
      <el-form-item prop="email">
        <el-input
          placeholder="邮箱"
          type="text"
          v-model="registerForm.email"
          clearable
          :prefix-icon="Lock"
        ></el-input>
      </el-form-item>

      <!-- 输入项:图片上传 -->
      <el-form-item label="头像" required prop="avatar">
        <el-upload
          :action="action"
          :on-success="handleAvatarSuccess"
          :on-error = "updatePhotoError"
          :before-upload = "beforeAvatarUpload"
          :show-file-list = "false"
          class="avatar-uploader"
          >
            <img v-if="registerForm.avatar" :src="registerForm.avatar" class="avatar">
            <el-icon v-else class="avatar-uploader-icon">
              <Plus/>
            </el-icon>
        </el-upload>

      </el-form-item>

      <!-- 注册按钮 -->
      <el-form-item>
        <el-button type="success" class="register_button" @click="register(registerFormRef)">注册</el-button>
      </el-form-item>

      <p style="padding-bottom: 20px">
        已有账号?
        <router-link to="/login" style="text-decoration: none">立即登录</router-link>
      </p>
    </el-form>
  </div>
</template>

script部分:

<script setup lang="js" charset="UTF-8">

import { ref,reactive } from 'vue'
import { ElMessage } from 'element-plus'
import {Lock, Plus, User} from '@element-plus/icons-vue'
import { reqRegister } from '@/api/login/index.js'
import {useRouter} from "vue-router";

// 图片上传的请求地址
let action = import.meta.env.VITE_APP_BASE_API + '/user/uploadAvatar'


let registerForm = ref({
  username: "",
  password: "",
  confirmPassword:"",
  email:"",
  avatar:""
})

const registerFormRef = ref()


//创建路由
const router = useRouter()
// 创建字段输入的规则
let rules = reactive({
  username: [
    {required: true, message: '请输入用户名', trigger: 'blur'},
    {min: 5, max: 16, message: '用户名长度为5~16位', trigger: 'blur'}
  ],
  password: [
    {required: true, message: '请输入密码', trigger: 'blur'},
    {min: 5, max: 16, message: '密码长度为5~16位', trigger: 'blur'},
  ],
  confirmPassword: [
    {required: true, message: '请输入确认密码', trigger: 'blur'},
    // 新增自定义验证规则
    {
      validator: (rule, value, callback) => {
        if (value === registerForm.value.password) {
          callback();
        } else {
          callback(new Error('密码和确认密码不一致,请重新输入'));
        }
      },
      trigger: 'blur'
    }
  ],
  email: [
    { required: true, message: '请输入邮箱', trigger: 'blur' },
    {
      type: 'email',  // 使用内置的验证类型来验证是否符合邮箱格式
      message: '请输入正确的邮箱格式',
      trigger: 'blur'
    }
  ],
  avatar: [
    { required: true, message: '请上传头像', trigger: 'blur' }
  ]
})

const register = async(formEl)=>{
  if ((!formEl)) return
  await formEl.validate(async (valid,fields)=>{
    if (valid) {
      let data = {
        username:registerForm.value.username,
        password:registerForm.value.password,
        confirmPassword:registerForm.value.confirmPassword,
        email:registerForm.value.email,
        avatar:registerForm.value.avatar
      }

      let res = await reqRegister(data)
      if (res.result != '用户名重复,不能重复添加') {
        ElMessage({
          type:'success',
          message:"注册成功!"
        })
        setTimeout(()=>{
          router.push('/login')
        },3000)
      }else{
        ElMessage({
          type: 'warning',
          message: "用户名重复,不能重复添加!"
        });
      }
    }else {
      console.log('error submit!',fields)
    }
  })
}


//图片上传成功的钩子
const handleAvatarSuccess = (result) => {
  console.log(result)

  registerForm.value.avatar = result.result  //后端返回给前端上传后的路径
  ElMessage({
    type:"success",
    message:"文件上传成功",
    duration:1200
  })
}

//上传图片之前触发的钩子函数
const beforeAvatarUpload = async (file) => {
  const allowedTypes = ['image/jpeg', 'image/png'];
  if (!allowedTypes.includes(file.type)) {
    ElMessage({
      type: 'error',
      message: '图片格式不对,请选择jpg或png格式的图片',
    });
    return false; // 返回false阻止文件上传
  }
  return true; // 格式正确,允许上传
}

//  图片上传失败后的回调函数
const updatePhotoError = ()=>{
  ElMessage({
    type:"error",
    message:"文件上传失败",
    duration:1200
  })
}

</script>

CSS部分的代码:

<!------------------
  样式修饰
-------------------->
<style>
	.a1 {
	  width: 400px;
	  margin: 0 auto;
	  margin-top: 50px;
	  background-color: #ffffff;
	  box-shadow: 10px 0px 10px gray;
	  padding-right: 20px;
	  padding-left: 20px;
	}
	
	body {
	  background-image: url("../../assets/login/bg.jpg");
	  background-repeat: no-repeat;
	  background-size: cover;
	}
	
	.register_button {
	  width: 100%;
	}
	
	router-link {
	  text-decoration: none;
	}
	
	.avatar-uploader .el-upload {
	  border: 1px dashed #d9d9d9;
	  border-radius: 6px;
	  cursor: pointer;
	  position: relative;
	  overflow: hidden;
	}
	.avatar-uploader .el-upload:hover {
	  border-color: #409EFF;
	}
	.avatar-uploader-icon {
	  font-size: 28px;
	  color: #8c939d;
	  width: 148px;
	  height: 138px;
	  line-height: 138px;
	  text-align: center;
	}
	.avatar {
	  width: 148px;
	  height: 138px;
	  display: block;
	}
</style>

上传图片的整体逻辑:

前端进行点击上传图片时请求后端写好的接口,后端进行处理(包括图片格式、大小的检查,以及图片上传到minio服务器上的名字和保存到数据库里的名字)上传到minio服务器上,并且返回图片的名字到前端,前端通过图片上传成功后的回调函数来拿到图片上传后的路径,来进行展示。
至此,完成图片上传到minio服务器上的整体流程
注:发现vue3的一个小bug
在项目中,必须有一个index.html在项目根目录下(不是src下,是直接在项目下),否则会打不开整个vue项目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值