手把手教你云相册项目建议开发day3-上传下载业务逻辑之上传业务

工具类等完整代码都可以去github上获取,地址:https://github.com/kkoneone11/cloud-photo

图片上传流程图 

 流程解释(粗体的都是要写的大致接口方法):

用户要上传文件,则先写一个接口用来获得上传地址,然后再通过一个Md5工具类根据文件的属性信息进行生成一个唯一的Md5,然后判断是否是需要秒传,如果是,即存在数据库中,则返回秒传标志(文件),如果不是的话则证明此文件之前没有上传过,即不存在数据库则根据Md5和地址工具类生成一个唯一的文件上传地址(专门供给这个照片的)供用户上传文件到,将上传照片资源到资源池minio,最后再写一个接口用来提交上传,即将存储文件相关信息存储到到数据库。同时无论是否是秒传都要将将消息入库到kafka队列,一个是存放图片缩略图方便管理员展示的时候使用。另一个队列是审核队列用来审核图片是否可以观看

会操作到的数据库表

  •  用户文件表:保存这个用户和其存储了的文件的相关信息,文件信息(文件名、文件大小、分类等信息),查询用户文件列表
  • 文件存储信息表:文件保存在哪,即资源池存储信息,通过桶Id和存储池文件id可以唯一识别该文件,通过存储信息可以生成下载地址,通过storage_object_ID、文件表和文件MD5关联
  • 文件MD5表:保存了文件的相关属性,保存文件MD5,校验文件秒传用。

开发大体步骤 :

1.基础配置

1.1相关配置

先创建一个common项目,然后里面导入相关的工具包,代码我已经放到了github上。可以自取。然后再创建一个trans子项目在pom里导入common子项目。接下来的接口都在trans上进行开发

注意!!!:使用getById方法的时候。在表生成的实体类上主键要加上以下代码,否则会报错

@TableId(type = IdType.ASSIGN_UUID)

 1.2

新建启动类,启动类扫描上配置Mapper文件 @MapperScan(basePackages = {"com.cloud.photo.trans.mapper"})

1.3

用代码生成工具类根据数据库表一键生成代码  "tb_user_file","tb_storage_object","tb_file_md5",记得根据自己的实际情况修改配置

1.4

配置一下application.yml文件。trans的端口是9006

2.接口开发

2.1接口1:获得上传地址 /trans/getPutUploadUrl   Get

1.参数分析:需要根据文件属性生成唯一地址(fileName、fileSize、fileMd5)(fileSize、fileMd5非必要,其实fileName也非必要,因为传进去只是为了方便拿到后缀名)、且需要知道是哪个用户生成的(userId,非必要)、用来判断是否已经生成过而进行秒传(fileMd5,非必要)。都是非必要的原因是地址的生成是根据objectId,而其又是UUID,随机且唯一,基本不会冲突。因此地址都是唯一

2.先写一个PutUploadUrlController,然后写获得上传地址方法getPutUploadUrl(),然后业务逻辑是要根据Md5的值判断,所以创建Service类进行逻辑判断并传入文件相关的信息,其中Service类要调用FileMd5接口根据Md5查询数据库中是否存在这个文件。判断的时候又得根据getOne方法去获得,如果没有则传入fileName然后调用S3工具类生成一个上传地址并返回

3.测试:trans/getPutUploadUrl。

测试阶段filesize、md5要用工具生成。现在这是一个未上传过的照片所以肯定是返回地址。然后返回的uri就是我们所需要上传文件的地址,base64就是服务端生成的,后面这两个需要用到!!

package cloud.photo.common.utils;

