Vue+SpringBoot+OSS+element实现图片上传服务器+图片展示墙

1、技术选型:

(1)Vue(前端)
(2)Element(上传图片组件)
(3)SpringBoot(后端)
(4)Mybatis(数据库操作)
(3)OSS(阿里云存储服务器)

2、实现效果概述:

(1)图片展示墙(做了一个简单的,没有太花里胡哨)

(2)上传页面

Vue+SpringBoot+OSS+element实现图片上传服务器+图片展示墙_第1张图片

3、项目概述:

注意这是一个练习项目,练习项目,练习项目,可能存在一些小问题!

简单来说,

=(1)图片上传逻辑:=

前端通过elementUI的上传图片的组件将图片进行上传,后端利用SpringBoot写的后端逻辑接受了前端穿过的图片,首先将图片上传到阿里云的OSS对象存储系统上,然后返回图片的地址存储到数据库。

=(2)图片展示墙逻辑:=

通过查询数据库的图片地址返回给前端,然后前端通过for循环,利用img标签对图片进行循环遍历然后显示!

项目简单,易于实现,全代码黏贴!!!!!!

4、图片上传

(1)前端项目结构:

Vue+SpringBoot+OSS+element实现图片上传服务器+图片展示墙_第2张图片
(2)ImagesUpload.vue

=补充:这里主要是借助了elementUI的上传图片组件!!!!
这里只需要将action中的地址改成自己的后端请求地址就可以!!!
=

<template>
    <div>
        <el-upload
                action="http://localhost:8086/images/upload"
                list-type="picture-card"
                :on-preview="handlePictureCardPreview"
                :on-remove="handleRemove">
            <i class="el-icon-plus"></i>
        </el-upload>


    </div>

</template>

<el-dialog :visible.sync="dialogVisible">
    <img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>

<script>
    export default {
        name: "images-upload",

        data() {
            return {
                dialogImageUrl: '',
                dialogVisible: false
            };
        },
        methods: {
            handleRemove(file, fileList) {
                console.log(file, fileList);
            },
            handlePictureCardPreview(file) {
                this.dialogImageUrl = file.url;
                this.dialogVisible = true;
            }
        }
    }
</script>

<style scoped>

</style>

==前端逻辑完成,开始后端逻辑=

(3)安装阿里云OSS所需依赖:

	<dependency>
			<groupId>com.aliyun.oss</groupId>
			<artifactId>aliyun-sdk-oss</artifactId>
			<version>2.8.3</version>
		</dependency>

(4)OSS工具类封装:

=这里根据自己的实际情况合理选择域名,访问用户,秘钥…=

package com.blog.util;

/**
 * @author 
 * @version 1.0
 * @description TODO
 * @date 2023/3/12 8:28
 */


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.Date;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectResult;

public class OSSClientUtil {

 /**
  * log日志
  */
 public static final Logger logger = LoggerFactory.getLogger(OSSClientUtil.class);
 /**
  * 访问域名
  */
 private String endpoint = "oss-cn-beijing.aliyuncs.com";
 /**
  * accessKey访问秘钥
  *  访问用户
  */
 private String accessKeyId = "########";
 /**
  * 密钥
  */
 private String accessKeySecret = "################";
 /**
  * 空间  名称
  */
 private String bucketName = "############";
 /**
  *  文件存储目录
  */
 private String filedir = "###########/";

 private OSSClient ossClient;

 public OSSClientUtil() {

  ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
 }

 /**
  * 初始化
  */
 public void init() {
  ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
 }

 /**
  * 销毁
  */
 public void destory() {

  ossClient.shutdown();
 }

 /**
  * 上传图片
  *
  * @param url
  * @throws
  */
 public void uploadImg2Oss(String url) throws IOException {
  File fileOnServer = new File(url);
  FileInputStream fin;
  try {
   fin = new FileInputStream(fileOnServer);
   String[] split = url.split("/");
   this.uploadFile2OSS(fin, split[split.length - 1]);
  } catch (FileNotFoundException e) {
   throw new IOException("图片上传失败");
  }
 }

