一、图片文件上传
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);
}
(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);
}
测试效果展示:
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);
}
测试效果展示:
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 "修改成功";
}