云计算百科
云计算领域专业知识百科平台

今日学习(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方法会根据成员变量的值计算出一个哈希码。
*/

其他:

  • @NoArgsConstructor:生成一个包含所有成员变量的构造函数。
  • @AllArgsConstructor:
  • @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 yumutils devicemapperpersistentdata lvm2
    添加 Docker 软件仓库:
    sudo yumconfigmanager addrepo https://download.docker.com/linux/centos/dockerce.repo

    安装 Docker
    sudo yum install dockerce
    启动 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">
    <elform ref="registerFormRef" :model="registerForm" :rules="rules" class="demo-ruleForm">
    <! 输入项:用户名 >
    <elformitem prop="username">
    <elinput style="padding-top: 20px" placeholder="用户名" vmodel="registerForm.username" clearable :prefixicon="User"></elinput>
    </elformitem>
    <! 输入项:密码 >
    <elformitem prop="password">
    <elinput
    placeholder="密码"
    type="password"
    vmodel="registerForm.password"
    clearable
    :prefixicon="Lock"
    ></elinput>
    </elformitem>
    <! 输入项:确认密码 >
    <elformitem prop="confirmPassword">
    <elinput
    placeholder="确认密码"
    type="password"
    vmodel="registerForm.confirmPassword"
    clearable
    :prefixicon="Lock"
    ></elinput>
    </elformitem>
    <! 输入项:邮箱 >
    <elformitem prop="email">
    <elinput
    placeholder="邮箱"
    type="text"
    vmodel="registerForm.email"
    clearable
    :prefixicon="Lock"
    ></elinput>
    </elformitem>

    <! 输入项:图片上传 >
    <elformitem label="头像" required prop="avatar">
    <elupload
    :action="action"
    :onsuccess="handleAvatarSuccess"
    :onerror = "updatePhotoError"
    :beforeupload = "beforeAvatarUpload"
    :showfilelist = "false"
    class="avatar-uploader"
    >
    <img vif="registerForm.avatar" :src="registerForm.avatar" class="avatar">
    <elicon velse class="avatar-uploader-icon">
    <Plus/>
    </elicon>
    </elupload>

    </elformitem>

    <! 注册按钮 >
    <elformitem>
    <elbutton type="success" class="register_button" @click="register(registerFormRef)">注册</elbutton>
    </elformitem>

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

    script部分:

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

    import { ref,reactive } from 'vue'
    import { ElMessage } from 'elementplus'
    import {Lock, Plus, User} from '@elementplus/iconsvue'
    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项目

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 今日学习(2024-12-23):Lombok、SpringBoot+Vue3实现图片上传到minio对象存储服务器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!