 public String uploadImg2Oss(MultipartFile file) throws IOException {
  if (file.getSize() > 10 * 1024 * 1024) {
   throw new IOException("上传图片大小不能超过10M!");
  }
  String originalFilename = file.getOriginalFilename();
  String substring = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
  Random random = new Random();
  String name = random.nextInt(10000) + System.currentTimeMillis() + substring;
  try {
   InputStream inputStream = file.getInputStream();
   this.uploadFile2OSS(inputStream, name);
   return name;
  } catch (Exception e) {
   throw new IOException("图片上传失败");
  }
 }

 /**
  * 获得图片路径
  *
  * @param fileUrl
  * @return
  */
 public String getImgUrl(String fileUrl) {
  System.out.println(fileUrl);
  if (!StringUtils.isEmpty(fileUrl)) {
   String[] split = fileUrl.split("/");
   return this.getUrl(this.filedir + split[split.length - 1]);
  }
  return "" ;
 }

 /**
  * 上传到OSS服务器 如果同名文件会覆盖服务器上的
  *
  * @param instream
  *            文件流
  * @param fileName
  *            文件名称 包括后缀名
  * @return 出错返回"" ,唯一MD5数字签名
  */
 public String uploadFile2OSS(InputStream instream, String fileName) {

  String ret = "";
  try {
   // 创建上传Object的Metadata
   ObjectMetadata objectMetadata = new ObjectMetadata();
   objectMetadata.setContentLength(instream.available());
   objectMetadata.setCacheControl("no-cache");
   objectMetadata.setHeader("Pragma", "no-cache");
   objectMetadata.setContentType(getcontentType(fileName.substring(fileName.lastIndexOf("."))));
   objectMetadata.setContentDisposition("inline;filename=" + fileName);
   // 上传文件
   PutObjectResult putResult = ossClient.putObject(bucketName, filedir + fileName, instream, objectMetadata);
   ret = putResult.getETag();
  } catch (IOException e) {
   logger.error(e.getMessage(), e);
  } finally {
   try {
    if (instream !=null ) {
     instream.close();
    }
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
  return ret;
 }

 /**
  * Description: 判断OSS服务文件上传时文件的contentType
  *
  * @param
  *
  * @return String
  */
 public static String getcontentType(String filenameExtension) {
  if (filenameExtension.equalsIgnoreCase("bmp")) {
   return "image/bmp";
  }
  if (filenameExtension.equalsIgnoreCase("gif")) {
   return "image/gif";
  }
  if (filenameExtension.equalsIgnoreCase("jpeg") || filenameExtension.equalsIgnoreCase("jpg")
          || filenameExtension.equalsIgnoreCase("png")) {
   return "image/jpeg";
  }
  if (filenameExtension.equalsIgnoreCase("html")) {
   return "text/html";
  }
  if (filenameExtension.equalsIgnoreCase("txt")) {
   return "text/plain";
  }
  if (filenameExtension.equalsIgnoreCase("vsd")) {
   return "application/vnd.visio";
  }
  if (filenameExtension.equalsIgnoreCase("pptx") || filenameExtension.equalsIgnoreCase("ppt")) {
   return "application/vnd.ms-powerpoint";
  }
  if (filenameExtension.equalsIgnoreCase("docx") || filenameExtension.equalsIgnoreCase("doc")) {
   return "application/msword";
  }
  if (filenameExtension.equalsIgnoreCase("xml")) {
   return "text/xml";
  }
  return "image/jpeg";
 }

 /**
  * 获得url链接
  *
  * @param key
  * @return
  */
 public String getUrl(String key) {
  // 设置URL过期时间为10年 3600l* 1000*24*365*10

  Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 10);
  // 生成URL
 // URL url = ossClient.generatePresignedUrl(bucketName, key, expiration);
  String url = "https://"+bucketName+"."+endpoint+"/"+key;
  if (url != null) {
   return url.toString();
  }
  return  "";
 }
}


(5)Controller层:

package com.blog.controller.Vue;

import com.blog.pojo.Images;
import com.blog.service.ImagesService;
import com.blog.util.FileUtil;
import com.blog.util.OSSClientUtil;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author 
 * @version 1.0
 * @description TODO
 * @date 2023/3/11 10:39
 */

@RestController
@RequestMapping("/images")
public class UploadController {

