JAVA零基础入门3-实现上传图片功能

完成了基础的增删改查,接下来我们开发一个新的功能-上传文件

2023/10/25

1.上传一个文件


首先,我们上传文件要新增一个按钮,并且要有一个文件上传框,默认是隐藏的;当我们点击上传按钮时,可以把文件上传框显示出来,这是基本的逻辑,接下来实现:

<button id="uploadbutton" onclick="document.getElementById('fileInput').click();">上传文件</button>)"
<input id="fileInput" th:type="file" style="display: none" onchange="upload()">

这里我们使用按钮的点击事件,当我们点击上传文件时,会自动获取到id为fileinput的输入框,此时它的隐藏状态虽然不会被改变,但是被选中了,所以会弹出来,然后我们接下来处理上传的逻辑,当我们选择好本地文件后,点击打开,需要触发一个change事件,JavaScript定义如下:

<script>
    function upload(){
        const fileInput = document.getElementById('fileInput');
        const formdata = new FormData();
        formdata.append('file',fileInput.files[0])
        fetch('/upload',{
            method:'POST',
            body:formdata
        }).then(res=>res.json())
            .then(res=>{
                console.log(res)
            })
            .catch(err=>{
                console.error("上传失败请重试"+err)
            })
    }
</script>

我们首先获取fileinput元素,然后定义一个formdata元素,这是常用来发送ajax请求的键值对对象,ajax是不用刷新整个页面就能获取服务器数据的好东西,formdata与model类似,我们定义它的file属性为fileinput的第一个文件数据,发送fetch请求(ajax的一种)路径是upload,方法为post,为什么不用get呢,因为get会把发送的东西附加在浏览器地址里,我们是文件不太合适,请求体设为formdata,一般不会变,后面两句是接收返回对象和打印。
 

前端写好了,我们先去数据库建立一整表,因为我们使用的是文件,字节文件,不能和cai放在一起了,建表语句为:

create table files(
id int auto_increment primary key,
filename varchar(255) not null,
filedata blob )


其中blob代表字节码文件
然后我们去新建一个实体类file,其中在java中我们定义为byte[]类型,对应数据库中的blob字节类型,

@Data
public class File {
    private int id;
    private String filename;
    private byte[] filedata;
}

有了file实体,定义它的mapper,这里我们定义一个sava方法,

@Mapper
public interface FileMapper {
    void save(File file);
}

定义service,注意一个点,我们在mapper和service中定义的文件类型都是File对象类型,不再是byte[]了,真正的文件类型只在实体类内部定义

@Service
public interface FileService extends FileMapper {
    void save(File file);
}

还是老方法,好习惯,我们使用service实现类定义真正的调用,service和mapper都只定义空方法,在实现接口中,调用mapper的方法,这里注意一个点,我们调用mapper方法,需要先自动注解,让spring可以识别到mapper,如果不写@Autowired 是无法运行的

public class FileServiceImpl implements FileService {
    @Autowired
    private FileMapper fileMapper;

    @Override
    public void save(File file) {
        fileMapper.save(file);
    }

}

处理对应的逻辑,定义@postmapping路径为upload,定义@ResponseBody返回体类型为http响应,否则只能返回视图名称,

这里我们把返回值类型改为ResponseEntity<Map<String, String>> 可以更容易地构建结构化的 JSON 响应,也就是我们最喜欢的键值对类型,一个名字一个值,首先定义response为hashmap键值对类型,接着trycatch,如果文件非空的话,新建一个文件对象,设置它的名字为我们file参数中的文件名,设置它的filedata为file参数中的获取字节数据方法,其中file设置为常用的文件上传MultipartFile 对象类型。设置号了file的数据库字段,我们调用save方法,保存到数据库,设置response的message属性的值,返回ok消息为response,如果不设为response就可以自定义别的,比如直接输出一段话;
如果文件为空,输出消息,返回错误请求

