• 第2-3-7章 个人网盘服务接口开发-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss


    5.8 导入其他接口代码

    第2-1-2章 传统方式安装FastDFS-附FastDFS常用命令
    第2-1-3章 docker-compose安装FastDFS,实现文件存储服务
    第2-1-5章 docker安装MinIO实现文件存储服务-springboot整合minio-minio全网最全的资料

    全套代码及资料全部完整提供,点此处下载

    5.8.1 接口导入-分页查询附件

    接口文档

    在这里插入图片描述

    AttachmentController代码:

    /**
    * 分页查询附件
    *
    */
    @ApiOperation(value = "分页查询附件", notes = "分页查询附件")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "current", value = "当前页", dataType = "long", paramType = "query", defaultValue = "1"),
        @ApiImplicitParam(name = "size", value = "每页显示几条", dataType = "long", paramType = "query", defaultValue = "10"),
    })
    @GetMapping(value = "/page")
    public R<IPage<Attachment>> page(FilePageReqDTO data) {
        Page<Attachment> page = getPage();
        attachmentService.page(page, data);
        return success(page);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    AttachmentService接口:

    /**
    * 查询附件分页数据
    *
    * @param page
    * @param data
    * @return
    */
    IPage<Attachment> page(Page<Attachment> page, FilePageReqDTO data);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    AttachmentServiceImpl类:

    /**
    * 查询附件分页数据
    *
    * @param page
    * @param data
    * @return
    */
    public IPage<Attachment> page(Page<Attachment> page, FilePageReqDTO data) {
        Attachment attachment = dozer.map(data, Attachment.class);
    
        // ${ew.customSqlSegment} 语法一定要手动eq like 等 不能用lbQ!
        LbqWrapper<Attachment> wrapper = Wraps.<Attachment>lbQ()
            .like(Attachment::getSubmittedFileName, attachment.getSubmittedFileName())
            .like(Attachment::getBizType, attachment.getBizType())
            .like(Attachment::getBizId, attachment.getBizId())
            .eq(Attachment::getDataType, attachment.getDataType())
            .orderByDesc(Attachment::getId);
        return baseMapper.page(page, wrapper);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    5.8.2 接口导入-根据业务类型/业务id查询附件

    接口文档:
    在这里插入图片描述

    AttachmentController代码:

    @ApiOperation(value = "查询附件", notes = "查询附件")
    @ApiResponses(
        @ApiResponse(code = 60103, message = "文件id为空")
    )
    @GetMapping
    public R<List<AttachmentResultDTO>> findAttachment(@RequestParam(value = "bizTypes", required = false) String[] bizTypes,
                                                       @RequestParam(value = "bizIds", required = false) String[] bizIds) {
        //不能同时为空
        BizAssert.isTrue(!(ArrayUtils.isEmpty(bizTypes) && ArrayUtils.isEmpty(bizIds)), BASE_VALID_PARAM.build("业务类型不能为空"));
        return success(attachmentService.find(bizTypes, bizIds));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    AttachmentService接口:

    /**
    * 根据业务类型和业务id查询附件
    *
    * @param bizTypes
    * @param bizIds
    * @return
    */
    List<AttachmentResultDTO> find(String[] bizTypes, String[] bizIds);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    AttachmentServiceImpl类:

    /**
    * 根据业务类型和业务id查询附件
    *
    * @param bizTypes
    * @param bizIds
    * @return
    */
    public List<AttachmentResultDTO> find(String[] bizTypes, String[] bizIds) {
            return baseMapper.find(bizTypes, bizIds);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5.9 导入网盘服务接口

    前面我们已经完成了文件服务中的附件服务相关接口的开发,附件服务最终是将上传的文件信息保存在pd_attachment表中。

    本小节要完成的是文件服务中的网盘服务功能,此功能最终是将上传的文件信息保存在pd_file表中。网盘服务和附件服务非常类似,只是多了一个文件夹的概念。

    5.9.1 导入FileController
    package com.itheima.pinda.file.controller;
    
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.itheima.pinda.base.BaseController;
    import com.itheima.pinda.base.R;
    import com.itheima.pinda.dozer.DozerUtils;
    import com.itheima.pinda.file.dto.FilePageReqDTO;
    import com.itheima.pinda.file.dto.FileUpdateDTO;
    import com.itheima.pinda.file.dto.FolderDTO;
    import com.itheima.pinda.file.dto.FolderSaveDTO;
    import com.itheima.pinda.file.entity.File;
    import com.itheima.pinda.file.manager.FileRestManager;
    import com.itheima.pinda.file.service.FileService;
    import com.itheima.pinda.log.annotation.SysLog;
    import io.swagger.annotations.*;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.validation.Valid;
    import javax.validation.constraints.NotNull;
    
    /**
     * 文件前端控制器
     */
    @Validated
    @RestController
    @RequestMapping("/file")
    @Slf4j
    @Api(value = "文件表", tags = "文件表")
    public class FileController extends BaseController {
        @Autowired
        private FileService fileService;
        @Autowired
        private FileRestManager fileRestManager;
        @Autowired
        private DozerUtils dozerUtils;
    
        /**
         * 查询单个文件信息
         *
         * @param id
         * @return
         */
        @ApiOperation(value = "查询文件", notes = "查询文件")
        @GetMapping
        public R<File> get(@RequestParam(value = "id") Long id) {
            File file = fileService.getById(id);
            if (file != null && file.getIsDelete()) {
                return success(null);
            }
            return success(file);
        }
    
        /**
         * 获取文件分页
         */
        @ApiOperation(value = "分页查询文件", notes = "获取文件分页")
        @ApiImplicitParams({
                @ApiImplicitParam(name = "current", value = "当前页", dataType = "long", paramType = "query", defaultValue = "1"),
                @ApiImplicitParam(name = "size", value = "每页显示几条", dataType = "long", paramType = "query", defaultValue = "10"),
        })
        @GetMapping(value = "/page")
        public R<IPage<File>> page(FilePageReqDTO data) {
            return success(fileRestManager.page(getPage(), data));
        }
    
        /**
         * 上传文件
         */
        @ApiOperation(value = "上传文件", notes = "上传文件 ")
        @ApiResponses({
                @ApiResponse(code = 60102, message = "文件夹为空"),
        })
        @ApiImplicitParams({
                @ApiImplicitParam(name = "source", value = "文件来源", dataType = "String", paramType = "query"),
                @ApiImplicitParam(name = "userId", value = "用户id", dataType = "long", paramType = "query"),
                @ApiImplicitParam(name = "folderId", value = "文件夹id", dataType = "long", paramType = "query"),
                @ApiImplicitParam(name = "file", value = "附件", dataType = "MultipartFile", allowMultiple = true, required = true),
        })
        @RequestMapping(value = "/upload", method = RequestMethod.POST)
        public R<File> upload(
                @NotNull(message = "文件夹不能为空")
                @RequestParam(name = "source", defaultValue = "inner") String source,
                @RequestParam(name = "userId", required = false) Long userId,
                @RequestParam(value = "folderId") Long folderId,
                @RequestParam(value = "file") MultipartFile simpleFile) {
            //1,先将文件存在本地,并且生成文件名
            log.info("contentType={}, name={} , sfname={}", simpleFile.getContentType(), simpleFile.getName(), simpleFile.getOriginalFilename());
            // 忽略路径字段,只处理文件类型
            if (simpleFile.getContentType() == null) {
                return fail("文件为空");
            }
    
            File file = fileService.upload(simpleFile, folderId, source, userId);
    
            return success(file);
        }
    
    
        /**
         * 保存文件夹
         */
        @ApiResponses({
                @ApiResponse(code = 60000, message = "文件夹为空"),
                @ApiResponse(code = 60001, message = "文件夹名称为空"),
                @ApiResponse(code = 60002, message = "父文件夹为空"),
        })
        @ApiOperation(value = "新增文件夹", notes = "新增文件夹")
        @RequestMapping(value = "", method = RequestMethod.POST)
        public R<FolderDTO> saveFolder(@Valid @RequestBody FolderSaveDTO folderSaveDto) {
            //2,获取身份
    
            FolderDTO folder = fileService.saveFolder(folderSaveDto);
            return success(folder);
        }
    
        /**
         * 修改文件、文件夹信息
         *
         * @param fileUpdateDTO
         * @return
         */
        @ApiOperation(value = "修改文件/文件夹名称", notes = "修改文件/文件夹名称")
        @ApiResponses({
                @ApiResponse(code = 60100, message = "文件为空"),
        })
        @RequestMapping(value = "", method = RequestMethod.PUT)
        public R<Boolean> update(@Valid @RequestBody FileUpdateDTO fileUpdateDTO) {
            // 判断文件名是否有 后缀
            if (StringUtils.isNotEmpty(fileUpdateDTO.getSubmittedFileName())) {
                File oldFile = fileService.getById(fileUpdateDTO.getId());
                if (oldFile.getExt() != null && !fileUpdateDTO.getSubmittedFileName().endsWith(oldFile.getExt())) {
                    fileUpdateDTO.setSubmittedFileName(fileUpdateDTO.getSubmittedFileName() + "." + oldFile.getExt());
                }
            }
            File file = dozerUtils.map2(fileUpdateDTO, File.class);
    
            fileService.updateById(file);
            return success(true);
        }
    
        /**
         * 根据Ids进行文件删除
         *
         * @param ids
         * @return
         */
        @ApiOperation(value = "根据Ids进行文件删除", notes = "根据Ids进行文件删除  ")
        @DeleteMapping(value = "/ids")
        public R<Boolean> removeList(@RequestParam(value = "ids[]") Long[] ids) {
            Long userId = getUserId();
            return success(fileService.removeList(userId, ids));
        }
    
        /**
         * 下载一个文件或多个文件打包下载
         *
         * @param ids
         * @param response
         * @throws Exception
         */
        @ApiOperation(value = "下载一个文件或多个文件打包下载", notes = "下载一个文件或多个文件打包下载")
        @GetMapping(value = "/download", produces = "application/octet-stream")
        public void download(
                @ApiParam(name = "ids[]", value = "文件id 数组")
                @RequestParam(value = "ids[]") Long[] ids,
                HttpServletRequest request, HttpServletResponse response) throws Exception {
            fileRestManager.download(request, response, ids, null);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    5.9.2 导入StatisticsController
    package com.itheima.pinda.file.controller;
    
    import com.itheima.pinda.base.BaseController;
    import com.itheima.pinda.base.R;
    import com.itheima.pinda.file.domain.FileStatisticsDO;
    import com.itheima.pinda.file.dto.FileOverviewDTO;
    import com.itheima.pinda.file.dto.FileStatisticsAllDTO;
    import com.itheima.pinda.file.service.FileService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import lombok.extern.slf4j.Slf4j;
    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.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import java.time.LocalDateTime;
    import java.util.List;
    
    /**
     * 文件统计接口
     */
    @Slf4j
    @RestController
    @RequestMapping("/statistics")
    @Api(value = "Statistics", tags = "统计接口")
    public class StatisticsController extends BaseController {
        @Autowired
        private FileService fileService;
    
        @ApiOperation(value = "云盘首页数据概览", notes = "云盘首页数据概览")
        @GetMapping(value = "/overview")
        public R<FileOverviewDTO> overview(@RequestParam(name = "userId", required = false) Long userId) {
            return success(fileService.findOverview(userId, null, null));
        }
    
        @ApiOperation(value = "按照类型,统计各种类型的 大小和数量", notes = "按照类型,统计当前登录人各种类型的大小和数量")
        @GetMapping(value = "/type")
        public R<List<FileStatisticsDO>> findAllByDataType(@RequestParam(name = "userId", required = false) Long userId) {
            return success(fileService.findAllByDataType(userId));
        }
    
        @ApiOperation(value = "按照时间统计各种类型的文件的数量和大小", notes = "按照时间统计各种类型的文件的数量和大小 不指定时间,默认查询一个月")
        @GetMapping(value = "")
        public R<FileStatisticsAllDTO> findNumAndSizeToTypeByDate(@RequestParam(name = "userId", required = false) Long userId,
                                                                  @RequestParam(value = "startTime", required = false) LocalDateTime startTime,
                                                                  @RequestParam(value = "endTime", required = false) LocalDateTime endTime) {
            return success(fileService.findNumAndSizeToTypeByDate(userId, startTime, endTime));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    5.9.3 导入FileRestManager
    package com.itheima.pinda.file.manager;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.itheima.pinda.context.BaseContextHandler;
    import com.itheima.pinda.file.constant.FileConstants;
    import com.itheima.pinda.file.dto.FilePageReqDTO;
    import com.itheima.pinda.file.entity.File;
    import com.itheima.pinda.file.service.FileService;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import static com.itheima.pinda.utils.StrPool.DEF_PARENT_ID;
    
    /**
     * 文件 公共代码 管理类
     */
    @Component
    public class FileRestManager {
        @Autowired
        private FileService fileService;
    
        public IPage<File> page(IPage<File> page, FilePageReqDTO filePageReq) {
            //类型和文件夹id同时为null时, 表示查询 全部文件
            if (filePageReq.getFolderId() == null && filePageReq.getDataType() == null) {
                filePageReq.setFolderId(DEF_PARENT_ID);
            }
    
            QueryWrapper<File> query = new QueryWrapper<>();
            LambdaQueryWrapper<File> lambdaQuery = query.lambda()
                    .eq(File::getIsDelete, false)
                    .eq(filePageReq.getDataType() != null, File::getDataType, filePageReq.getDataType())
                    .eq(filePageReq.getFolderId() != null, File::getFolderId, filePageReq.getFolderId())
                    .like(StringUtils.isNotEmpty(filePageReq.getSubmittedFileName()), File::getSubmittedFileName, filePageReq.getSubmittedFileName());
    
            query.orderByDesc(String.format("case when %s='DIR' THEN 1 else 0 end", FileConstants.DATA_TYPE));
            lambdaQuery.orderByDesc(File::getCreateTime);
    
            fileService.page(page, lambdaQuery);
            return page;
        }
    
        public void download(HttpServletRequest request, HttpServletResponse response, Long[] ids, Long userId) throws Exception {
            userId = userId == null || userId <= 0 ? BaseContextHandler.getUserId() : userId;
            fileService.download(request, response, ids, userId);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    5.9.4 导入FileService
    package com.itheima.pinda.file.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.itheima.pinda.file.domain.FileAttrDO;
    import com.itheima.pinda.file.domain.FileStatisticsDO;
    import com.itheima.pinda.file.dto.FileOverviewDTO;
    import com.itheima.pinda.file.dto.FileStatisticsAllDTO;
    import com.itheima.pinda.file.dto.FolderDTO;
    import com.itheima.pinda.file.dto.FolderSaveDTO;
    import com.itheima.pinda.file.entity.File;
    import org.springframework.web.multipart.MultipartFile;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.time.LocalDateTime;
    import java.util.List;
    /**
     * 业务接口
     * 文件表
     *
     */
    public interface FileService extends IService<File> {
        /**
         * 保存文件夹
         *
         * @param folderSaveDto 文件夹
         * @return
         */
        FolderDTO saveFolder(FolderSaveDTO folderSaveDto);
    
        /**
         * 根据文件id下载文件,并统计下载次数
         *
         * @param request  请求
         * @param response 响应
         * @param ids      文件id集合
         * @param userId   用户id
         * @throws Exception
         */
        void download(HttpServletRequest request, HttpServletResponse response,
                      Long[] ids, Long userId) throws Exception;
    
        /**
         * 根据文件id和用户id 删除文件或者文件夹
         *
         * @param userId 用户id
         * @param ids    文件id集合
         * @return
         */
        Boolean removeList(Long userId, Long[] ids);
    
        /**
         * 根据文件夹id查询
         *
         * @param folderId
         * @return
         */
        FileAttrDO getFileAttrDo(Long folderId);
    
        /**
         * 文件上传
         *
         * @param simpleFile 文件
         * @param folderId   文件夹id
         * @return
         */
        File upload(MultipartFile simpleFile, Long folderId);
    
    	/**
    	 * 文件上传
    	 *
    	 * @param simpleFile
    	 * @param folderId
    	 * @param source
    	 * @param userId
    	 * @return
    	 */
    	File upload(MultipartFile simpleFile, Long folderId, String source, Long userId);
    
        /**
         * 首页概览
         *
         * @param userId
         * @param startTime
         * @param endTime
         * @return
         */
        FileOverviewDTO findOverview(Long userId, LocalDateTime startTime, LocalDateTime endTime);
    
        /**
         * 首页个人文件发展概览
         *
         * @param userId
         * @param startTime
         * @param endTime
         * @return
         */
        FileStatisticsAllDTO findAllByDate(Long userId, LocalDateTime startTime, LocalDateTime endTime);
    
    
        /**
         * 按照 数据类型分类查询 当前人的所有文件的数量和大小
         *
         * @param userId
         * @return
         */
        List<FileStatisticsDO> findAllByDataType(Long userId);
    
        /**
         * 查询下载排行前20的文件
         *
         * @param userId
         * @return
         */
        List<FileStatisticsDO> downTop20(Long userId);
    
        /**
         * 根据日期查询,特定类型的数量和大小
         *
         * @param userId
         * @param startTime
         * @param endTime
         * @return
         */
        FileStatisticsAllDTO findNumAndSizeToTypeByDate(Long userId, LocalDateTime startTime, LocalDateTime endTime);
    
        /**
         * 根据日期查询下载大小
         *
         * @param userId
         * @param startTime
         * @param endTime
         * @return
         */
        FileStatisticsAllDTO findDownSizeByDate(Long userId, LocalDateTime startTime,
                                                LocalDateTime endTime);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    5.9.5 导入FileServiceImpl
    package com.itheima.pinda.file.service.impl;
    
    import com.baomidou.mybatisplus.core.toolkit.Wrappers;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.itheima.pinda.database.mybatis.conditions.Wraps;
    import com.itheima.pinda.database.mybatis.conditions.update.LbuWrapper;
    import com.itheima.pinda.dozer.DozerUtils;
    import com.itheima.pinda.file.biz.FileBiz;
    import com.itheima.pinda.file.dao.FileMapper;
    import com.itheima.pinda.file.domain.FileAttrDO;
    import com.itheima.pinda.file.domain.FileDO;
    import com.itheima.pinda.file.domain.FileDeleteDO;
    import com.itheima.pinda.file.domain.FileStatisticsDO;
    import com.itheima.pinda.file.dto.FileOverviewDTO;
    import com.itheima.pinda.file.dto.FileStatisticsAllDTO;
    import com.itheima.pinda.file.dto.FolderDTO;
    import com.itheima.pinda.file.dto.FolderSaveDTO;
    import com.itheima.pinda.file.entity.File;
    import com.itheima.pinda.file.enumeration.DataType;
    import com.itheima.pinda.file.enumeration.IconType;
    import com.itheima.pinda.file.service.FileService;
    import com.itheima.pinda.file.strategy.FileStrategy;
    import com.itheima.pinda.utils.BizAssert;
    import com.itheima.pinda.utils.DateUtils;
    import lombok.Getter;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.ArrayUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.time.LocalDateTime;
    import java.time.LocalTime;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    import static com.itheima.pinda.exception.code.ExceptionCode.BASE_VALID_PARAM;
    import static com.itheima.pinda.utils.StrPool.DEF_PARENT_ID;
    import static com.itheima.pinda.utils.StrPool.DEF_ROOT_PATH;
    import static java.util.stream.Collectors.groupingBy;
    
    /**
     * 业务实现类
     * 文件表
     */
    @Slf4j
    @Service
    public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements FileService {
        @Autowired
        private DozerUtils dozerUtils;
    
        @Autowired
        private FileBiz fileBiz;
        @Resource
        private FileStrategy fileStrategy;
    
        @Override
        public File upload(MultipartFile simpleFile, Long folderId) {
            FileAttrDO fileAttrDO = this.getFileAttrDo(folderId);
            String treePath = fileAttrDO.getTreePath();
            String folderName = fileAttrDO.getFolderName();
            Integer grade = fileAttrDO.getGrade();
    
            File file = fileStrategy.upload(simpleFile);
            file.setFolderId(folderId);
            file.setFolderName(folderName);
            file.setGrade(grade);
            file.setTreePath(treePath);
            super.save(file);
            return file;
        }
    
    	@Override
    	public File upload(MultipartFile simpleFile, Long folderId, String source, Long userId) {
    		FileAttrDO fileAttrDO = this.getFileAttrDo(folderId);
    		String treePath = fileAttrDO.getTreePath();
    		String folderName = fileAttrDO.getFolderName();
    		Integer grade = fileAttrDO.getGrade();
    
    		File file = fileStrategy.upload(simpleFile);
    		file.setFolderId(folderId);
    		file.setFolderName(folderName);
    		file.setGrade(grade);
    		file.setTreePath(treePath);
    
    		file.setSource(source);
    		if (userId != null) {
    			file.setCreateUser(userId);
    		}
    
    		super.save(file);
    		return file;
    	}
    
    	@Override
        public FileAttrDO getFileAttrDo(Long folderId) {
            String treePath = DEF_ROOT_PATH;
            String folderName = "";
            Integer grade = 1;
            if (folderId == null || folderId <= 0) {
                return new FileAttrDO(treePath, grade, folderName, DEF_PARENT_ID);
            }
            File folder = this.getById(folderId);
    
            if (folder != null && !folder.getIsDelete() && DataType.DIR.eq(folder.getDataType())) {
                folderName = folder.getSubmittedFileName();
                treePath = StringUtils.join(folder.getTreePath(), folder.getId(), DEF_ROOT_PATH);
                grade = folder.getGrade() + 1;
            }
            BizAssert.isTrue(grade <= 10, BASE_VALID_PARAM.build("文件夹层级不能超过10层"));
            return new FileAttrDO(treePath, grade, folderName, folderId);
        }
    
    
        @Override
        public FolderDTO saveFolder(FolderSaveDTO folderSaveDto) {
            File folder = dozerUtils.map2(folderSaveDto, File.class);
            if (folderSaveDto.getFolderId() == null || folderSaveDto.getFolderId() <= 0) {
                folder.setFolderId(DEF_PARENT_ID);
                folder.setTreePath(DEF_ROOT_PATH);
                folder.setGrade(1);
            } else {
                File parent = super.getById(folderSaveDto.getFolderId());
                BizAssert.notNull(parent, BASE_VALID_PARAM.build("父文件夹不能为空"));
                BizAssert.isFalse(parent.getIsDelete(), BASE_VALID_PARAM.build("父文件夹已经被删除"));
                BizAssert.equals(DataType.DIR.name(), parent.getDataType().name(), BASE_VALID_PARAM.build("父文件夹不存在"));
                BizAssert.isTrue(parent.getGrade() < 10, BASE_VALID_PARAM.build("文件夹层级不能超过10层"));
                folder.setFolderName(parent.getSubmittedFileName());
                folder.setTreePath(StringUtils.join(parent.getTreePath(), parent.getId(), DEF_ROOT_PATH));
                folder.setGrade(parent.getGrade() + 1);
            }
            if (folderSaveDto.getOrderNum() == null) {
                folderSaveDto.setOrderNum(0);
            }
            folder.setIsDelete(false);
            folder.setDataType(DataType.DIR);
            folder.setIcon(IconType.DIR.getIcon());
            setDate(folder);
            super.save(folder);
            return dozerUtils.map2(folder, FolderDTO.class);
        }
    
        private void setDate(File file) {
            LocalDateTime now = LocalDateTime.now();
            file.setCreateMonth(DateUtils.formatAsYearMonthEn(now))
                    .setCreateWeek(DateUtils.formatAsYearWeekEn(now))
                    .setCreateDay(DateUtils.formatAsDateEn(now));
        }
    
        public boolean removeFile(Long[] ids, Long userId) {
            LbuWrapper<File> lambdaUpdate =
                    Wraps.<File>lbU()
                            .in(File::getId, ids)
                            .eq(File::getCreateUser, userId);
            File file = File.builder().isDelete(Boolean.TRUE).build();
    
            return super.update(file, lambdaUpdate);
        }
    
        @Override
        public Boolean removeList(Long userId, Long[] ids) {
            if (ArrayUtils.isEmpty(ids)) {
                return Boolean.TRUE;
            }
            List<File> list = super.list(Wrappers.<File>lambdaQuery().in(File::getId, ids));
            if (list.isEmpty()) {
                return true;
            }
            super.removeByIds(Arrays.asList(ids));
    
            fileStrategy.delete(list.stream().map((fi) -> FileDeleteDO.builder()
                    .relativePath(fi.getRelativePath())
                    .fileName(fi.getFilename())
                    .group(fi.getGroup())
                    .path(fi.getPath())
                    .file(false)
                    .build())
                    .collect(Collectors.toList()));
            return true;
        }
    
        @Override
        public void download(HttpServletRequest request, HttpServletResponse response, Long[] ids, Long userId) throws Exception {
            if (ids == null || ids.length == 0) {
                return;
            }
            List<File> list = (List<File>) super.listByIds(Arrays.asList(ids));
    
            if (list == null || list.size() == 0) {
                return;
            }
            List<FileDO> listDo = list.stream().map((file) ->
                    FileDO.builder()
                            .dataType(file.getDataType())
                            .size(file.getSize())
                            .submittedFileName(file.getSubmittedFileName())
                            .url(file.getUrl())
                            .build())
                    .collect(Collectors.toList());
            fileBiz.down(listDo, request, response);
        }
    
    
        @Override
        public FileOverviewDTO findOverview(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
            InnerQueryDate innerQueryDate = new InnerQueryDate(userId, startTime, endTime).invoke();
            startTime = innerQueryDate.getStartTime();
            endTime = innerQueryDate.getEndTime();
    
            List<FileStatisticsDO> list = baseMapper.findNumAndSizeByUserId(userId, null, "ALL", startTime, endTime);
            FileOverviewDTO.FileOverviewDTOBuilder builder = FileOverviewDTO.myBuilder();
    
            long allSize = 0L;
            int allNum = 0;
            for (FileStatisticsDO fs : list) {
                allSize += fs.getSize();
                allNum += fs.getNum();
                switch (fs.getDataType()) {
                    case DIR:
                        builder.dirNum(fs.getNum());
                        break;
                    case IMAGE:
                        builder.imgNum(fs.getNum());
                        break;
                    case VIDEO:
                        builder.videoNum(fs.getNum());
                        break;
                    case DOC:
                        builder.docNum(fs.getNum());
                        break;
                    case AUDIO:
                        builder.audioNum(fs.getNum());
                        break;
                    case OTHER:
                        builder.otherNum(fs.getNum());
                        break;
                    default:
                        break;
                }
            }
            builder.allFileNum(allNum).allFileSize(allSize);
            return builder.build();
        }
    
        @Override
        public FileStatisticsAllDTO findAllByDate(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
            InnerQueryDate innerQueryDate = new InnerQueryDate(userId, startTime, endTime).invoke();
            startTime = innerQueryDate.getStartTime();
            endTime = innerQueryDate.getEndTime();
            List<String> dateList = innerQueryDate.getDateList();
            String dateType = innerQueryDate.getDateType();
    
            //不完整的数据
            List<FileStatisticsDO> list = baseMapper.findNumAndSizeByUserId(userId, dateType, null, startTime, endTime);
    
            //按月份分类
            Map<String, List<FileStatisticsDO>> map = list.stream().collect(groupingBy(FileStatisticsDO::getDateType));
    
            List<Long> sizeList = new ArrayList<>();
            List<Integer> numList = new ArrayList<>();
    
            dateList.forEach((date) -> {
                if (map.containsKey(date)) {
                    List<FileStatisticsDO> subList = map.get(date);
    
                    Long size = subList.stream().mapToLong(FileStatisticsDO::getSize).sum();
                    Integer num = subList.stream().filter((fs) -> !DataType.DIR.eq(fs.getDataType()))
                            .mapToInt(FileStatisticsDO::getNum).sum();
                    sizeList.add(size);
                    numList.add(num);
                } else {
                    sizeList.add(0L);
                    numList.add(0);
                }
            });
    
            return FileStatisticsAllDTO.builder().dateList(dateList).numList(numList).sizeList(sizeList).build();
        }
    
    
        @Override
        public List<FileStatisticsDO> findAllByDataType(Long userId) {
            List<DataType> dataTypes = Arrays.asList(DataType.values());
            List<FileStatisticsDO> list = baseMapper.findNumAndSizeByUserId(userId, null, "ALL", null, null);
    
            Map<DataType, List<FileStatisticsDO>> map = list.stream().collect(groupingBy(FileStatisticsDO::getDataType));
    
            return dataTypes.stream().map((type) -> {
                FileStatisticsDO fs = null;
                if (map.containsKey(type)) {
                    fs = map.get(type).get(0);
                } else {
                    fs = FileStatisticsDO.builder().dataType(type).size(0L).num(0).build();
                }
                return fs;
            }).collect(Collectors.toList());
        }
    
        @Override
        public List<FileStatisticsDO> downTop20(Long userId) {
            return baseMapper.findDownTop20(userId);
        }
    
        @Override
        public FileStatisticsAllDTO findNumAndSizeToTypeByDate(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
            return common(userId, startTime, endTime,
                    (qd) -> baseMapper.findNumAndSizeByUserId(qd.getUserId(), qd.getDateType(), "ALL", qd.getStartTime(), qd.getEndTime()));
        }
    
        @Override
        public FileStatisticsAllDTO findDownSizeByDate(Long userId, LocalDateTime startTime,
                                                       LocalDateTime endTime) {
            return common(userId, startTime, endTime,
                    (qd) -> baseMapper.findDownSizeByDate(qd.getUserId(), qd.getDateType(), qd.getStartTime(), qd.getEndTime()));
        }
    
        /**
         * 抽取公共查询公共代码
         *
         * @param userId    用户id
         * @param startTime 开始时间
         * @param endTime   结束时间
         * @param function  回调函数
         * @return
         */
        private FileStatisticsAllDTO common(Long userId, LocalDateTime startTime, LocalDateTime endTime, Function<InnerQueryDate, List<FileStatisticsDO>> function) {
            InnerQueryDate innerQueryDate = new InnerQueryDate(userId, startTime, endTime).invoke();
            List<String> dateList = innerQueryDate.getDateList();
    
            List<FileStatisticsDO> list = function.apply(innerQueryDate);
    
            //按月份分类
            Map<String, List<FileStatisticsDO>> map = list.stream().collect(groupingBy(FileStatisticsDO::getDateType));
    
            List<Long> sizeList = new ArrayList<>(dateList.size());
            List<Integer> numList = new ArrayList<>(dateList.size());
    
            List<Integer> dirNumList = new ArrayList<>(dateList.size());
    
            List<Long> imgSizeList = new ArrayList<>(dateList.size());
            List<Integer> imgNumList = new ArrayList<>(dateList.size());
    
            List<Long> videoSizeList = new ArrayList<>(dateList.size());
            List<Integer> videoNumList = new ArrayList<>(dateList.size());
    
            List<Long> audioSizeList = new ArrayList<>(dateList.size());
            List<Integer> audioNumList = new ArrayList<>(dateList.size());
    
            List<Long> docSizeList = new ArrayList<>(dateList.size());
            List<Integer> docNumList = new ArrayList<>(dateList.size());
    
            List<Long> otherSizeList = new ArrayList<>(dateList.size());
            List<Integer> otherNumList = new ArrayList<>(dateList.size());
    
            dateList.forEach((date) -> {
                if (map.containsKey(date)) {
                    List<FileStatisticsDO> subList = map.get(date);
    
                    Function<DataType, Stream<FileStatisticsDO>> stream = (dataType) -> subList.stream().filter((fs) -> !dataType.eq(fs.getDataType()));
                    Long size = stream.apply(DataType.DIR).mapToLong(FileStatisticsDO::getSize).sum();
                    Integer num = stream.apply(DataType.DIR).mapToInt(FileStatisticsDO::getNum).sum();
                    sizeList.add(size);
                    numList.add(num);
    
                    Integer dirNum = subList.stream().filter((fs) -> DataType.DIR.eq(fs.getDataType()))
                            .mapToInt(FileStatisticsDO::getNum).sum();
                    dirNumList.add(dirNum);
    
                    add(imgSizeList, imgNumList, subList, DataType.IMAGE);
                    add(videoSizeList, videoNumList, subList, DataType.VIDEO);
                    add(audioSizeList, audioNumList, subList, DataType.AUDIO);
                    add(docSizeList, docNumList, subList, DataType.DOC);
                    add(otherSizeList, otherNumList, subList, DataType.OTHER);
    
                } else {
                    sizeList.add(0L);
                    numList.add(0);
                    dirNumList.add(0);
                    imgSizeList.add(0L);
                    imgNumList.add(0);
                    videoSizeList.add(0L);
                    videoNumList.add(0);
                    audioSizeList.add(0L);
                    audioNumList.add(0);
                    docSizeList.add(0L);
                    docNumList.add(0);
                    otherSizeList.add(0L);
                    otherNumList.add(0);
                }
            });
    
            return FileStatisticsAllDTO.builder()
                    .dateList(dateList)
                    .numList(numList).sizeList(sizeList)
                    .dirNumList(dirNumList)
                    .imgNumList(imgNumList).imgSizeList(imgSizeList)
                    .videoNumList(videoNumList).videoSizeList(videoSizeList)
                    .audioNumList(audioNumList).audioSizeList(audioSizeList)
                    .docNumList(docNumList).docSizeList(docSizeList)
                    .otherNumList(otherNumList).otherSizeList(otherSizeList)
                    .build();
        }
    
        private void add(List<Long> sizeList, List<Integer> numList, List<FileStatisticsDO> subList, DataType dt) {
            Function<DataType, Stream<FileStatisticsDO>> stream =
                    dataType -> subList.stream().filter(fs -> dataType.eq(fs.getDataType()));
    
            Long size = stream.apply(dt).mapToLong(FileStatisticsDO::getSize).sum();
            Integer num = stream.apply(dt).mapToInt(FileStatisticsDO::getNum).sum();
            sizeList.add(size);
            numList.add(num);
        }
    
        @Getter
        private static class InnerQueryDate {
            private LocalDateTime startTime;
            private LocalDateTime endTime;
            private List<String> dateList;
            private String dateType;
            private Long userId;
    
            public InnerQueryDate(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
                this.userId = userId;
                this.startTime = startTime;
                this.endTime = endTime;
            }
    
            public InnerQueryDate invoke() {
                if (startTime == null) {
                    startTime = LocalDateTime.now().plusDays(-9);
                }
                if (endTime == null) {
                    endTime = LocalDateTime.now();
                }
                endTime = LocalDateTime.of(endTime.toLocalDate(), LocalTime.MAX);
                dateList = new ArrayList<>();
                dateType = DateUtils.calculationEn(startTime, endTime, dateList);
                return this;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    5.9.6 扩展FileMapper接口方法
    /**
    * 查询下次次数前20的文件
    *
    * @param userId
    * @return
    */
    List<FileStatisticsDO> findDownTop20(@Param("userId") Long userId);
    
    /**
    * 统计时间区间内文件的下次次数和大小
    *
    * @param userId
    * @param dateType  日期类型 {MONTH:按月;WEEK:按周;DAY:按日} 来统计
    * @param startTime
    * @param endTime
    * @return
    */
    List<FileStatisticsDO> findDownSizeByDate(@Param("userId") Long userId,
                                              @Param("dateType") String dateType,
                                              @Param("startTime") LocalDateTime startTime,
                                              @Param("endTime") LocalDateTime endTime);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    第2-1-2章 传统方式安装FastDFS-附FastDFS常用命令
    第2-1-3章 docker-compose安装FastDFS,实现文件存储服务
    第2-1-5章 docker安装MinIO实现文件存储服务-springboot整合minio-minio全网最全的资料

    全套代码及资料全部完整提供,点此处下载

  • 相关阅读:
    面向对象之【探索C++】硬核 | 造轮子的快乐源泉【引用篇】
    数据可视化工具中的显眼包:奥威BI自带方案上阵
    面试问题回忆
    【安卓基础2】简单控件
    GoLang语言基本代码整理
    Java之Stream流
    kaggle大模型竞赛优胜方案总结与思考
    Python 中的异步请求
    接口测试这件小事
    [激光原理与应用-24]:《激光原理与技术》-10- 控制技术-调Q技术、Q开关、Q驱动器
  • 原文地址:https://blog.csdn.net/weixin_42208775/article/details/127944810