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方法会根据成员变量的值计算出一个哈希码。
*/
其他:
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项目
评论前必须登录!
注册