最后catch一个错误,使用log.error输出它,这个以后比较常用,但是注意要加@SLF4J注解才能使用log,返回错误信息。

    @PostMapping("/upload")
    @ResponseBody
    public ResponseEntity<Map<String, String>> upload(@RequestParam("file") MultipartFile file) {
        Map<String, String> response = new HashMap<>();
        try {
            if (!file.isEmpty()) {
                // 获取文件名
                File file1 = new File(); // 注意这里,建议重新命名您的File类
                file1.setFilename(file.getOriginalFilename());
                file1.setFiledata(file.getBytes());
                fileService.save(file1);
                response.put("message", "文件保存成功");
                return ResponseEntity.ok(response);
            } else {
                response.put("message", "上传的文件为空");
                return ResponseEntity.badRequest().body(response);
            }
        } catch (Exception e) {
            log.error("Error during file upload:", e);
            response.put("message", "上传失败");
            return ResponseEntity.badRequest().body(response);
        }
    }

注意不要忘了自动注解service接口,否则spring找不到它的实现类

    @Autowired
    private FileService fileService;

response是我们定义的一个hashmap键值对对象,理论上我们可以定义任何键值,常用的有message,error,data(返回数据),id,result(返回查询结果或列表),status(成功或者失败),当然我们也可以给自己定义一个加油打气:

以后,我们会逐渐使用这些大家通用的方法来替代我们原来的基础东西,慢慢成长

2023/10/28

2.上传一个菜品图片并显示

前面的代码中提到,如何上传一个文件到数据库,这里我们已经可以存到数据库了,任何文件都可以上传,我本以为显示会很简单,但是耗费了我好几天才归类出一个显示图片的办法,不同的文件不可以通用一个模块,本次我们首先讲解菜品图片的上传与显示。

首先,我们上传一张图片到files表中,因为它与菜品信息相关,所有我将cai的id设置为files的外键,其实files这里我们只当作caifiles就行了,专为cai而创建的图片文件,创建外键的好处就是当我们删除主表信息,也就是cai时,会自动删除files中对应外键的文件信息,接下来是建表语句:
 

ALTER TABLE files
ADD cai_id INT,
ADD FOREIGN KEY (cai_id) REFERENCES cai(id) on delete cascade

我们修改一点点前端的代码,让我们上传图片时,传入一个id信息,目前只修改查询的菜品部分,我们将上传按钮放到这里来,并且传输文件的同时,传输一个caiid,注意这里的传送方式,我们需要的是caiid的text,所以不能只使用dom的getelementbyid方法,同时还要获取它的textcontent元素,通过formdata传送controller
 

<!-- 展示查询菜品数据 -->
    <tr th:if="${caiinfo != null}">
        <td id="caiid" th:text="${caiinfo.id}"></td>
        <td th:text="${caiinfo.name}"></td>
        <td th:text="${caiinfo.price}"></td>
        <td th:text="${caiinfo.fenlei}"></td>
        <td>
            <img th:src="${imageURL}" alt="菜品图片" />
        </td>
        <td>
            <form th:action="@{/delete}" method="get">
                <input type="hidden" th:field="*{caiinfo.id}">
                <button type="submit" id="deletebutton">删除</button>
            </form>
        </td>
        <td>
            <button id="uploadbutton" onclick="document.getElementById('fileInput').click()" type="button">上传文件</button>
            <input id="fileInput" type="file" onchange="upload()" style="display: none">
        </td>
    </tr>
    </tbody>
</table>
<script>
    function upload(){
        const fileInput = document.getElementById('fileInput');
        const formdata = new FormData();
        const element = document.getElementById('caiid')
        const cid = element.textContent
        formdata.append('file',fileInput.files[0])
        formdata.append('id',cid)
        fetch('/upload',{
            method:'POST',
            body:formdata
        }).then(res=>res.json())
            .then(res=>{
                console.log(res)
            })
            .catch(err=>{
                console.error("上传失败请重试"+err)
            })
    }
</script>

