【Easy云盘】Day2 文件秒传、分片上传、异步合并

注册

  1. 先判断该邮箱和昵称是否存在
  2. 校验邮箱(之前写过)
  3. 将用户信息插入UserInfo表(包含总系统空间大小(5MB)和个人系统空间大小(0MB))

登陆

参数(email,password)

  1. 用邮箱获取个人信息
  2. 比对密码,判断是否被禁用
  3. 重置最近登陆时间
  4. 新建一个SessionWebUserDto对象(昵称,用户id,是否管理员)存入session
  5. 将该用户个人系统空间大小存入redis
 @Override
    public SessionWebUserDto login(String email, String password) {
        UserInfo userInfo = this.userInfoMapper.selectByEmail(email);
        if (null == userInfo || !userInfo.getPassword().equals(password)) {
            throw new BusinessException("账号或者密码错误");
        }
        if (UserStatusEnum.DISABLE.getStatus().equals(userInfo.getStatus())) {
            throw new BusinessException("账号已禁用");
        }
        UserInfo updateInfo = new UserInfo();
        updateInfo.setLastLoginTime(new Date());
        this.userInfoMapper.updateByUserId(updateInfo, userInfo.getUserId());
        //新建session对象
        SessionWebUserDto sessionWebUserDto = new SessionWebUserDto();
        sessionWebUserDto.setNickName(userInfo.getNickName());
        sessionWebUserDto.setUserId(userInfo.getUserId());
        if (ArrayUtils.contains(appConfig.getAdminEmails().split(","), email)) {
            sessionWebUserDto.setAdmin(true);
        } else {
            sessionWebUserDto.setAdmin(false);
        }
        //用户空间
        UserSpaceDto userSpaceDto = new UserSpaceDto();
        userSpaceDto.setUseSpace(fileInfoService.getUserUseSpace(userInfo.getUserId()));
        userSpaceDto.setTotalSpace(userInfo.getTotalSpace());
        redisComponent.saveUserSpaceUse(userInfo.getUserId(), userSpaceDto);
        return sessionWebUserDto;
    }

找回密码(重置密码)

参数(邮箱,新密码,邮箱验证码)

  1. 根据邮箱查找用户是否存在
  2. 校验邮箱验证码
  3. 更新密码

获取、更新头像

获取头像:根据id获取用户头像,如果没有就返回默认头像,如果没有默认头像就返回错误信息,最后用readFile方法读取头像文件
更新头像:判断头像文件夹是否存在,不存在就创建,然后新建头像文件。

获取用户使用内存大小

用session存的用户id从redis中取

  • 这里补充一下,如果session不在参数里,怎么获取session?
    Spring提供了RequestContextHolder类,可以从当前线程中获取上下文
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();

登陆拦截

利用之前的AOP拦截每个方法(除了部分比如获取验证码之类的方法),Session中看有没有user,如果没有就拦截,否则放行。

文件条件分页查询

新建fileinfo文件表(用户id,文件id,文件类型(文件或文件夹),父级id,文件md5,文件大小,名称,路径,文件种类,状态(上传中,上传失败,已上传),删除标记(未被删除,在回收站中,彻底删除),回收时间)
在这里插入图片描述

将各种查询条件(文件类型、userId、排列顺序、删除标记)放入FileInfoQuery中,然后查询

 public ResponseVO loadDataList(HttpSession session, FileInfoQuery query, String category) {
        FileCategoryEnums categoryEnum = FileCategoryEnums.getByCode(category);
        if (null != categoryEnum) {
            query.setFileCategory(categoryEnum.getCategory());
        }
        query.setUserId(getUserInfoFromSession(session).getUserId());
        query.setOrderBy("last_update_time desc");
        query.setDelFlag(FileDelFlagEnums.USING.getFlag());
        PaginationResultVO result = fileInfoService.findListByPage(query);
        return getSuccessResponseVO(convert2PaginationVO(result, FileInfoVO.class));
    }

文件上传

