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

简介: 手把手教你云相册项目建议开发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.测试

目录
相关文章
|
26天前
|
安全 JavaScript 前端开发
购物全返商城平台系统开发步骤流程/需求设计/教程指南/源码功能
开发购物全返商城平台系统涉及多个步骤和考虑因素。
|
5月前
|
数据库
手把手教你云相册项目简易开发day4-上传下载业务逻辑之缩略图上传下载业务和审核业务(下)
手把手教你云相册项目简易开发day4-上传下载业务逻辑之缩略图上传下载业务和审核业务(下)
25 0
|
11月前
|
存储 小程序 JavaScript
借助云开发实现小程序的登陆注册功能
借助云开发实现小程序的登陆注册功能
240 0
|
5月前
|
存储 消息中间件 Java
手把手教你云相册项目简易开发day4-上传下载业务逻辑之缩略图上传下载业务和审核业务(上)
手把手教你云相册项目简易开发day4-上传下载业务逻辑之缩略图上传下载业务和审核业务
42 0
|
1月前
|
Go
区域代理分红商城系统开发指南教程/步骤功能/方案逻辑/源码项目
The development of regional proxy dividend distribution mall system involves multiple aspects such as proxy dividend function and electronic mall system development. The following is an overview of the steps for developing a regional agent dividend distribution mall system
|
3月前
|
存储 弹性计算 关系型数据库
阿里云网盘与相册服务提供了丰富的功能和灵活的使用方式
阿里云网盘与相册服务提供了丰富的功能和灵活的使用方式【1月更文挑战第13天】【1月更文挑战第61篇】
35 2
|
6月前
|
缓存 小程序 前端开发
【易售小程序项目】请求包创建+登录功能实现【基于若依管理系统开发】
【易售小程序项目】请求包创建+登录功能实现【基于若依管理系统开发】
62 0
|
6月前
|
小程序 前端开发
【易售小程序项目】修改“我的”界面前端实现;查看、重新编辑、下架自己发布的商品【后端基于若依管理系统开发】
【易售小程序项目】修改“我的”界面前端实现;查看、重新编辑、下架自己发布的商品【后端基于若依管理系统开发】
53 0
|
8月前
|
编解码 NoSQL 关系型数据库
五脏俱全,搭建部署多人语音厅源码功能分析
首先,要搭建部署一个稳定成熟的多人语音厅源码,具体的实现方式可能因项目需求以及使用的工具而有所不同,下边来简单分析下。
五脏俱全,搭建部署多人语音厅源码功能分析
|
9月前
|
SQL JSON NoSQL
ChatGPT工作提效之小鹅通二次开发批量API对接解决方案(学习记录同步、用户注册同步、权益订购同步、开发文档)
ChatGPT工作提效之小鹅通二次开发批量API对接解决方案(学习记录同步、用户注册同步、权益订购同步、开发文档)
277 0
ChatGPT工作提效之小鹅通二次开发批量API对接解决方案(学习记录同步、用户注册同步、权益订购同步、开发文档)