然后修改一下controller的逻辑,我们分为两个方法,第一个方法用来查询菜品信息

    @GetMapping("/caiinfo")
    public String getCaiInfo(@RequestParam("id")int id,Model model){
        Cai cai = caiService.selectById(id);
        model.addAttribute("caiinfo",cai);
        model.addAttribute("imageURL","/image?id="+id);
        return "index";
    }

这里我们查询cai的info,同时我们设置一个imageURL属性的值返回给前端,当前端加载image URL时,会自动加载/image?id=#{id}路径,实现了我们使用菜品id查询文件的要求,然后写image路径的方法,来返回图片
 

    @GetMapping("/image")
    public ResponseEntity<ByteArrayResource> queryResult(@RequestParam("id") int id) {
        File file = fileService.selectfilebyid(id);
        ByteArrayResource resource = new ByteArrayResource(file.getFiledata());
        return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(resource);
    }

这里是难点,因为我们存储到数据库中使用的bolb也就是字节文件,此时我们需要设置responseEntity的类型为ByteArrayResource,也就是字节数组资源,这是将字节文件转化为输入流的方式,返回给前端,最后我们返回的时候使用ok的contentype方法,选择文件的类型,响应体为resource对象,最后前端显示图片的效果如下所示:

3.弹窗检查是否id已存在(小功能)

在成功上传图片后,我想到如果我再次上传会怎样,结果图片却无法显示了,检查了一会,发现原来我的外键没有设置唯一性约束

Alter TABLE files ADD UNIQUE (cai_id)

然后我再次上传图片时,发现图片不会消失了,因为有外键约束,无法正常上传,但是页面也没有任何反应,作为用户我肯定不希望出现这样的情况的,所以我想着能不能写一个检查id是否存在于files表中,如果不存在再进行上传操作,存在的话就要提示用户原因;id已存在。实现如下:

我们加入一个existbyid方法,同时生成service,和serviceImpl方法,要求返回一个Boolean类型,要么有,要么没有
​​​​​

#Mapper和Service    
 Boolean existsbyid(int id);
#ServiceImpl
 public Boolean existsbyid(int id) {
    return fileMapper.existsbyid(id);
 }

然后写sql语句,如果大于0就返回true

    <select id="existsbyid" resultType="java.lang.Boolean">
        select count(*)>0 from files where cai_id=#{id}
    </select>

我们重新处理一下上传到controller逻辑,如果id不存在,就更新文件,如果id存在,就设置message信息提示用户,最后在前端弹窗
controller修改部分

                if(!fileService.existsbyid(id)){
                    fileService.save(file1);
                    response.put("message", "文件保存成功");
                    response.put("评价","wa!这也太厉害了");
                    return ResponseEntity.ok(response);
                }
                response.put("message","此菜品已存在产品图");
                return ResponseEntity.badRequest().body(response);

前端修改部分

        fetch('/upload',{
            method:'POST',
            body:formdata
        }).then(res=>res.json())
            .then(res=>{
                console.log(res)
                alert(res.message)
            })

效果如下,虽然我们肉眼就能看到有图了,但是哈哈万一忍不住手贱再上传一次,总得有点提示把,反正我目前觉得有用

2023/10/30

4.规范化代码

(解决table显示问题和只能上传第一个id的文件问题)

在上面的代码中,我们成功针对一条菜品上传了文件,并在前端显示出来,后来我发现当我上传第2个id的文件时,仍然传递的是第一个id信息,苦思冥想,发现在我们的删除和上传文件按钮中id为固定值,每次获取到的按钮信息为同一个按钮,同时,之前我们传递菜品id时,使用的也是固定id的方式,因此统统需要改为动态显示,此一部分只涉及前端修改,后端逻辑不变,同时将table的显示也改为更合理一些,前端代码如下:

