Layui文件上传及页面回显、数据库绑定

1:文件上传分析

文件上传作为一个独立功能,不能和业务产生过于直接的关系,得单独设计表和代码

2:数据表分析

首先需要一个表保存文件的基本信息,如:源文件名,服务器文件路径,文件类型等。

还需要一个表用来关联业务数据,但是不同业务的id也许有重复的,所以不能用业务id关联,得单独定义一个字段

 

以下实现是基于Layui2.5.6和springboot2.3.8实现的

3:数据表创建

首先是保存文件基本信息的表:file_upload

CREATE TABLE `file_upload` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `file_source_name` varchar(128) DEFAULT NULL COMMENT '源文件名',
  `file_new_name` varchar(128) DEFAULT NULL COMMENT '新文件名',
  `file_url` varchar(512) DEFAULT NULL COMMENT '文件全路径',
  `file_type` varchar(32) DEFAULT NULL COMMENT '文件类型',
  `create_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '上传时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=73 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='文件上传记录表';

保存业务文件关联关系表:file_service_map

CREATE TABLE `file_service_map` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `file_id` bigint NOT NULL COMMENT '文件id',
  `service_id` varchar(128) NOT NULL COMMENT '业务id',
  `service_type` varchar(64) NOT NULL COMMENT '业务类型',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='文件业务中间表';

 

4:页面设计

页面的话,就不需要自己写,用layui现成的上传组件,但是有些地方还是得调整一下或者开拓一下

页面上传思路:

使用layui上传文件,文件在服务器保存后返回文件id,页面把id隐藏的保存起来,然后提交业务数据的时候,把文件的id带到服务器,然后生成关联id,保存业务表,保存中间关系表

页面回显思路:

使用业务表数据查询接口,查询数据,同时根据查询出来的文件关联id去查询中间表,拿到中间表的数据。在业务表的回显vo中,增加一个List,用来装文件列表信息。页面根据这个信息去渲染或者生成html段落即可实现文件的列表回显

 

5:页面实现

html页面中加入一段容器,尽量不要把上传组件的html代码写到这里面,封装到js里进行统一管理

<!--文件上传容器-->
    <div class="layui-form-item" id="uploadElementId">

    </div>

 

js代码,下面几个方法是通用的,所以可以单独放一个js里。其中预览文件只对图片做了预览

//支持的图片格式
var imgTypes = ['.bmp', '.jpg', '.png', '.tif', '.gif', '.pcx', '.tga', '.exif', '.fpx', '.svg', '.psd', '.cdr', '.pcd', '.dxf', '.ufo', '.eps',
    'ai', '.raw', '.WMF', '.webp', '.avif', '.apng'];


layui.define(['layer', 'upload'], function () {
    let $ = layui.$;
    let upload = layui.upload;
    //文件id集合
    var fileIds = [];

    window.initUpload = function (elementId) {
        //判断容器是否存在,如果不存在再渲染
        if ($("#" + elementId).children().length > 0) {
            return;
        }
        $("#" + elementId).append(
            `
            <div class="layui-input-block layui-col-md16">
                    <!--这个ids是每次上传文件后,js会重新赋值这个id,格式是逗号分开的文件id-->
                    <input type="hidden" placeholder="文件id" id="fileIds" name="fileIds"/>
                    <!--文件预览的图片容器-->
                    <div id="load-img">
        
                    </div>
                    <div class="layui-upload-drag" title="支持拖拽上传" id="upload" style="width: 80%">
                        <i class="layui-icon"></i>
                        <p>点击上传文件,或将文件拖拽到此处</p>
                    </div>
                    <!--上传后列表展示,但是如果业务表不提交,这个id也就无法保存到数据库,也就无法关联文件-->
                    <div class="layui-upload-list">
                        <table class="layui-table">
                            <thead>
                            <tr>
                                <th>文件名</th>
                                <th>操作</th>
                                <th hidden="true">文件id</th>
                            </tr>
                            </thead>
                            <tbody id="fileUploadList">
                            </tbody>
                        </table>
                    </div>
                </div>
            `
        )
        //构建文件上传
        upload.render({
            elem: '#upload'
            , url: '/upload'
            , accept: 'file'
            , multiple: true
            , done: function (res, index, upload) { //上传后的回调
                if (res.code == 0) {
                    //提示成功
                    layer.msg(res.msg, {icon: 1});
                    //把全局变量修改
                    fileIds.push(res.data.id);
                    //赋值给页面的input参数
                    $("#fileIds").val(fileIds);
                    //渲染页面数据(关键方法)
                    uploadElementRend(res.data, "fileUploadList");
                } else {
                    layer.msg(res.msg, {icon: 2});
                }
            }
        })
    }
})

/**
 * 下载文件
 * @param fileId
 */
function downFile(fileId) {
    window.open("/upload/download?fileId=" + fileId);
}

/**
 * 重命名
 * @param fileId 文件id
 * @param fileName 文件现在的名字
 */
function updateFileName(fileId, fileName) {
    fileName = fileName.substr(0, fileName.lastIndexOf("."))
    layer.prompt({
        formType: 0,
        title: '请输入名称',
        btn: ['确定', '取消'],
        btnAlign: 'c',
        value: fileName
    }, function (value, index) {

        $.ajax({
            type: "get",
            url: "/upload/updateFileName",
            cache: false,
            data: {
                "fileId": fileId,
                "fileName": value
            },
            success: function (result) {
                if (result.code === 0 || result.code === 202) {
                    $("#" + fileId + "FileName").text(result.data);
                    layer.msg(result.msg, {icon: 1});
                } else {
                    layer.msg(result.msg, {icon: 2});
                }
            },
            error: function (err) {
                layer.msg(err.responseText ? err.responseText : '无权限或操作失败!', {icon: 2});
            }
        });
        layer.close(index);
    });
}

/**
 * 打开图片
 */
function openPhoto(url, fileName) {
    let type = fileName.substr(fileName.lastIndexOf("."), fileName.length);
    if (imgTypes.includes(type)) {
        parent.layer.open({
            type: 1,
            title: fileName,
            skin: 'layui-layer-rim',
            area: ['75%', '85%'],
            shadeClose: true,
            end: function (index, layero) {
                return false;
            },
            content: '<div style="text-align:center;"><img src="/file/' + url + '" /></div>'
        });
    } else {
        layer.msg('暂不支持预览该文件!', {icon: 7});
    }
}

/**
 * 删除文件
 * @param fileId
 */
function deleteFile(fileId) {
    layer.confirm('"确定要删除该文件吗?"', {icon: 3, title: '警示'}, function (index) {
        $.ajax({
            type: "get",
            url: "/upload/deleteFile?fileId=" + fileId,
            async: false,
            success: function (data) {
                if (data.code === 202 || data.code === 0) {
                    layer.msg(data.msg, {icon: 1});
                    //删除页面上的文件列表
                    $("#" + fileId).remove();
                    //删除图片相关的元素
                    $("." + fileId).remove();
                } else {
                    layer.msg(data.msg, {icon: 2});
                }
            },
            error: function (err) {
                layer.msg(err.responseText ? err.responseText : '无权限或操作失败!', {icon: 2});
            }
        })
    })
}

/**
 * 详情或修改时的文件回显,与下面方法其实是一样的,只是这里是个集合
 * @param d 数据
 * @param elementId id
 */
function rendEditFile(d, elementId) {
    if (d != null) {
        //业务数据中的文件列表信息
        let msgList = d.fileMsgList;
        if (msgList) {
            for (let i = 0; i < msgList.length; i++) {
                uploadElementRend(msgList[i]);
            }
        }
    }
}

/**
 * 上传文件的回显  如果要自定义实现,则把下面代码复制一份进行操作
 * @param d 上传的返回值
 */
function uploadElementRend(d) {
    let fileCss = "";
    //如果是图片类型,就直接展示
    if (imgTypes.includes(d.fileType)) {
        //如果是图片 支持预览的情况下 让文字变颜色
        fileCss = "color: green;cursor: pointer;";
        //渲染图片的id是封装死的,其他页面尽量不要修改
        $("#load-img").append(
            `<img class="layui-upload-img ` + d.id + `" onclick="openPhoto('` + d.fileUrl + `','` + d.fileSourceName + `')" src="/file/` + d.fileUrl + `" width="100"/>`
        )
    }

    //构建删除按钮 这一段是跳转时候用来判断跳转类型的,如果是详情的话,就不要删除按钮
    let h = "";
    let type = getUrlParam('lookType');
    if ("detail" != type) {
        h = `<a class="layui-btn layui-btn-danger layui-btn-xs" onclick=deleteFile(` + d.id + `)>删除</a>`;
        h += `<a class="layui-btn layui-btn-normal layui-btn-xs" onclick=updateFileName('` + d.id + `','` + d.fileSourceName + `')>重命名</a>`;

    }
    //添加内容和按钮 文件列表的id是封装死的,尽量不要修改
    $("#fileUploadList").append(
        `
        <tr id="` + d.id + `">
            <td style="` + fileCss + `" id="` + d.id + `FileName" onclick="openPhoto('` + d.fileUrl + `','` + d.fileSourceName + `')">` + d.fileSourceName + `</td>
            <td>
                <a class="layui-btn layui-btn-normal layui-btn-xs" onclick=downFile(` + d.id + `)>下载</a>
                ` + h + `
                
            </td>
            <td hidden="true">` + d.id + `</td>
        </tr>
        `
    )
}

/**
 * 用于封装整个js核心方法,上述其他方法均为实现,而业务操作不需要关注实现,只需要调用下面这两个方法即可
 * @type
 */
var fileUploadFactory = {
    //初始化文件上传组件
    initUpload: function (elementId) {
        initUpload(elementId);
        lookType();
    },
    //文件上传后的ui渲染
    rendEditFile: function (d, elementId) {
        //初始化一下,如果容器存在,它不会再次初始化
        initUpload(elementId);
        //根据数据d,渲染列表信息
        rendEditFile(d, elementId);
    }
}

 

 

文件回显,是需要查询到数据后,调用上面js文件末尾的函数:

fileUploadFactory (数据,容器id);

 

html页面调用上面的js

文件上传的创建页需要调用的js,主要是:fileUploadFactory.initUpload("uploadElementId");

layui.use(['layer', 'form', 'laydate', 'upload'], function () {
    let $ = layui.$;
    let form = layui.form;
    let laydate = layui.laydate;
    laydate.render({
        elem: '#proTime'
    });
    // 提交事件
    form.on('submit(save-problem)', function (data) {
        let field = data.field;
        save(field);
        return false;
    });
//初始文件上传容器
    fileUploadFactory.initUpload("uploadElementId");
    //保存业务数据
    function save(problem) {
        $.ajax({
            type: "post",
            url: "/problem/save",
            data: JSON.stringify(problem),
            contentType: 'application/json',
            success: function (data) {
                if (data.code === 202 || data.code === 0) {
                    layer.msg(data.msg, {icon: 1});
                } else {
                    layer.msg(data.msg, {icon: 2});
                }
                closePage();
            },
            error: function (err) {
                layer.msg(err.responseText ? err.responseText : '无权限或操作失败!', {icon: 2});
            }
        })
    }

    function closePage() {
        setTimeout(function () {
            let index = parent.layer.getFrameIndex(window.name);
            parent.layer.close(index);
        }, 1000)
    }
})

 

 文件上传的详情页需要调用的js,主要是:fileUploadFactory.rendEditFile(d, "uploadElementId");

 

layui.use(['layer', 'form', 'laydate', 'upload'], function () {
    let $ = layui.$;
    let form = layui.form;
    let laydate = layui.laydate;
    laydate.render({
        elem: '#proTime'
    });
    // 修改事件
    form.on('submit(update-problem)', function (data) {
        let field = data.field;
        field.id = getUrlParam('problemId');
        update(field);
        return false;
    });
    //查数据
    let d = edit();
    //渲染页面数据
    rendEdit(d);

//渲染文件列表 fileUploadFactory.rendEditFile(d, "uploadElementId"); //判断是否需要禁用页面元素 lookType(); function update(role) { $.ajax({ type: "post", url: "/problem/update", data: JSON.stringify(role), contentType: 'application/json', success: function (data) { if (data.code === 202 || data.code === 0) { layer.msg(data.msg, {icon: 1}); } else { layer.msg(data.msg, {icon: 2}); } closePage(); }, error: function (err) { layer.msg(err.responseText ? err.responseText : '无权限或操作失败!', {icon: 2}); } }) } function closePage() { setTimeout(function () { let index = parent.layer.getFrameIndex(window.name); parent.layer.close(index); }, 1000) } }) function edit() { let d = {}; $.ajax({ type: "get", url: "/problem/detail?id=" + getUrlParam('problemId'), contentType: 'application/json', async: false, success: function (data) { d = data; } }) return d; }

 

 

 

6:后端代码分析

上传文件需要动态配置服务器的路径,不能写死。保存的文件路径只能保存设置的基本路径后面的路径,不能把服务器前面的获取的路径也保存下来。拿到文件以后,不能直接保存,需要重新命名文件名,这是防止不同的业务,但是有相同的文件。

保存时为了后期方便管理,要根据文件类型放在各自类型的文件下,否则时间长了就会很乱也不好处理

》接口配置思路:

首先需要几个基本的接口

  上传、下载、删除

还需要几个给业务的接口,方便业务操作的。

  绑定业务文件关系、业务数据中文件列表的填充、解除关联并删除文件

 

 》同时还需要提供一个实体类,用来限定文件上传的参数和定义一些方法

import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;

import java.util.List;

/**
 * 上传文件的服务接收和入参都需要继承这个类,用于统一管理字段名
 **/
@Data
public class BaseUploadMapEntity {
    /**
     * 业务表文件关联id字段名
     */
    private String serviceId;

    /**
     * 文件id集合
     */
    @TableField(exist = false)
    private String fileIds;

    /**
     * 关联的文件集合
     */
    @TableField(exist = false)
    private List<UploadEntity> fileMsgList;
}

 

后端controller

@RestController
@RequestMapping("/upload")
public class UploadController {

    private final UploadService uploadService;

    public UploadController(UploadService uploadService) {
        this.uploadService = uploadService;
    }

    /**
     * 上传文件
     *
     * @param file 文件
     * @return 上传详细信息
     */
    @PostMapping
    public LayuiPageInfo upload(MultipartFile file) {
        UploadEntity upload = uploadService.upload(file);
        if (upload == null) {
            return LayuiPageFactory.createErrInfo("文件上传失败!", -1);
        }
        return LayuiPageFactory.createLayuiPageInfo(upload);
    }

    /**
     * 文件下载
     *
     * @param fileId   文件id
     * @param response 响应
     */
    @PreAuthorize("hasAnyAuthority('/upload/download')")
    @GetMapping("/download")
    public void download(Integer fileId,
                         HttpServletResponse response) {
        uploadService.download(fileId, response);
    }

    /**
     * 删除文件
     *
     * @param fileId 文件id
     * @return 删除结果
     */
    @PreAuthorize("hasAnyAuthority('/upload/deleteFile')")
    @GetMapping("/deleteFile")
    public LayuiPageInfo deleteFile(Integer fileId,
                                    HttpServletResponse response) {
        return uploadService.deleteFile(fileId);
    }

    /**
     * 修改文件名
     * @param fileId 文件id
     * @param fileName 新文件名,建议不写后缀
     * @return
     */
    @PreAuthorize("hasAnyAuthority('/upload/updateFileName')")
    @GetMapping("/updateFileName")
    public LayuiPageInfo updateFileName(Integer fileId, @RequestParam(required = true) String fileName) {
        return uploadService.updateFileName(fileId, fileName);
    }

    /**
     * 清理没有关联的文件,释放系统磁盘空间
     * @return 清理结果
     */
    @PreAuthorize("hasAnyAuthority('/upload/clearFile')")
    @GetMapping("/clearFile")
    public LayuiPageInfo clearFile(){
        return uploadService.clearFile();
    }
}

 

service接口

public interface UploadService {
    /**
     * 上传文件
     *
     * @param file 文件
     * @return 上传详细信息
     */
    UploadEntity upload(MultipartFile file);

    /**
     * 文件下载
     *
     * @param fileId   文件id
     * @param response 响应
     */
    void download(Integer fileId, HttpServletResponse response);

    /**
     * 删除文件
     *
     * @param fileId 文件id
     * @return 删除结果
     */
    LayuiPageInfo deleteFile(Integer fileId);

    /**
     * 修改文件名
     *
     * @param fileId   文件id
     * @param fileName 新文件名
     * @return 修改结果
     */
    LayuiPageInfo updateFileName(Integer fileId, String fileName);

    /**
     * 清理没有关联的文件,释放系统磁盘空间
     *
     * @return 清理结果
     */
    LayuiPageInfo clearFile();




    //-------------------------------下面三个方法是业务操作的方法---------------------------------------

    /**
     * 绑定文件业务
     *
     * @param uploadMapEntity 文件业务基本类
     * @param type            业务类型
     */
    void saveOrUpdateFile(BaseUploadMapEntity uploadMapEntity, UploadServiceType type);

    /**
     * 文件详情的数据填充
     *
     * @param uploadMapEntity 文件业务基本类
     */
    void fileDetail(BaseUploadMapEntity uploadMapEntity);

    /**
     * 解除关联并删除文件
     *
     * @param uploadMapEntity 文件业务基本类
     * @return 删除结果
     */
    void deleteFile(BaseUploadMapEntity uploadMapEntity);
}

 

 

实现类

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * @author 文件上传实现*/
@Service
@Slf4j
@DS("userdb")
public class UploadServiceImpl implements UploadService {

    private final UploadFileMapperService fileMapperService;
    private final FileServiceMapMapperService serviceMapMapperService;

    public UploadServiceImpl(UploadFileMapperService fileMapperService, FileServiceMapMapperService serviceMapMapperService) {
        this.fileMapperService = fileMapperService;
        this.serviceMapMapperService = serviceMapMapperService;
    }

    /**
     * 配置文件配置的服务器保存路径
     */
    @Value("${application.fileUploadPath}")
    private String basePath;

    @Override
    public UploadEntity upload(MultipartFile file) {
        File toFile = null;
        if (file.equals("") || file.getSize() <= 0) {
            return null;
        } else {

            /*获取文件原名称*/
            String originalFilename = file.getOriginalFilename();
            /*获取文件格式*/
            String fileFormat = originalFilename.substring(originalFilename.lastIndexOf("."));

            String uuid = UUID.randomUUID().toString().trim().replaceAll("-", "");
            String currentPath = System.getProperty("user.dir");
            //避免命名重复导致文件覆盖
            String fileNewName = uuid + fileFormat;
            //保存路径中根据类型不同,保存在不同的文件夹
            String fileTypePath = fileFormat.substring(1, fileFormat.length()) + "/" + fileNewName;
            toFile = new File(currentPath + basePath + fileTypePath);

            String absolutePath = null;
            try {
                absolutePath = toFile.getCanonicalPath();

                /*判断路径中的文件夹是否存在,如果不存在,先创建文件夹*/
                String dirPath = absolutePath.substring(0, absolutePath.lastIndexOf(File.separator));
                File dir = new File(dirPath);
                if (!dir.exists()) {
                    dir.mkdirs();
                }

                InputStream ins = file.getInputStream();

                //写文件
                saveToServiceFile(ins, toFile);
                ins.close();

                //保存文件信息
                UploadEntity entity = new UploadEntity();
                entity.setFileSourceName(originalFilename);
                entity.setFileNewName(fileNewName);
                entity.setFileUrl(fileTypePath);
                entity.setFileType(fileFormat);
                fileMapperService.save(entity);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return fileMapperService.getOne(new LambdaQueryWrapper<UploadEntity>()
                    .eq(UploadEntity::getFileNewName, fileNewName));
        }
    }

    public UploadEntity queryUploadMsgByFileId(Integer fileId) {
        return fileMapperService.getById(fileId);
    }

    /**
     * 根据业务关联id查询文件详情
     *
     * @param serviceId 业务关联id
     * @return 文件列表详情
     */
    public List<UploadEntity> queryUploadMsgByServiceId(String serviceId) {
        List<FileServiceMap> list = serviceMapMapperService.list(new LambdaQueryWrapper<FileServiceMap>()
                .eq(FileServiceMap::getServiceId, serviceId));
        if (!CollectionUtils.isEmpty(list)) {
            List<Integer> fileIds = list.stream().map(f -> f.getFileId()).collect(Collectors.toList());
            return fileMapperService.list(new LambdaQueryWrapper<UploadEntity>()
                    .in(UploadEntity::getId, fileIds));
        }
        return Collections.emptyList();
    }

    /**
     * 构建文件关联的map对象
     *
     * @param serviceId 业务关联id
     * @param fileIds   文件id集合
     * @param type      业务类型
     * @return 构建的集合
     */
    public List<FileServiceMap> buildFileServiceMap(String serviceId, String fileIds, UploadServiceType type) {

        return Arrays.asList(fileIds.split(",")).stream().map(f -> {
            FileServiceMap map = new FileServiceMap();
            map.setServiceId(serviceId);
            map.setFileId(Integer.valueOf(f));
            map.setServiceType(type.name());
            return map;
        }).collect(Collectors.toList());
    }

    /**
     * 文件下载
     *
     * @param fileId   文件id
     * @param response 响应
     */
    @Override
    public void download(Integer fileId, HttpServletResponse response) {
        try {
            UploadEntity upload = queryUploadMsgByFileId(fileId);
            if (upload == null) {
                return;
            }
            String fileUrl = upload.getFileUrl();
            if (!StringUtils.isEmpty(fileUrl)) {
                String currentPath = System.getProperty("user.dir");
                File uploadFile = new File(currentPath + basePath + fileUrl);
                ServletOutputStream outputStream = response.getOutputStream();
                response.addHeader("Content-Disposition", "attachment;filename =" + URLEncoder.encode(upload.getFileSourceName(), "UTF-8"));
                response.setContentType("application/octet-stream");
                byte[] bytes = FileUtil.readBytes(uploadFile);
                outputStream.write(bytes);
                outputStream.flush();
                outputStream.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("文件下载异常!详细信息:{}", e.getMessage());
        }
    }

    /**
     * 文件删除
     *
     * @param fileId 文件id
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public LayuiPageInfo deleteFile(Integer fileId) {
        UploadEntity upload = queryUploadMsgByFileId(fileId);
        if (upload == null) {
            return LayuiPageFactory.createErrInfo("文件不存在!", -1);
        }
        //删除业务关联关系
        boolean b = serviceMapMapperService.remove(new LambdaQueryWrapper<FileServiceMap>()
                .eq(FileServiceMap::getFileId, fileId));
        boolean removeFileMsg = fileMapperService.removeById(fileId);
        if (b || removeFileMsg) {
            String currentPath = System.getProperty("user.dir");
            File file = new File(currentPath + basePath + upload.getFileUrl());
            if (file.exists()) {
                boolean delete = file.delete();
                log.warn("删除文件:" + upload.getFileSourceName() + (delete ? "(成功)" : "(失败)"));
                return delete ? LayuiPageFactory.createMsgInfo("文件删除成功!") : LayuiPageFactory.createErrInfo("文件删除失败!", -1);
            }
        }
        return LayuiPageFactory.createErrInfo("文件关联关系解除失败!", -1);
    }

    /**
     * 保存业务文件关联表
     *
     * @param serviceId
     * @param fileIds
     * @param serviceType
     */
    public void saveFileServiceMap(String serviceId, String fileIds, UploadServiceType serviceType) {
        if (fileIds != null && !"".equals(fileIds)) {
            //构建文件关系集合
            List<FileServiceMap> map = buildFileServiceMap(serviceId, fileIds, serviceType);
            boolean b = serviceMapMapperService.saveBatch(map);
            log.info("[" + serviceType.getDesc() + "]业务保存文件" + (b ? "成功!" : "失败!"));
        }
    }

    /**
     * 生成业务关联id
     *
     * @return
     */
    public String generateServiceId() {
        return UUID.randomUUID().toString().trim().replaceAll("-", "");
    }

    /**
     * 修改文件名
     *
     * @param fileId   文件id
     * @param fileName 新文件名
     * @return
     */
    @Override
    public LayuiPageInfo updateFileName(Integer fileId, String fileName) {
        if (StringUtils.isEmpty(fileName) || "".equals(fileName.trim())) {
            return LayuiPageFactory.createErrInfo("文件名不能为空!", -1);
        }
        //1:根据id查询文件信息
        UploadEntity fileEntity = fileMapperService.getById(fileId);
        if (fileEntity == null) {
            return LayuiPageFactory.createErrInfo("文件不存在!", -1);
        }
        //2:判断新文件名是否有相同后缀
        if (fileName.endsWith(fileEntity.getFileType())) {
            fileEntity.setFileSourceName(fileName);
        } else {
            int typeFlag = fileName.indexOf(".");
            if (typeFlag > 0) {
                //切割一下,防止页面传错
                fileName = fileName.substring(0, typeFlag);
            }
            fileEntity.setFileSourceName(fileName + fileEntity.getFileType());
        }
        boolean b = fileMapperService.updateById(fileEntity);
        LayuiPageInfo msgInfo = LayuiPageFactory.createMsgInfo("文件修改成功!");
        msgInfo.setData(fileName + fileEntity.getFileType());
        return b ? msgInfo : LayuiPageFactory.createErrInfo("文件修改失败!", -1);
    }

    @Override
    public LayuiPageInfo clearFile() {
        log.warn("开始清理服务多余文件!");
        //1:查询中间表和文件表差集
        List<UploadEntity> uploadEntities = fileMapperService.queryFileNotInMap();
        //2:根据查询出来的文件信息,把文件都删掉,需要排错,有些数据存在 但文件可能已经没有了
        if (!CollectionUtils.isEmpty(uploadEntities)) {
            uploadEntities.forEach(f -> deleteFile(f.getId()));
            return LayuiPageFactory.createMsgInfo("本次共清理" + uploadEntities.size() + "个文件!");
        }
        return LayuiPageFactory.createMsgInfo("没有多余文件清理!");
    }

    /**
     * 写文件
     *
     * @param ins
     * @param file
     */
    private static void saveToServiceFile(InputStream ins, File file) {
        OutputStream os = null;
        try {
            os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            ins.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 渲染查询时文件列表详情数据
     *
     * @param uploadMapEntity 文件业务基本类
     * @param type
     */
    @Override
    public void saveOrUpdateFile(BaseUploadMapEntity uploadMapEntity, UploadServiceType type) {
        //生成id
        if (StringUtils.isEmpty(uploadMapEntity.getServiceId())
                && (uploadMapEntity.getFileIds() != null && !"".equals(uploadMapEntity.getFileIds()))) {
            uploadMapEntity.setServiceId(generateServiceId());
        }
        //保存中间表信息
        saveFileServiceMap(uploadMapEntity.getServiceId(), uploadMapEntity.getFileIds(), type);
    }

    /**
     * 渲染查询时文件列表详情数据
     *
     * @param uploadMapEntity 文件业务基本类
     */
    @Override
    public void fileDetail(BaseUploadMapEntity uploadMapEntity) {
        //设置文件详情集合
        uploadMapEntity.setFileMsgList(queryUploadMsgByServiceId(uploadMapEntity.getServiceId()));
    }

    /**
     * @param uploadMapEntity 文件业务基本类
     */
    @Override
    public void deleteFile(BaseUploadMapEntity uploadMapEntity) {
        List<UploadEntity> entityList = queryUploadMsgByServiceId(uploadMapEntity.getServiceId());
        if (!CollectionUtils.isEmpty(entityList)) {
            entityList.forEach(f -> deleteFile(f.getId()));
        }
    }
}

 

 

使用的时候,让业务实体类去继承这个类,就可以方便和规范的设置数据。具体看后面的业务实现代码

里面的两个service对应的是两个数据库表的service,是mybatisPlus做的。就不在贴代码

js中预览图片的路径是/file开头的,没有完全的服务器路径,也不能写全服务器路径。所以后端需要写一个配置文件映射一下地址:把/file开头的请求,映射到指定的文件路径下,这样就页面就可以通过请求直接看到图片了

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Value("${application.fileUrl}")
    private String fileUrl;

    @Value("${application.fileUploadPath}")
    private String fileUploadPath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //将匹配上/file/**虚拟路径的url映射到文件上传到服务器的目录,
        registry.addResourceHandler("/" + fileUrl + "/**")
                .addResourceLocations("file:" + System.getProperty("user.dir") + fileUploadPath);
        WebMvcConfigurer.super.addResourceHandlers(registry);
    }
}

 这段代码表示如:localhost:8080/file/xxx.png

在后端会映射到:本地盘/指定的fileUploadPath/xxx.png

到此,文件上传一系列接口都完善了,然后就是业务关联

 

7:业务上传处理思路

页面提交时会携带一个fileIds字段,这个字段是用逗号分隔的文件id,后端拿到后,直接调用文件服务的保存方法,方法里面已经赋值serviId了。

实体类变化

import com.baomidou.mybatisplus.annotation.TableName;
import com.test.trigger.upload.entity.BaseUploadMapEntity;
import lombok.Data;

import java.util.Date;

/**
 * 业务实体(正常情况下,对接数据库的类是不能作为返回页面的,这里没有封装vo)*/
@Data
@TableName("problem")
public class ProblemEntity extends BaseUploadMapEntity {
    private int id;

    /**
     * 环境
     */
    private String env;
    /**
     * 服务名
     */
    private String serviceName;

    /**
     * 标题
     */
    private String title;
    /**
     * 问题类型
     */
    private String type;
    /**
     * 问题
     */
    private String problem;
    /**
     * 处理方式
     */
    private String handleMethod;
    /**
     * 处理负责人
     */
    private String handleHead;
    /**
     * 问题发生时间
     */
    private String proTime;
    /**
     * 状态
     */
    private int status;
    private Date createDate;

    public void create() {
        setCreateDate(new Date());
    }
}

 

上面关键的地方在于继承了文件上传的基本类。理论上返回的数据应该要封装vo或者其他的非数据库实体的对象,这里为了方便就都写一块

 

保存时写法

@Override
    @Transactional(rollbackFor = {Exception.class})
    public LayuiPageInfo save(ProblemEntity problem) {
        problem.create();
        //生成业务关联id 并保存。内部根据是否存在关联id而选择是否需要赋值
        uploadService.saveOrUpdateFile(problem, UploadServiceType.PROBLEM_SERVICE_TYPE);
        //保存业务数据
        boolean b = problemMapperService.save(problem);
        return b ? LayuiPageFactory.createMsgInfo("保存成功!") : LayuiPageFactory.createErrInfo("保存失败!", -1);
    }

 

页面需要文件关联详情的,也就是详情和修改的时候,list集合的时候,一般不用查

@Override
    public ProblemEntity detail(int id) {
        //查业务数据
        ProblemEntity entity = problemMapperService.getById(id);
        //查文件详情集合并赋值
        uploadService.fileDetail(entity);
        return entity;
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public LayuiPageInfo update(ProblemEntity problem) {//生成业务关联id 并保存。内部根据是否存在关联id而选择是否需要赋值
        uploadService.saveOrUpdateFile(problem, UploadServiceType.PROBLEM_SERVICE_TYPE);
        boolean b = problemMapperService.updateById(problem);

        return b ? LayuiPageFactory.createMsgInfo("修改成功!") : LayuiPageFactory.createErrInfo("修改失败!", -1);
    }

    @Override
    @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
    public LayuiPageInfo delete(String id) {
        //1:查询中间表文件,删除文件
        ProblemEntity problemEntity = problemMapperService.getById(id);
        if (problemEntity != null) {
            uploadService.deleteFile(problemEntity);
            //2:删除问题数据
            boolean b = problemMapperService.removeById(id);
            return b ? LayuiPageFactory.createMsgInfo("删除成功!") : LayuiPageFactory.createErrInfo("删除失败!", -1);
        }
        return LayuiPageFactory.createErrInfo("文件不存在!", -1);
    }

 

最后的效果

查看

 

 

 修改

 点击放大

 

 服务器的文件就是这样分部的

 

 

文件详情表

 

 文件业务关联表

 

 

 业务表字段

 

posted @ 2023-12-04 16:50  鸭猪是的念来过倒  阅读(1372)  评论(0编辑  收藏  举报