参数:session存储的用户信息,fileId(传第一个分片的时候没有,由代码生成,传其它分片的时候使用),file,filename,fileMd5(验证文件使用),chunkIndex(第几个分片),chunks(分片数)

  • 秒传逻辑:用文件的md5值(前端生成的32位字符串)检查文件是否存在,如存在直接引用
  • 分片存储:文件在前端分成n个分片,后端处理放入temp文件。同时要检查用户剩余空间是否充足。事务提交后处理异步合并。最后清理临时文件。

如果是第一个分片,查询数据库里有无MD5相同的文件,如果有直接秒传:先检查空间是否充足(redis查询),将查询到的文件更新数据(userId、文件信息)插入表里。

         FileInfoQuery infoQuery = new FileInfoQuery();
         infoQuery.setFileMd5(fileMd5); // 根据文件 MD5 查询是否已有相同文件
         infoQuery.setSimplePage(new SimplePage(0, 1)); // 分页,取第一个结果
         infoQuery.setStatus(FileStatusEnums.USING.getStatus());
         List<FileInfo> dbFileList = this.fileInfoMapper.selectList(infoQuery);
// 秒传逻辑:如果已存在相同的文件,直接关联而不重新上传
                if (!dbFileList.isEmpty()) {
                    FileInfo dbFile = dbFileList.get(0);
                    // 检查文件大小是否超出用户剩余空间
                    if (dbFile.getFileSize() + spaceDto.getUseSpace() > spaceDto.getTotalSpace()) {
                        throw new BusinessException(ResponseCodeEnum.CODE_904); // 空间不足
                    }
                    // 更新文件信息
                    dbFile.setFileId(fileId);
                    dbFile.setFilePid(filePid);
                    dbFile.setUserId(webUserDto.getUserId());
                    dbFile.setFileMd5(fileMd5);
                    dbFile.setCreateTime(curDate);
                    dbFile.setLastUpdateTime(curDate);
                    dbFile.setStatus(FileStatusEnums.USING.getStatus());
                    dbFile.setDelFlag(FileDelFlagEnums.USING.getFlag());
                    dbFile.setFileName(autoRename(filePid, webUserDto.getUserId(), fileName));
                    this.fileInfoMapper.insert(dbFile); // 保存到数据库

                    resultDto.setStatus(UploadStatusEnums.UPLOAD_SECONDS.getCode()); // 秒传成功状态
                    updateUserSpace(webUserDto, dbFile.getFileSize()); // 更新用户空间使用
                    return resultDto; // 返回结果

否则将文件分片到临时文件夹里,并更新redis里临时文件大小。如果不是最后一个分片,就返回数据(fileId和上传类型(上传中))。
如果是最后一个分片,设置文件属性(将状态设为转码中)并插入数据库。更新数据库中用户使用空间。异步合并。
合并逻辑:打开目标文件的写入流,并按顺序逐个读取分片文件内容。建一个缓冲区(大小为 10KB)将分片文件内容写入目标文件。
在这里插入图片描述

  • 为什么需要异步合并?
  1. 如果采用同步合并,用户需要等待所有分片合并完成后才能收到上传成功的响应,影响体验。使用异步合并后,服务端在接收到最后一个分片时即可告知用户“上传完成”,随后在后台执行合并操作。
  2. 同步合并会占用当前线程,导致上传接口阻塞,无法及时处理其他用户的请求。异步操作将合并任务交由后台线程池处理,可以让主线程迅速返回,保持高并发能力。
  • 如何在事务提交后异步调用
    注册一个事务同步对象(可以监听事务的生命周期),在事务结束后触发afterCommit方法
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    transferFile(fileInfo.getFileId(), webUserDto);
                }
            });

加上@Async注释标记方法异步执行
在这里插入图片描述
但是这样依旧没法异步执行,要交给Spring管理,使用 fileInfoService 代理对象调用这个方法

public void afterCommit() {
                    fileInfoService.transferFile(fileInfo.getFileId(), webUserDto);
                }

所以要注入自己,@lazy懒加载只有调用的时候加载,避免循环依赖的问题。
在这里插入图片描述
tip:用redis读取数据减少对数据库的压力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值