#//此为table全部菜品部分 
   <tr th:each="cai : ${caiList}">
        <td th:text="${cai.id}" th:id="'cid'+cai.id"></td>
        <td th:text="${cai.name}"></td>
        <td th:text="${cai.price}"></td>
        <td th:text="${cai.fenlei}"></td>
        <td>
            <img th:src="${imageURL+cai.id}" alt="菜品图片" />
        </td>
        <td>
            <button type="button" th:id="'deletebutton'+${cai.id}" th:data-id="${cai.id}" onclick="deleteCai(this)">删除</button>
        </td>
        <td>
            <button th:id="'uploadbutton'+cai.id"
                    th:attr="onclick='document.getElementById(\'' + 'fileInput' + ${cai.id} + '\').click()'"
                    type="button">上传文件</button>
            <input th:id="'fileInput' + ${cai.id}" th:data-id="${cai.id}" type="file" onchange="uploadFile(this)" style="display: none">

        </td>
    </tr>

<script th:inline="javascript">
    function deleteCai(button){
        const cid = button.getAttribute('data-id');
        const formdata = new FormData()
        formdata.append('id',cid)
        fetch('delete',{
            method:'POST',
            body:formdata
        }).then(res=>res.json())
            .then(res=>{alert(res.message)
                location.reload();
                })
        console.log()
    }
    function uploadFile(inputElement) {
        console.log(inputElement);
        const caiId = inputElement.getAttribute('data-id');
        const formdata = new FormData();
        formdata.append('file', inputElement.files[0]);
        console.log(caiId)
        // 获取菜品ID并添加到表单数据中
        formdata.append('id', caiId);
        fetch('/upload',{
            method:'POST',
            body:formdata
        }).then(res=>res.json())
            .then(res=>{
                console.log(res)
                alert(res.message)
                location.reload();
            })
            .catch(err=>{
                console.error("上传失败请重试"+err)
            })
    }
</script>

在前端我们尽量已显示全部菜品为主要页面,加入了一个展示菜品的按钮,后端不再以默认路径/来查询全部,更改为了/pageInfo

#前端
<button id="pageinfo" onclick="page()">展示菜品</button>
<script>
    function page() {
        window.location.href = '/pageInfo';
    }
</script>
#后端
    @GetMapping ("/pageInfo")
    public String page(Model model){
        List<Cai> caiList = caiService.selectAll();
        model.addAttribute("caiList",caiList);
        model.addAttribute("imageURL","/image?id=");
        return "index";
    }

当我们做了修改后,使用reload函数,重新加载即可,修改后的实现,更加合理化,展示如下:

本章节主要实现以下功能:

1.对应id信息来上传图片

2.前端显示对应id的图片信息

3.检查是否已经存在图片信息并弹窗

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java 上传图片并生成缩略图的步骤如下: 1. 获取上传的图片文件流。 2. 根据图片文件流,创建一个图片对象(BufferedImage)。 3. 创建一个指定大小的缩略图(BufferedImage)对象。 4. 使用Graphics2D对象的drawImage方法将原始图片绘制在缩略图上,并按照指定大小进行缩放。 5. 将缩略图保存到指定路径。 下面是一个示例代码: ```java import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; public class ImageUtil { /** * 生成缩略图 * @param srcImagePath 原始图片路径 * @param destImagePath 缩略图保存路径 * @param width 缩略图宽度 * @param height 缩略图高度 * @throws IOException */ public static void createThumbnail(String srcImagePath, String destImagePath, int width, int height) throws IOException { // 读取原始图片 File srcFile = new File(srcImagePath); BufferedImage srcImage = ImageIO.read(srcFile); // 创建缩略图 BufferedImage thumbnail = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = thumbnail.createGraphics(); g2d.drawImage(srcImage, 0, 0, width, height, null); // 保存缩略图 File destFile = new File(destImagePath); ImageIO.write(thumbnail, "JPEG", destFile); } } ``` 使用示例: ```java public class Main { public static void main(String[] args) throws IOException { String srcImagePath = "D:/images/test.jpg"; String destImagePath = "D:/images/test_thumbnail.jpg"; int width = 100; int height = 100; ImageUtil.createThumbnail(srcImagePath, destImagePath, width, height); } } ``` 上面的代码可以将原始图片缩放为指定大小的缩略图,并保存到指定路径。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值