import org.apache.commons.codec.digest.DigestUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Md5Util {

    public static void main(String[] args) {
        String filePath = "C:\\Users\\admin\\Postman\\files\\test6.png";
        String md5 = getFileMd5(filePath);
        System.out.println(md5);
        System.out.println(new File(filePath).length());
    }

    public static String getFileMd5(String filePath){
        String md5 = null;
        try {
            md5 = DigestUtils.md5Hex(new FileInputStream(filePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return md5;
    }
}

测试:上传资源到资源池

1.在接口1我们获得到了一个地址然后我们复制截取uri的部分,往里发一个post请求,相当于传入文件。这里是往minio或者华为云obs中存入文件

2.选择二进制文件,上传当时文件里已经计算好了的那张照片。(注意!!!这里要用PUT请求而不是POST,虽然POST也能提交成功但返回值有问题)

 再将接口1中返回的base64Md5的值按照以下的形式填入到请求头里面

可以看到已经有了一条信息

2.2接口2:提交存储文件 文件入库、发送审核、图片消息到Kafka /trans/commit  Post

1.参数分析:首先要知道谁传入了这个文件(userId),文件的信息(fileName fileSize fileMd5)、传去哪个资源池(containerId)、在资源池中哪个是它(objectId)、存储的地址方便拿到数据(uploadUrl)、文件或者照片是什么类型(category)、存储的时间(uploadTime)、文件的状态正常或者删除(statue)、base64文件md5(base64Md5)、storageObjectId是用来连接用户文件表和文件存储信息表且用来判断入库(storageObjectId)。这里参数比较多因此我们封装成一个请求体FileUpLoadBo类

2.这一步是根据获得上传地址接口再继续往下判断的,根据StoreObjectId判断是否秒传,因为已经存在了的话肯定会已经入库,而不根据md5是因为有可能生成了但还没入库,如果

  • 是秒传(数据库中存在)的话则也要通过storageObjectId检查到底是否上传的是同一份文件,然后也要判断秒传文件大小是否相同(storage_object_id字段是已经传过才会生成的)最后再将传入一条图片处理消息和一条审核处理消息到kafka
  • 非秒传的话(数据库中不存在)则通过s3工具通过ObjectId校验已经上传和上传文件是否相同。最后上传成功的文件要分别在FileMd5数据库和StrorageObject表进行入库

3.继续在PutUploadUrlController中写一个commit接口,接口里能处理一个秒传和一个非秒传的操作,这里的逻辑判断部分同样是在PutUploadUrlService中实现,然后Controller部分调用即可

4.在判断完是否需要秒传之后,分别在在PutUploadUrlService中实现一个commit和一个commitTransSecond方法,用来非秒传和秒传。在非秒传和秒传里的业务逻辑还需要判断

  • 秒传:1.根据StorageObjectId判断上传的和数据库数据是否一致 2.根据fileMd5和size判断上传文件是否一致
  • 非秒传:1.根据objectId判断有没有上传 2.根据fileMd5和size判断上传文件是否一致

5.最后面都分别对UserFile表、MD5表、StorageObject表入库即可。而UserFile入库的时候注意还需要对Kafka分别发送一条审核消息和一条生成缩略图消息供后续处理,因此需要写一个saveAndFileDeal方法独立处理

注意在创建StorageObject和FileMd5的时候要加一个无参的构造方法,不然会报错

package cloud.photo.common.bo;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

/**
 * 上传文件信息体
 * @author linzsh
 */
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FileUploadBo {

    /**
     * 文件名
     */
    private String fileName;
    /**
     * 文件大小
     */
    private Long fileSize;
    /**
     * 文件MD5
     */
    private String fileMd5;
    /**
     * 后面拼接用户ID
     */
    private String userId;
    /**
     * 对象ID
     */
    private String objectId;
    /**
     * 资源池ID
     */
    private String containerId;
    /**
     * 对象ID(秒传用)
     */
    private String storageObjectId;
    /**
     * 上传地址
     */
    private String uploadUrl;
    /**
     * 分类
     */
    private Integer category;

    /**
     * base64文件md5
     */
    private String base64Md5;

    /**
     * 上传状态
     */
    private String status;

    /**
     * 上传时间
     */
    private String uploadTime;
}

这里其实Getmapping也可以的,因为GetMapping = RequestMapping+Get,而HttpServlet也不是必须的只是为了方便打印请求的id和传回的请求体

6.测试:/trans/commit

2.3接口3:文件下载接口 /trans/getDownloadUrlByFileId Get

1.参数分析:是通过UserFile表去查相关文件信息,因此UserIdfileId可以唯一确定一个文件。因为可以看S3工具类里的getDownloadUrl方法,封装好了一个containerId和一个objectId,因此我们只需要在业务层通过fileId在UserFile表拿到StorageObjectId然后去这张表里拿到这两个参数进行查询地址返回回来即可。

2.写一个DownloadController,在里面写getDownloadUrlByFileId方法,同样也要编写IDownloadService和DownloadServiceImpl方法,在里面实现逻辑判断

3.测试:

2.4接口4:文件列表查询接口 /trans/userFilelist   

1.参数分析:因为是查询的文件列表,所以肯定需要当前用户是谁(userId)然后在UserFile表里就能根据userId查到其对应下的文件即可然后还需要当前页(current)每一页展现的个数(PageSize)、同时还需要一个分类(category),因此我们封装一个AlbumPageBo类

2.在UserFileController写一个userFilelist方法

package com.cloud.photo.trans.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cloud.photo.common.bo.AlbumPageBo;
import com.cloud.photo.common.common.ResultBody;
import com.cloud.photo.trans.entity.UserFile;
import com.cloud.photo.trans.service.IUserFileService;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author kkoneone11
 * @since 2023-07-20
 */
@Controller
@RequestMapping("/trans")
public class UserFileController {
    @Autowired
    IUserFileService iUserFileService;

    @RequestMapping("/userFilelist")
    public ResultBody userFilelist(HttpServletRequest request , HttpServletResponse response,
                                   @RequestBody AlbumPageBo albumPageBo){
        //设置QueryWrapper
        QueryWrapper<UserFile> qw = new QueryWrapper<>();
        //通过HashMap组装多个条件Wrapper
        HashMap<String,Object> hm = new HashMap<>();
        if(albumPageBo.getCategory()!=null){
            hm.put("category" , albumPageBo.getCategory());
        }
        hm.put("userId", albumPageBo.getUserId());
        qw.allEq(hm);
        Integer pageSize = albumPageBo.getPageSize();
        Integer current = albumPageBo.getCurrent();
        if(current == null) current = 1;
        if(pageSize == null ) pageSize = 20;
        //组装一下page
        Page<UserFile> page = new Page<UserFile>(current, pageSize);
        IPage<UserFile> userFilePage = iUserFileService.page(page, qw.orderByDesc("user_id", "create_time"));

        return ResultBody.success(userFilePage);
    }
}

3.测试

2.5接口5:更新文件审核状态接口 /trans/updateUserFile

1.参数分析:更新文件审核这个业务很明显需要操作的字段有状态(status)、根据存储池信息(storageObjectId)字段进行查询然后更新状态

2.在UserFileController写updateUserFile方法,可以分别根据StorageObjectId和userfileid来执行更新语句,用updateWrapper

@RequestMapping("/updateUserFile")
    public Boolean updateUserFile(HttpServletRequest request, HttpServletResponse response,
                                     @RequestBody List<UserFile> userFileBoList){
        //打印这次请求信息
        String requestId = RequestUtil.getRequestId(request);
        RequestUtil.printQequestInfo(request);

        //取出每个userFile分别进行更新
        for(UserFile userFile : userFileBoList){
            UpdateWrapper<UserFile> updateWrapper = new UpdateWrapper<>();
            //根据StorageObjectId条件更新审核
            if(StringUtils.isNotBlank(userFile.getStorageObjectId())){
                updateWrapper.eq("storage_object_id",userFile.getStorageObjectId());
            }
            //添加userfileid条件更新审核
            if(StringUtils.isNotBlank(userFile.getUserFileId())){
                updateWrapper.eq("user_file_id",userFile.getUserFileId());
            }
            //设置更新的状态
            updateWrapper.set("audit_status",userFile.getAuditStatus());
            //执行pdateWrapper
            iUserFileService.update(updateWrapper);

        }

        return true;
    }

3.测试

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kkoneone11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值