    @Autowired
    ImagesService imagesService;

    @PostMapping("/upload")
    public Map<String, Object> fileupload(MultipartFile file, HttpServletRequest req) throws IOException {
        if (file == null || file.getSize() <= 0) {
            throw new IOException("file不能为空");
        }
        //获取文件的大小,单位/KB
        long size = file.getSize();
        OSSClientUtil ossClient=new OSSClientUtil();
        //将文件上传
        String name = ossClient.uploadImg2Oss(file);
        //获取文件的URl地址  以便前台  显示
        String imgUrl = ossClient.getImgUrl(name);
        HashMap<String, Object> map=new HashMap<>();
        //文件存储的路径
        map.put("name", imgUrl);
        Images images = new Images();
        images.setImg(imgUrl);
        System.out.println("添加数据");
        imagesService.insert(images);
        return map ;

    }

}

(6)Service层:

package com.blog.service;

import com.blog.pojo.Images;

import java.util.List;

public interface ImagesService {
    int insert(Images images);
    List<Images> getImages();
}

(7)ServiceImpl:

package com.blog.service.impl;

import com.blog.dao.ImagesDao;
import com.blog.pojo.Images;
import com.blog.service.ImagesService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author 
 * @version 1.0
 * @description TODO
 * @date 2023/3/12 10:38
 */
@Service
public class ImagesServiceImpl implements ImagesService {
 @Autowired
 ImagesDao imagesDao;

 @Override
 public int insert(Images images) {
  return imagesDao.insert(images);
 }

 @Override
 public List<Images> getImages() {
  return imagesDao.getImages();
 }
}

(8)Dao层:

package com.blog.dao;


import com.blog.pojo.Images;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface ImagesDao {
    int insert(Images images);
    List<Images> getImages();
}

(9)xml层:

<?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.blog.dao.ImagesDao">

    <resultMap id="images" type="Images">
        <id property="id" column="id"/>
        <result property="img" column="img"/>

    </resultMap>

    <insert id="insert" parameterType="Images">
        insert into t_images (img)
        values (#{img});
    </insert>

    <select id="getImages" resultMap="images">
        select * from t_images
    </select>
</mapper>

(10)实体类:

package com.blog.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 
 * @version 1.0
 * @description TODO
 * @date 2023/3/12 10:33
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Images {
 private Integer id;
 private String img;
}

5、照片墙功能:

(1)前端项目结构:

Vue+SpringBoot+OSS+element实现图片上传服务器+图片展示墙_第3张图片

(2)Waterfall组件(主要是通过一个瀑布流图片展示组件来实现)

<template>
    <div class="m-waterfall-wrap" :style="`background: ${backgroundColor}; width: ${width}px; padding: ${columnGap}px; column-count: ${columnCount}; column-gap: ${columnGap}px;`">
        <div class="m-img" :style="`margin-bottom: ${columnGap}px;`" v-for="(item, index) in imageData" :key="index">
            <img class="u-img" :src="item.img" :title="item.title" :alt="item.title" />
        </div>
    </div>
</template>
<script>
    /*
      纯CSS,实现简单,但图片顺序是每列从上往下排列
    */
    export default {
        name: 'Waterfall',
        props: {
            imageData: { // 瀑布流的图片数组
                type: Array,
                required: true,
                default: () => {
                    return []
                }
            },
            columnCount: { // 瀑布流要划分的列数
                type: Number,
                default: 3
            },
            columnGap: { // 瀑布流各列之间的间隙
                type: Number,
                default: 30
            },
            totalWidth: { // 瀑布流区域的总宽度
                type: Number,
                default: 1200
            },
            backgroundColor: { // 瀑布流区域背景填充色
                type: String,
                default: '#F2F4F8'
            }
        },
        computed: {
            width () {
                 return this.totalWidth - 2 * this.columnGap

            }
        }
    }
</script>
<style lang="less" scoped>
    .m-waterfall-wrap {
        .m-img {
            .u-img {
                width: 100%;
                vertical-align: bottom;
            }
        }

    }
</style>

(3)ImagesShow.vue(照片墙显示页面,借助于上一个瀑布流图片展示组件)

=①在这里需要注意路径问题,这里./代表了本项目所在的目录,在引入组件地址的时候,要根据自己项目的实际路径合理改变!=

<template>
    <div style="background-color: #fffef9">
        <p style="text-align: center;font-size: 24px;font-family: 'Adobe 黑体 Std R'" >图片展示墙</p>
        <Waterfall
                :imageData="images"
                :columnCount="3"
                :columnGap="30"
                :totalWidth="totalWidth"
                style="margin: 0 auto"
                backgroundColor="#f6f5ec" />


    </div>
</template>
<script>
    import Waterfall from '../../components/Waterfall.vue'
    import {list} from '../../api/images';
    
    export default {
        name: "images-show",
        components:{
            Waterfall
        },
        data(){
            return{
                //screenWidth: document.body.clientWidth, // 屏幕宽度
                totalWidth: document.body.clientWidth, // 屏幕高度
                images:[],
            }
        },
        mounted() {
            window.screenWidth = document.body.clientWidth;
            window.screenHeight = document.body.clientHeight;
            this.screenWidth = window.screenWidth;
            this.screenHeight = window.screenHeight;

            console.log(window.screenWidth);
            console.log(window.screenHeight);
            console.log(this.screenWidth);
            console.log(this.screenHeight);
        },
        created(){
          this.test();
        },
        methods:{
            test(){
                console.log("测试")
                list().then(res=>{
                    this.images = res.data;
                    console.log(res)
                })
            },

        }

    }
</script>

<style scoped>

</style>

(4)images.js

import request from '@/request/request'
export function list() {
    return request({
        url: '/imagesList/list',
        method: 'get',
    })
}

=配置到这里,那么前端核心的逻辑已经写完了=


(5)ImagesController

package com.blog.controller.Vue;

import com.blog.pojo.Images;
import com.blog.service.ImagesService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author 
 * @version 1.0
 * @description TODO
 * @date 2023/3/12 11:00
 */
@RestController
@RequestMapping("/imagesList")
public class ImagesController {
	@Autowired
	ImagesService imagesService;


	@GetMapping("/list")
	public List<Images> getImagesList(){
		List<Images> images = imagesService.getImages();
		return images;
	}

}

(6)关于service层、Dao层、实体类都在图片上传的ImagesService里,在这里我就不重复添加了!!!

6、总结:

(1)利用了vue、element组件搭建了前端
(2)利用SpringBoot、Mybatis实现了后端
(3)借助OSS组件将图片上传到了阿里云OSS

7、补充:

如果你对阿里云的OSS不太熟悉,那么给你推荐几篇博客,加速理解。

=(1)阿里云OSS注册及功能详解:=

https://blog.csdn.net/weixin_52851967/article/details/126924361

=(2)SpringBoot整合阿里云OSS云存储=

https://blog.csdn.net/qq_43021813/article/details/82665394

=(3)可能碰到的问题,以我为例,我创建的是私有Bucket,所以只有带着秘钥的时候才能访问,只根据图片路径而不带秘钥是无法访问的,这篇博客包含了我遇到的一些问题!=

https://blog.csdn.net/H_Q_Li/article/details/126471699

Vue+SpringBoot+OSS+element实现图片上传服务器+图片展示墙_第4张图片

你可能感兴趣的:(vue,element,前端技术学习,vue.js,spring,boot,服务器)